From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.221.43 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793744101; b=4MSGVWx0+AE4dOmYhekqONM/ZXRH6lW5TIYVS4cOu0iB5LR+LE0T3W6TMxYV8iXSv9Di QF7J3aV/s73Rf4Bc9f0Lh+vQ+QPMXBysNotcoCfu1D7E64pjaErbtffAVwumMUg8bBZ0W GKuKyk/9Pvks1uW5+24zNoyyjBYLtOOixtmdmADocvgynsqDytZplphZVwj/st7soA6C8 mBRCg7/eoMiPIpXhCA3Pn3iubhbJo1C8GPTRJS9OScr+RINKRzWvW8aT+TufiI3AKupQY spzZOc5XFyLYhuoLOwH8B6erwYt/H/lbrurBOcd4uk5wotwcbly0qXcLWpcbHbl7I/Q== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793744101; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding; bh=uFwxZU1RMzxyqTuYb4ke0p/MN7TmaXHcXhZ6EGMkUAU=; b=SXFHt+V2fcEhgsnGvBSm7WddiPrqkfAu5paN7bQ/lnD9oo36d0/0sMtw6Qo2LqYIEpQx brIHlV8SITxWWzwpfwITL+GJ/GAsCSmu9uE7RisPI81aTC3aUZvSKVMUXqt/3F3fBTdKU e0KPOjRoF1fneByl+k6ucyOff8FPCqhTDuZEt7Efs7r1zq1EdOwzYZEjbOeLbCwq8gmbd pVGHi1PJ4xf6VmEnMyLdfViQWsw/sqmzF9ZeRUmPCjFeLPY4+FnC7Kiiwikkzjh+U96J5 /dNw1rv2Y7MbS+S359mnA6HTcrznCOg4g+DM7pmPgFbn9HhthOiELa560M1mZzEUm8Q== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=BSQ4imhC; arc=none smtp.remote-ip=209.85.221.43 Received: from mail-wr1-f43.google.com (mail-wr1-f43.google.com [209.85.221.43]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 26902606EEA5 for ; Tue, 3 Nov 2026 22:15:00 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 26902606EEA5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=BSQ4imhC Received: by mail-wr1-f43.google.com with SMTP id ffacd0b85a97d-48df8fe8513so71165f8f.0 for ; Tue, 03 Nov 2026 14:14:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793744098; x=1794348898; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=uFwxZU1RMzxyqTuYb4ke0p/MN7TmaXHcXhZ6EGMkUAU=; b=BSQ4imhCfdwT89Lf48JVSl8kt/ucBnYwCn6DKXSuqmG3HlqgyCiyyAL5+EEwiDqpBB 7vST/DJn23/RS5kVEIJuQCFZRPqwFH3yRUgcNf06+1BNcMeGHiexQVrtfF149ONgdPrB qbrEXiYUI3s+zBbMS7mkczIQzh+q8MuKPfNaNr8ACm6aivq/f04RJQL9ARxsd52YdkfB 2geqr9WuSNXP2GlzBo9PbrEymJiJzz9cgfo9hOQaUtvs6gvHDrSWXFNsb08YYDbqywrX v9LhvgMk4VNQs0ADdlNydq+lRbPGBqkcaxm5szNDz91ylDIzFxcPEyJJGAIx/0tSFqFO tWGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793744098; x=1794348898; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=uFwxZU1RMzxyqTuYb4ke0p/MN7TmaXHcXhZ6EGMkUAU=; b=bB7/hFJIHpK2HG2kWpw7Nsh7cm9E46wrAmxNHnI94e90ivHCrR1J3X65j3aIENvCNm SbkqQiQsQ3/LsXiuWh91KYN29b+r579iWxKD+VdbAvWhJaqd8i3jGgCMXu0YOc1qIBXn GhfzPO6tkPX0mh5kaQeE5G379SQa8rtFDfiPk+75aPz628O6pFDEZoAAMhN84HZQiEjf A5LJbn7BfOVZflLznqZL3dKQA5PUdDlly1LR0wxSxHhehMDcR44bY53kJpPHqKLSmi8+ eh/10nlGbfwFsZSSn3QHJG6/lvdIRa9rBKVuHMrqb2euakHOLqyq9Bj5rJvl9UsEvK5W dLjg== X-Forwarded-Encrypted: i=1; AKwUvBzz+stHyPx74kTSr4nKb8DZGBYgnLD9o7a6OgoOMMbNFrj2Io+r9HIe9zMipuNn8qBzM9jt5WU=@vger.kernel.org X-Gm-Message-State: APuU4Z9yNyuqT5Q0/WxVhxPgzqqW+rLK1UavcwL9+/6kFMKrYcOzGKkU fq8c7hqqqYLdTc8qG6OlVUyKwMlx+RJ5tMr0VkMGu7dPtfaiAzyLBnnM X-Gm-Gg: AYBFou0eJp+blK8MoNZRUzWPAB1y71dP8jaKvBp9rTy1tN6s6NIbix5x4/S58rIGn32 cmGYkNn/D9y9qMi30fC2+ULCZqMTbAbFlxg48UzAMwN8HTLnK9MY3f0C31GIxdDUcQhLhcSHz/C pHWfs0hfEVoxMI65HZXCHqWG+CTwNgNkN6A48xX9K/V6YRTdl/RYHndqdMchVNzfgZgYokIrhvF IpSTI8Fwp7GC3Eh8VOC9ea9b3tUFB2F2hROSEpL17jCwzgxRj4MCZAYIF1XhaV69A0u72ASBz4B LnWuMIhqpTbrIBAfvls7G8sp7v91vAqaYBA3PsFBClk7VTAilk6OnYTYxU89FpiV6TzE5nrAtvL sIiJhCxfwBTqVLQkoxtK7FZwzvKro2Jp8WIoFlV+E6uPd5veNVs/FdgrzW3KsZSLOzqtts3b2oR L89afDJuWOMJMPsCPWlQaOozyp1HZYBdZYBSPg1j5W6tRwaFER4LLn8+f5RwmaqiL3kxUaHUz6q Bz4priBS1LasclOdYkWaG2na6V57xT9pEW5fqvPgcncroWYCZM6X5bwufwGCaQx+ZOuHg7QTDbp t3rPG/n3sb6jkE7+cpweLPp5tm0mCwoNrQ6DiNDt5bCUTArkcFe0Ds+D/gmeM5FKn5fK7N+cCUo n8XHFbvRm4Zn4ExUKYsbci1HcNvkpkBCS6w== X-Received: by 2002:a05:6000:2884:b0:48e:6eb1:3d02 with SMTP id ffacd0b85a97d-48e6eb13d20mr30262983f8f.0.1793744098073; Tue, 03 Nov 2026 14:14:58 -0800 (PST) Received: from Ansuel-XPS24.localdomain (93-34-90-148.ip49.fastwebnet.it. [93.34.90.148]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-48fec9cd43bsm328556f8f.14.2026.11.03.14.14.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 03 Nov 2026 14:14:57 -0800 (PST) From: Christian Marangi To: Andrew Lunn , Heiner Kallweit , Russell King , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Christian Marangi , Daniel Golle , Qingfang Deng , SkyLake Huang , Matthias Brugger , AngeloGioacchino Del Regno , Joris Vaisvila , Ahmed Naseef , linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-mediatek@lists.infradead.org Subject: [PATCH net-next v4 5/6] net: phy: mediatek: add calibration logic for AN7581 Date: Tue, 3 Nov 2026 23:14:36 +0100 Message-ID: <20261103221439.30502-6-ansuelsmth@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261103221439.30502-1-ansuelsmth@gmail.com> References: <20261103221439.30502-1-ansuelsmth@gmail.com> X-Mailing-List: netdev@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Airoha AN7581 require dedicated calibration and specific tuneup to make the Ethernet port work on specific HW PCB layout. The calibration process is similar to Mediatek PHY but Airoha PHY use Software calibration process instead of making use of EFUSE. It does also require some complex table for TX Amp compensation and multiple register settings for EEE and 10/100M to correctly work. Also the PHY apply a different TX Amp compensation table based on the SoC PDIDR version. Such version info is stored in the SCU SoC registers. Add module version as suggested by Airoha to identify the calibration logic with a specific version. Signed-off-by: Christian Marangi --- drivers/net/phy/mediatek/airoha-ge-soc.c | 1526 ++++++++++++++++++++++ drivers/net/phy/mediatek/mtk-ge-soc.h | 17 + 2 files changed, 1543 insertions(+) diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c index e40e4597cd94..17fd741f8922 100644 --- a/drivers/net/phy/mediatek/airoha-ge-soc.c +++ b/drivers/net/phy/mediatek/airoha-ge-soc.c @@ -1,16 +1,1519 @@ // SPDX-License-Identifier: GPL-2.0+ +#include #include #include +#include "../phylib.h" #include "mtk.h" #include "mtk-ge-soc.h" +#define AIROHA_DEFAULT_PORT0_ADDR 0x9 #define AIROHA_PHY_MAX_LEDS 2 +enum airoha_mdi_resister_type { + MDI_0R, + MDI_5R, + + MDI_TYPE_MAX, +}; + +enum airoha_transformer_type { + DISCRETE, + TXMR, + + TRANSFORMER_TYPE_MAX, +}; + +enum airoha_calib_const_type { + TX_AMP_TEST_A, + TX_AMP_TEST_B, + TX_AMP_TEST_C, + TX_AMP_TEST_D, + TX_AMP_1G_A, + TX_AMP_1G_B, + TX_AMP_1G_C, + TX_AMP_1G_D, + TX_AMP_100M_A, + TX_AMP_100M_B, + TX_AMP_10M_A, + TX_AMP_10M_B, + R50_A, + R50_B, + R50_C, + R50_D, + + CALIB_CONST_TYPE_MAX, +}; + +struct airoha_socphy_shared { + struct phy_device *phydev_p0; + enum airoha_transformer_type transformer_type[4]; + enum airoha_mdi_resister_type mdi_resister_type; + bool rext_sw_calib_done; + int (*tx_amp_compensation_tbl)[TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4]; + u8 *r50_cal_tbl; + u32 soc_pdidr; +}; + +/* + * 2 chip revision + * TXMR or discrete + * 2 MDI type + * TX AMP test/TX AMP 1G/TX AMP 100M/TX AMP 10M/R50 + * 4 PHY + */ +static int an7581_tx_amp_compensation_tbl[2][TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4] = { + { /* IC version 1 */ + [DISCRETE] = { + { }, + { + [TX_AMP_TEST_A] = { -9, -7, -7, -5 }, + [TX_AMP_TEST_B] = { -9, -9, -7, -4 }, + [TX_AMP_TEST_C] = { -9, -7, -9, -6 }, + [TX_AMP_TEST_D] = { -11, -9, -10, -8 }, + [TX_AMP_1G_A] = { -9, -7, -7, -5 }, + [TX_AMP_1G_B] = { -9, -8, -7, -4 }, + [TX_AMP_1G_C] = { -9, -7, -9, -6 }, + [TX_AMP_1G_D] = { -11, -8, -10, -8 }, + [TX_AMP_100M_A] = { 4, 3, 4, 4 }, + [TX_AMP_100M_B] = { 4, 4, 5, 4 }, + [TX_AMP_10M_A] = { 0, 0, 0, 0 }, + [TX_AMP_10M_B] = { 0, 0, 0, 0 }, + [R50_A] = { 0, 0, 0, 0 }, + [R50_B] = { 0, 0, 0, 0 }, + [R50_C] = { 0, 0, 0, 0 }, + [R50_D] = { 0, 0, 0, 0 }, + }, + }, + [TXMR] = { + { }, + { + [TX_AMP_TEST_A] = { -12, -11, -9, -6 }, + [TX_AMP_TEST_B] = { -9, -13, -9, -5 }, + [TX_AMP_TEST_C] = { -5, -4, -3, 1 }, + [TX_AMP_TEST_D] = { -9, -8, -7, -5 }, + [TX_AMP_1G_A] = { -12, -11, -9, -6 }, + [TX_AMP_1G_B] = { -9, -13, -9, -5 }, + [TX_AMP_1G_C] = { -5, -4, -3, 1 }, + [TX_AMP_1G_D] = { -9, -8, -7, -5 }, + [TX_AMP_100M_A] = { -2, -2, 0, 2 }, + [TX_AMP_100M_B] = { -2, -2, 1, 1 }, + [TX_AMP_10M_A] = { 0, 0, 0, 0 }, + [TX_AMP_10M_B] = { 0, 0, 0, 0 }, + [R50_A] = { 15, 15, 15, 15 }, + [R50_B] = { 15, 15, 18, 15 }, + [R50_C] = { -8, -8, -8, -8 }, + [R50_D] = { -8, -8, -8, -8 }, + }, + }, + }, + { /* IC version 2 */ + [DISCRETE] = { + { }, + { + [TX_AMP_TEST_A] = { -11, 11, -12, -7 }, + [TX_AMP_TEST_B] = { -9, -10, -9, -8 }, + [TX_AMP_TEST_C] = { -6, -3, -7, -4 }, + [TX_AMP_TEST_D] = { -7, -8, -10, -7 }, + [TX_AMP_1G_A] = { -11, -11, -12, -7 }, + [TX_AMP_1G_B] = { -9, -10, -9, -8 }, + [TX_AMP_1G_C] = { -6, -3, -7, -4 }, + [TX_AMP_1G_D] = { -7, -8, -10, -7 }, + [TX_AMP_100M_A] = { 2, 2, 1, 2 }, + [TX_AMP_100M_B] = { 3, 3, 3, 3 }, + [TX_AMP_10M_A] = { 0, 0, 0, 0 }, + [TX_AMP_10M_B] = { 0, 0, 0, 0 }, + [R50_A] = { 12, 12, 12, 12 }, + [R50_B] = { 7, 7, 7, 7 }, + [R50_C] = { 2, 2, 0, 0 }, + [R50_D] = { 0, 0, 2, 2 }, + }, + }, + [TXMR] = { + { }, + { + [TX_AMP_TEST_A] = { -10, -12, -12, -11 }, + [TX_AMP_TEST_B] = { -10, -11, -11, -10 }, + [TX_AMP_TEST_C] = { -5, -7, -9, -3 }, + [TX_AMP_TEST_D] = { -8, -10, -7, -8 }, + [TX_AMP_1G_A] = { -12, -6, -6, 4 }, + [TX_AMP_1G_B] = { -10, -5, -5, 1 }, + [TX_AMP_1G_C] = { -5, -3, -4, 11 }, + [TX_AMP_1G_D] = { -8, -3, -3, 4 }, + [TX_AMP_100M_A] = { 2, 1, 0, 3 }, + [TX_AMP_100M_B] = { 0, 0, 0, 3 }, + [TX_AMP_10M_A] = { 0, 0, 0, 0 }, + [TX_AMP_10M_B] = { 0, 0, 0, 0 }, + [R50_A] = { 15, 15, 15, 15 }, + [R50_B] = { 15, 15, 18, 15 }, + [R50_C] = { 4, 4, 4, 4 }, + [R50_D] = { 4, 4, 4, 4 }, + }, + }, + }, +}; + +static u8 an7581_zcal_to_r45ohm[64] = { + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 123, 119, 115, 112, 108, 104, 100, 96, + 94, 92, 88, 85, 82, 80, 76, 74, 72, 68, 66, 64, 62, 60, 56, 55, + 52, 50, 48, 46, 44, 42, 40, 39, 36, 35, 32, 32, 30, 28, 27, 25 +}; + +static int airoha_cal_cycle(struct phy_device *phydev, int devad, + u32 regnum, u16 mask, u16 cal_val) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + struct phy_device *phydev_p0; + int ret; + + phydev_p0 = shared->phydev_p0; + + phy_modify_mmd(phydev, devad, regnum, mask, cal_val); + + ret = mtk_cal_cycle_wait(phydev_p0); + phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret); + + return ret; +} + +static int airoha_rext_cal_sw(struct phy_device *phydev) +{ + int calibration_polarity; + u8 zcal_ctrl = 32; + int first_calib; + int ret; + + /* BG voltage output */ + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0xc000); + /* tst_mode2 */ + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xff, 0x2); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN | + MTK_PHY_RG_REXT_CALEN); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0); + + phydev_dbg(phydev, "Start REXT SW cal.\n"); + first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5, + MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl); + + if (first_calib < 0) { + phydev_err(phydev, "REXT SW calibration failed.\n"); + return -EINVAL; + } + + /* If REXT calibration failed: + * - increase dB until calibration succeed. + * If REXT calibration succeeded: + * - decrease dB until calibration fail to fine tune it. + */ + if (first_calib == 1) + calibration_polarity = -1; + else + calibration_polarity = 1; + + while (zcal_ctrl > 0 && + zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) { + zcal_ctrl += calibration_polarity; + + ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5, + MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl); + /* Exit if we either failed or succeeded compared to the + * first calibration result. (aka we finished fine tuning or + * we succeeded with calibration) + */ + if (ret != first_calib) + break; + } + + if (ret < 0) { + phydev_err(phydev, "REXT SW calibration failed.\n"); + return -EINVAL; + } + + if (zcal_ctrl == 0 || + zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) { + zcal_ctrl = 32; + phydev_err(phydev, "REXT SW calibration saturation. Defaulting to %x.\n", + zcal_ctrl); + } + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5, + MTK_PHY_RG_REXT_TRIM_MASK, + FIELD_PREP(MTK_PHY_RG_REXT_TRIM_MASK, zcal_ctrl)); + phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL, + MTK_PHY_RG_BG_RASEL_MASK, + FIELD_PREP(MTK_PHY_RG_BG_RASEL_MASK, zcal_ctrl >> 3)); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + + return 0; +} + +static int airoha_tx_offset_cal_sw(struct phy_device *phydev, u8 txg_calen_x) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + struct phy_device *phydev_p0; + u16 dev1e_145_tmp, bmcr_tmp; + int calibration_polarity; + u16 reg_dac1, reg_dac2; + int zcal_ctrl = 32; + int first_calib; + u16 reg, mask; + int ret; + + phydev_p0 = shared->phydev_p0; + + /* BG voltage output */ + phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ANA_CALEN); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_TXVOS_CALEN); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ANA_CALEN); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_TXVOS_CALEN); + + /* Force 1G full duplex for calibration */ + bmcr_tmp = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000); + + /* Force MDI */ + dev1e_145_tmp = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x0145); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, 0x1010); + + /* 1e_96[15]:bypass_tx_offset_cal, Hw bypass, Fw cal */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, + 0x8000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, + 0xf808); + + switch (txg_calen_x) { + case PAIR_A: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_A; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_A; + reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B; + mask = MTK_PHY_CR_TX_AMP_OFFSET_A_MASK; + break; + case PAIR_B: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(8)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_B; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_B; + reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B; + mask = AIROHA_PHY_CR_TX_AMP_OFFSET_B_MASK; + break; + case PAIR_C: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(4)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_C; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_C; + reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D; + mask = MTK_PHY_CR_TX_AMP_OFFSET_C_MASK; + break; + case PAIR_D: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(0)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_D; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_D; + reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D; + mask = AIROHA_PHY_CR_TX_AMP_OFFSET_D_MASK; + break; + default: + return -EINVAL; + } + + phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac1, + 0x8000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac2, + 0x8000); + + phydev_dbg(phydev, "Start TX Offset SW cal.\n"); + + first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, mask, + zcal_ctrl << __ffs(mask)); + + /* If TX Offset calibration failed: + * - increase dB until calibration succeed. + * If TX Offset calibration succeeded: + * - decrease dB until calibration fail to fine tune it. + */ + if (first_calib == 1) + calibration_polarity = -1; + else + calibration_polarity = 1; + + while (zcal_ctrl > 0 && zcal_ctrl < 63) { + u32 val; + + zcal_ctrl += calibration_polarity; + if (zcal_ctrl >= 0) + val = zcal_ctrl; + else + val = 63; + + ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, mask, + val << __ffs(mask)); + /* Exit if we either failed or succeeded compared to the + * first calibration result. (aka we finished fine tuning or + * we succeeded with calibration) + */ + if (ret != first_calib) + break; + } + + if (ret < 0) { + phydev_err(phydev, "TX Offset calibration failed.\n"); + ret = -EINVAL; + } + + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0x0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, 0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0); + + /* Restore BMCR */ + phy_write(phydev, MII_BMCR, bmcr_tmp); + + /* Restore MDI */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, dev1e_145_tmp); + + return ret < 0 ? ret : 0; +} + +static int airoha_tx_amp_cal_sw(struct phy_device *phydev, u8 txg_calen_x) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + u16 mask_gbe, mask_tbt, mask_tst, mask_hbt; + u16 reg, reg_100, reg_dac1, reg_dac2; + struct phy_device *phydev_p0; + int calibration_polarity; + u8 zcal_ctrl = 32; + int first_calib; + int ret; + + phydev_p0 = shared->phydev_p0; + + /* BG voltage output */ + phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x145, 0x1010); + + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_TXVOS_CALEN); + + /* select 1V */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10); + + /* enable Tx VLD*/ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xf808); + + /* Force 1G full duplex for calibration */ + phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, + 0x8000 | 0xf0); + + switch (txg_calen_x) { + case PAIR_A: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_A; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_A; + reg = MTK_PHY_TXVLD_DA_RG; + mask_gbe = MTK_PHY_DA_TX_I2MPB_A_GBE_MASK; + mask_tbt = MTK_PHY_DA_TX_I2MPB_A_TBT_MASK; + reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_A2; + mask_hbt = MTK_PHY_DA_TX_I2MPB_A_HBT_MASK; + mask_tst = MTK_PHY_DA_TX_I2MPB_A_TST_MASK; + break; + case PAIR_B: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(8)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_B; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_B; + reg = MTK_PHY_TX_I2MPB_TEST_MODE_B1; + mask_gbe = MTK_PHY_DA_TX_I2MPB_B_GBE_MASK; + mask_tbt = MTK_PHY_DA_TX_I2MPB_B_TBT_MASK; + reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_B2; + mask_hbt = MTK_PHY_DA_TX_I2MPB_B_HBT_MASK; + mask_tst = MTK_PHY_DA_TX_I2MPB_B_TST_MASK; + break; + case PAIR_C: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(4)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_C; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_C; + reg = MTK_PHY_TX_I2MPB_TEST_MODE_C1; + mask_gbe = MTK_PHY_DA_TX_I2MPB_C_GBE_MASK; + mask_tbt = MTK_PHY_DA_TX_I2MPB_C_TBT_MASK; + reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_C2; + mask_hbt = MTK_PHY_DA_TX_I2MPB_C_HBT_MASK; + mask_tst = MTK_PHY_DA_TX_I2MPB_C_TST_MASK; + break; + case PAIR_D: + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(0)); + reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_D; + reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_D; + reg = MTK_PHY_TX_I2MPB_TEST_MODE_D1; + mask_gbe = MTK_PHY_DA_TX_I2MPB_D_GBE_MASK; + mask_tbt = MTK_PHY_DA_TX_I2MPB_D_TBT_MASK; + reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_D2; + mask_hbt = MTK_PHY_DA_TX_I2MPB_D_HBT_MASK; + mask_tst = MTK_PHY_DA_TX_I2MPB_D_TST_MASK; + break; + default: + return -EINVAL; + } + + phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac1, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac2, + 0x8000 | 0xf0); + + phydev_dbg(phydev, "Start TX Amp SW cal.\n"); + first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, + mask_gbe, zcal_ctrl << __ffs(mask_gbe)); + + /* If TX Amp calibration failed: + * - increase dB until calibration succeed. + * If TX Amp calibration succeeded: + * - decrease dB until calibration fail to fine tune it. + */ + if (first_calib == 1) + calibration_polarity = -1; + else + calibration_polarity = 1; + + while (zcal_ctrl > 0 && + zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) { + zcal_ctrl += calibration_polarity; + + ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, + mask_gbe, zcal_ctrl << __ffs(mask_gbe)); + /* Exit if we either failed or succeeded compared to the + * first calibration result. (aka we finished fine tuning or + * we succeeded with calibration) + */ + if (ret != first_calib) + break; + } + + if (ret < 0) { + phydev_err(phydev, "TX AMP calibration failed.\n"); + zcal_ctrl = 32; + phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg, + mask_gbe | mask_tbt, + (zcal_ctrl << __ffs(mask_gbe)) | + (zcal_ctrl << __ffs(mask_tbt))); + + return -EINVAL; + } + + if (zcal_ctrl == 0 || + zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) + phydev_warn(phydev, "TX AMP SW calibration saturation.\n"); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg, + mask_gbe | mask_tbt, + (zcal_ctrl << __ffs(mask_gbe)) | + (zcal_ctrl << __ffs(mask_tbt))); + + phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg_100, + mask_hbt | mask_tst, + (zcal_ctrl << __ffs(mask_hbt)) | + (zcal_ctrl << __ffs(mask_tst))); + + phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, 0x145, 0x1000); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0); + + /* Restore AN enable */ + phy_write(phydev, MII_BMCR, BMCR_ANENABLE); + + return 0; +} + +static int airoha_tx_r50_cal_sw(struct phy_device *phydev, u8 txg_calen_x) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + struct phy_device *phydev_p0; + u16 dev1e_145_tmp, bmcr_tmp; + int calibration_polarity; + u8 zcal_ctrl = 32; + int first_calib; + u16 val; + int ret; + + phydev_p0 = shared->phydev_p0; + + /* BG voltage output */ + phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000); + + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + /* select 1V */ + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + /* select 1V */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10); + + /* Force 1G full duplex for calibration */ + bmcr_tmp = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000); + + /* Force MDI */ + dev1e_145_tmp = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x0145); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, 0x1010); + + /* disable tx slew control */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0185, 0x0000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00fb, 0x0100); + + switch (txg_calen_x) { + case PAIR_A: + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG0, + MTK_PHY_RG_ZCALEN_A); + break; + case PAIR_B: + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_B); + break; + case PAIR_C: + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_C); + break; + case PAIR_D: + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_ANA_CAL_RG1, + MTK_PHY_RG_ZCALEN_D); + break; + default: + return -EINVAL; + } + + phydev_dbg(phydev, "Start TX r50 SW cal.\n"); + first_calib = airoha_cal_cycle(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5, + MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl); + + if (first_calib < 0) { + phydev_err(phydev, "TX r50 SW calibration failed.\n"); + return -EINVAL; + } + + /* If TX r50 calibration failed: + * - increase dB until calibration succeed. + * If TX r50 calibration succeeded: + * - decrease dB until calibration fail to fine tune it. + */ + if (first_calib == 1) + calibration_polarity = -1; + else + calibration_polarity = 1; + + while (zcal_ctrl > 0 && + zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) { + zcal_ctrl += calibration_polarity; + + ret = airoha_cal_cycle(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5, + MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl); + /* Exit if we either failed or succeeded compared to the + * first calibration result. (aka we finished fine tuning or + * we succeeded with calibration) + */ + if (ret != first_calib) + break; + } + + if (ret < 0) { + phydev_err(phydev, "TX r50 SW calibration failed.\n"); + return -EINVAL; + } + + if (zcal_ctrl == 0 || + zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) + phydev_warn(phydev, "TX r50 SW calibration saturation.\n"); + + zcal_ctrl = shared->r50_cal_tbl[zcal_ctrl]; + + switch (txg_calen_x) { + case PAIR_A: + val = BIT(15) | FIELD_PREP(AIROHA_PHY_CR_TX_R50_OFFSET_A_MASK, zcal_ctrl); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_CR_TX_R50_OFFSET_A_B, + BIT(15) | AIROHA_PHY_CR_TX_R50_OFFSET_A_MASK, val); + break; + case PAIR_B: + val = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_B_MASK, zcal_ctrl); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_CR_TX_R50_OFFSET_A_B, + BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_B_MASK, val); + break; + case PAIR_C: + val = BIT(15) | FIELD_PREP(AIROHA_PHY_CR_TX_R50_OFFSET_C_MASK, zcal_ctrl); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_CR_TX_R50_OFFSET_C_D, + BIT(15) | AIROHA_PHY_CR_TX_R50_OFFSET_C_MASK, val); + break; + case PAIR_D: + val = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_D_MASK, zcal_ctrl); + phy_modify_mmd(phydev, MDIO_MMD_VEND1, + MTK_PHY_RG_CR_TX_R50_OFFSET_C_D, + BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_D_MASK, val); + break; + default: + return -EINVAL; + } + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0); + + /* Enable tx slew control */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0185, 0x0001); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00fb, 0x0000); + + phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x0); + phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x0); + + /* Restore BMCR */ + phy_write(phydev, MII_BMCR, bmcr_tmp); + + /* Restore MDI */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, dev1e_145_tmp); + + return 0; +} + +static int airoha_rx_offset_cal_sw(struct phy_device *phydev) +{ + /* Hw bypass, Fw cal */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, 0x0); + /* tx/rx_cal_criteria_value */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x37, 0x0033); + + /* rx offset cal by Hw setup */ + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39, + BIT(14) | BIT(11)); + + /* disable rtune calibration */ + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0x107, + BIT(12)); + + /* bypass tx/rx dc offset cancellation process */ + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171, + BIT(8) | BIT(7)); + + /* rx offset calibration start */ + phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39, + BIT(13)); + + /* rx offset calibration stop */ + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39, + BIT(13)); + + mdelay(10); + + phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171, + BIT(8) | BIT(7)); + + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x145, 0x1000); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, + 0x1); + + return 0; +} + +static int airoha_cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item, + u8 start_pair, u8 end_pair) +{ + u8 pair_n; + int ret; + + for (pair_n = start_pair; pair_n <= end_pair; pair_n++) { + switch (cal_item) { + case REXT: + ret = airoha_rext_cal_sw(phydev); + break; + case TX_OFFSET: + ret = airoha_tx_offset_cal_sw(phydev, pair_n); + break; + case TX_AMP: + ret = airoha_tx_amp_cal_sw(phydev, pair_n); + break; + case TX_R50: + ret = airoha_tx_r50_cal_sw(phydev, pair_n); + break; + case RX_OFFSET: + ret = airoha_rx_offset_cal_sw(phydev); + break; + default: + return -EINVAL; + } + if (ret) + return ret; + } + return 0; +} + +static int airoha_start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item, + enum CAL_MODE cal_mode, u8 start_pair, + u8 end_pair, u32 *buf) +{ + int ret; + + switch (cal_mode) { + case SW_M: + ret = airoha_cal_sw(phydev, cal_item, start_pair, end_pair); + break; + default: + return -EINVAL; + } + + if (ret) { + phydev_err(phydev, "cal %d failed\n", cal_item); + return -EIO; + } + + return 0; +} + +static int airoha_phy_calib(struct phy_device *phydev) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + struct phy_device *phydev_p0; + int ret; + + phydev_p0 = shared->phydev_p0; + + /* PreCalibrate Set */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x5c, 0x6666); + + phy_package_lock(phydev); + if (!shared->rext_sw_calib_done) { + ret = airoha_start_cal(phydev, REXT, SW_M, NO_PAIR, NO_PAIR, NULL); + if (ret) { + phy_package_unlock(phydev); + return ret; + } + + shared->rext_sw_calib_done = true; + } + phy_package_unlock(phydev); + + ret = airoha_start_cal(phydev, TX_R50, SW_M, PAIR_A, PAIR_D, NULL); + if (ret) + return ret; + + ret = airoha_start_cal(phydev, TX_OFFSET, SW_M, PAIR_A, PAIR_D, NULL); + if (ret) + return ret; + + ret = airoha_start_cal(phydev, TX_AMP, SW_M, PAIR_A, PAIR_D, NULL); + if (ret) + return ret; + + ret = airoha_start_cal(phydev, RX_OFFSET, SW_M, NO_PAIR, NO_PAIR, NULL); + if (ret) + return ret; + + /* Gating, short with other pair */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x15, 0x0); + + phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0); + + return 0; +} + +static int airoha_phy_auto_select_transformer(struct phy_device *phydev) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + struct phy_device *phydev_p0; + u8 phy_offset; + u16 bmcr_tmp; + int oldpage; + int ret = 0; + u16 val; + int i; + + phydev_p0 = shared->phydev_p0; + phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr; + + /* Force 1G full duplex for calibration */ + bmcr_tmp = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000); + + phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x271, GENMASK(4, 0), + FIELD_PREP(GENMASK(4, 0), 0x3)); + + phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x269, GENMASK(15, 12), + FIELD_PREP(GENMASK(15, 12), 0x2)); + + phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x26f, GENMASK(14, 12), + FIELD_PREP(GENMASK(14, 12), 0x0)); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12)); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, + 0x8000 | 0xf0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, + 0x8000 | 0xf0); + + /* Force pGA gain */ + oldpage = phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + __mtk_tr_modify(phydev, 0x1, 0xf, 0x10, + GENMASK(20, 15), + FIELD_PREP(GENMASK(20, 15), 0x1f)); + + __mtk_tr_modify(phydev, 0x1, 0xf, 0x11, + GENMASK(23, 18) | GENMASK(15, 10) | GENMASK(7, 2), + FIELD_PREP(GENMASK(23, 18), 0x1f) | + FIELD_PREP(GENMASK(15, 10), 0x1f) | + FIELD_PREP(GENMASK(7, 2), 0x1f)); + phy_restore_page(phydev, oldpage, 0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xc9, 0xffff); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x151, 0x12); + + switch (phy_offset) { + case 0: + val = 0x1; + break; + case 1: + val = 0x3; + break; + case 2: + val = 0x5; + break; + case 3: + val = 0x7; + break; + default: + ret = -EINVAL; + goto exit; + } + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x15, + FIELD_PREP(GENMASK(14, 12), val) | 0x161); + mdelay(500); + + for (i = 0; i < 3; i++) + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0x1a); + + /* Check the value and report transformer type for later */ + shared->transformer_type[phy_offset] = val < 0xe0; + +exit: + /* Switch to Default */ + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x15, 0x1000); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0x0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, + 0x0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, + 0x0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xc9, 0xfff); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x151, 0x0); + + oldpage = phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + __mtk_tr_modify(phydev, 0x1, 0xf, 0x10, + GENMASK(20, 15), + FIELD_PREP(GENMASK(20, 15), 0x4)); + + __mtk_tr_modify(phydev, 0x1, 0xf, 0x11, + GENMASK(23, 18) | GENMASK(15, 10) | GENMASK(7, 2), + FIELD_PREP(GENMASK(23, 18), 0x0) | + FIELD_PREP(GENMASK(15, 10), 0x0) | + FIELD_PREP(GENMASK(7, 2), 0x0)); + phy_restore_page(phydev, oldpage, 0); + + /* Restore BMCR */ + phy_write(phydev, MII_BMCR, bmcr_tmp); + + return ret; +} + +static int airoha_phy_tx_amp_compensation(struct phy_device *phydev) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + + u16 reg_1e_12_read; + u16 reg_1e_16_read; + u16 reg_1e_17_read; + u16 reg_1e_18_read; + u16 reg_1e_19_read; + u16 reg_1e_20_read; + u16 reg_1e_21_read; + u16 reg_1e_22_read; + u16 reg_1e_174_read; + u16 reg_1e_175_read; + + int reg_1e_12_header; + int reg_1e_16_header; + int reg_1e_17_header; + int reg_1e_18_header; + int reg_1e_19_header; + int reg_1e_21_header; + int reg_1e_174_header; + int reg_1e_175_header; + + int reg_1e_12_end; + int reg_1e_16_end; + int reg_1e_17_end; + int reg_1e_18_end; + int reg_1e_20_end; + int reg_1e_22_end; + int reg_1e_174_end; + int reg_1e_175_end; + + struct phy_device *phydev_p0; + u8 phy_offset; + + int txamp_low_limit = -3; + int txamp_high_limit = 0x42; + + int r50_low_limit = -8; + int r50_high_limit = 0x88; + + bool overflow; + int (*tx_amp_table)[CALIB_CONST_TYPE_MAX][4]; + int transformer_type, mdi_resister_type; + + phydev_p0 = shared->phydev_p0; + phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr; + + transformer_type = shared->transformer_type[phy_offset]; + mdi_resister_type = shared->mdi_resister_type; + tx_amp_table = &(*shared->tx_amp_compensation_tbl)[transformer_type][mdi_resister_type]; + + reg_1e_12_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x12); + reg_1e_16_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x16); + reg_1e_17_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x17); + reg_1e_18_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x18); + reg_1e_19_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x19); + reg_1e_20_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x20); + reg_1e_21_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x21); + reg_1e_22_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x22); + reg_1e_174_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x174); + reg_1e_175_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x175); + + reg_1e_12_header = ((reg_1e_12_read & 0xfc00) / 1024) + (*tx_amp_table)[TX_AMP_1G_A][phy_offset]; + reg_1e_17_header = ((reg_1e_17_read & 0x3f00) / 256) + (*tx_amp_table)[TX_AMP_1G_B][phy_offset]; + reg_1e_19_header = ((reg_1e_19_read & 0x3f00) / 256) + (*tx_amp_table)[TX_AMP_1G_C][phy_offset]; + reg_1e_21_header = ((reg_1e_21_read & 0x3f00) / 256) + (*tx_amp_table)[TX_AMP_1G_D][phy_offset]; + + overflow = false; + if (reg_1e_12_header < txamp_low_limit || + reg_1e_12_header > txamp_high_limit) + overflow = true; + if (reg_1e_17_header < txamp_low_limit || + reg_1e_17_header > txamp_high_limit) + overflow = true; + if (reg_1e_19_header < txamp_low_limit || + reg_1e_19_header > txamp_high_limit) + overflow = true; + if (reg_1e_21_header < txamp_low_limit || + reg_1e_21_header > txamp_high_limit) + overflow = true; + + if (overflow) { + reg_1e_12_header = 0x20; + reg_1e_17_header = 0x20; + reg_1e_19_header = 0x20; + reg_1e_21_header = 0x20; + } else { + if (reg_1e_12_header < 0) + reg_1e_12_header = 0; + else if (reg_1e_12_header > 0x3f) + reg_1e_12_header = 0x3f; + if (reg_1e_17_header < 0) + reg_1e_17_header = 0; + else if (reg_1e_17_header > 0x3f) + reg_1e_17_header = 0x3f; + if (reg_1e_19_header < 0) + reg_1e_19_header = 0; + else if (reg_1e_19_header > 0x3f) + reg_1e_19_header = 0x3f; + if (reg_1e_21_header < 0) + reg_1e_21_header = 0; + else if (reg_1e_21_header > 0x3f) + reg_1e_21_header = 0x3f; + } + + reg_1e_16_end = (reg_1e_16_read & 0x003f) + (*tx_amp_table)[TX_AMP_TEST_A][phy_offset]; + reg_1e_18_end = (reg_1e_18_read & 0x003f) + (*tx_amp_table)[TX_AMP_TEST_B][phy_offset]; + reg_1e_20_end = (reg_1e_20_read & 0x003f) + (*tx_amp_table)[TX_AMP_TEST_C][phy_offset]; + reg_1e_22_end = (reg_1e_22_read & 0x003f) + (*tx_amp_table)[TX_AMP_TEST_D][phy_offset]; + + overflow = false; + if (reg_1e_16_end < txamp_low_limit || + reg_1e_16_end > txamp_high_limit) + overflow = true; + if (reg_1e_18_end < txamp_low_limit || + reg_1e_18_end > txamp_high_limit) + overflow = true; + if (reg_1e_20_end < txamp_low_limit || + reg_1e_20_end > txamp_high_limit) + overflow = true; + if (reg_1e_22_end < txamp_low_limit || + reg_1e_22_end > txamp_high_limit) + overflow = true; + + if (overflow) { + reg_1e_16_end = 0x20; + reg_1e_18_end = 0x20; + reg_1e_20_end = 0x20; + reg_1e_22_end = 0x20; + } else { + if (reg_1e_16_end < 0) + reg_1e_16_end = 0; + else if (reg_1e_16_end > 0x3f) + reg_1e_16_end = 0x3f; + if (reg_1e_18_end < 0) + reg_1e_18_end = 0; + else if (reg_1e_18_end > 0x3f) + reg_1e_18_end = 0x3f; + if (reg_1e_20_end < 0) + reg_1e_20_end = 0; + else if (reg_1e_20_end > 0x3f) + reg_1e_20_end = 0x3f; + if (reg_1e_22_end < 0) + reg_1e_22_end = 0; + else if (reg_1e_22_end > 0x3f) + reg_1e_22_end = 0x3f; + } + + reg_1e_16_header = ((reg_1e_16_read & 0xfc00) / 1024) + (*tx_amp_table)[TX_AMP_100M_A][phy_offset]; + reg_1e_18_header = ((reg_1e_18_read & 0x3f00) / 256) + (*tx_amp_table)[TX_AMP_100M_B][phy_offset]; + + overflow = false; + if (reg_1e_16_header < txamp_low_limit || + reg_1e_16_header > txamp_high_limit) + overflow = true; + if (reg_1e_18_header < txamp_low_limit || + reg_1e_18_header > txamp_high_limit) + overflow = true; + + if (overflow) { + reg_1e_16_header = 0x20; + reg_1e_18_header = 0x20; + } else { + if (reg_1e_16_header < 0) + reg_1e_16_header = 0; + else if (reg_1e_16_header > 0x3f) + reg_1e_16_header = 0x3f; + if (reg_1e_18_header < 0) + reg_1e_18_header = 0; + else if (reg_1e_18_header > 0x3f) + reg_1e_18_header = 0x3f; + } + + reg_1e_12_end = (reg_1e_12_read & 0x003f) + (*tx_amp_table)[TX_AMP_10M_A][phy_offset]; + reg_1e_17_end = (reg_1e_17_read & 0x003f) + (*tx_amp_table)[TX_AMP_10M_B][phy_offset]; + + overflow = false; + if (reg_1e_12_end < txamp_low_limit || + reg_1e_12_end > txamp_high_limit) + overflow = true; + if (reg_1e_17_end < txamp_low_limit || + reg_1e_17_end > txamp_high_limit) + overflow = true; + + if (overflow) { + reg_1e_12_end = 0x20; + reg_1e_17_end = 0x20; + } else { + if (reg_1e_12_end < 0) + reg_1e_12_end = 0; + else if (reg_1e_12_end > 0x3f) + reg_1e_12_end = 0x3f; + if (reg_1e_17_end < 0) + reg_1e_17_end = 0; + else if (reg_1e_17_end > 0x3f) + reg_1e_17_end = 0x3f; + } + + reg_1e_174_header = ((reg_1e_174_read & 0x7f00) / 256) + (*tx_amp_table)[R50_A][phy_offset]; + reg_1e_174_end = (reg_1e_174_read & 0x007f) + (*tx_amp_table)[R50_B][phy_offset]; + reg_1e_175_header = ((reg_1e_175_read & 0x7f00) / 256) + (*tx_amp_table)[R50_C][phy_offset]; + reg_1e_175_end = (reg_1e_175_read & 0x007f) + (*tx_amp_table)[R50_D][phy_offset]; + + overflow = false; + if (reg_1e_174_header < r50_low_limit || + reg_1e_174_header > r50_high_limit) + overflow = true; + if (reg_1e_174_end < r50_low_limit || + reg_1e_174_end > r50_high_limit) + overflow = true; + if (reg_1e_175_header < r50_low_limit || + reg_1e_175_header > r50_high_limit) + overflow = true; + if (reg_1e_175_end < r50_low_limit || + reg_1e_175_end > r50_high_limit) + overflow = true; + + if (overflow) { + reg_1e_174_header = 0x34; + reg_1e_174_end = 0x34; + reg_1e_175_header = 0x34; + reg_1e_175_end = 0x34; + } else { + if (reg_1e_174_header < 0) + reg_1e_174_header = 0; + else if (reg_1e_174_header > 0x7f) + reg_1e_174_header = 0x7f; + if (reg_1e_174_end < 0) + reg_1e_174_end = 0; + else if (reg_1e_174_end > 0x7f) + reg_1e_174_end = 0x7f; + if (reg_1e_175_header < 0) + reg_1e_175_header = 0; + else if (reg_1e_175_header > 0x7f) + reg_1e_175_header = 0x7f; + if (reg_1e_175_end < 0) + reg_1e_175_end = 0; + else if (reg_1e_175_end > 0x7f) + reg_1e_175_end = 0x7f; + } + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x12, + (reg_1e_12_header * 1024) + reg_1e_12_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x16, + (reg_1e_16_header * 1024) + reg_1e_16_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x17, + (reg_1e_17_header * 256) + reg_1e_17_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x18, + (reg_1e_18_header * 256) + reg_1e_18_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x19, + (reg_1e_19_read & 0x00ff) + (reg_1e_19_header * 256)); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x20, + (reg_1e_20_read & 0xff00) + reg_1e_20_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x21, + (reg_1e_21_read & 0x00ff) + (reg_1e_21_header * 256)); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x22, + (reg_1e_22_read & 0xff00) + reg_1e_22_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x174, + 0x8080 + (reg_1e_174_header * 256) + reg_1e_174_end); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x175, + 0x8080 + (reg_1e_175_header * 256) + reg_1e_175_end); + + return 0; +} + +static int an7581_phy_package_config_init_once(struct phy_device *phydev) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + + /* FIXME: Read SoC PDIDR if available or default to 2 (latest) */ + shared->soc_pdidr = 2; + + shared->mdi_resister_type = MDI_5R; + if (shared->soc_pdidr == 2) + shared->tx_amp_compensation_tbl = &an7581_tx_amp_compensation_tbl[1]; + else + shared->tx_amp_compensation_tbl = &an7581_tx_amp_compensation_tbl[0]; + shared->r50_cal_tbl = an7581_zcal_to_r45ohm; + + return 0; +} + +static int an7581_phy_config_init(struct phy_device *phydev) +{ + struct airoha_socphy_shared *shared = phy_package_get_priv(phydev); + struct phy_device *phydev_p0; + u8 phy_offset; + int oldpage; + int ret; + + phydev_p0 = shared->phydev_p0; + phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr; + + if (phy_package_init_once(phydev)) { + ret = an7581_phy_package_config_init_once(phydev); + if (ret) + return ret; + } + + ret = airoha_phy_calib(phydev); + if (ret) + return ret; + + ret = airoha_phy_auto_select_transformer(phydev); + if (ret) + return ret; + + ret = airoha_phy_tx_amp_compensation(phydev); + if (ret) + return ret; + + /* RX setting for 5R_TXMR before AN setting */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe6, 0x1111); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe7, 0x5555); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe9, 0x0001); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xfe, 0x0000); + + /* Long Loop Reach setting */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x11, 0xff00); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x202, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x203, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x204, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x205, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x206, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x207, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x208, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x209, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20a, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20b, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20e, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20f, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x210, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x211, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x212, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x213, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x214, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x215, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x216, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x217, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21a, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21b, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21c, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21d, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21e, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21f, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x220, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x221, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x222, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x223, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x226, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x227, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x228, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x229, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22a, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22b, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22c, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22d, 0x0025); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22e, 0x2310); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22f, 0x0025); + + /* RX Setting */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3c, 0xc000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3d, 0x0000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000); + + /* EEE setting */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x3c, 0x0000); + + /* RX */ + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x41, 0x3333); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x44, 0x00c0); + + /* 10M settings */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a3, 0x00d2); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a4, 0x010e); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27b, 0x1177); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27c, 0x0808); + + oldpage = phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1); + __phy_write(phydev, 0x14, 0x3a18); + phy_restore_page(phydev, oldpage, 0); + + phy_write(phydev, 0x9, 0x1e00); + + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x2114); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26a, 0x1113); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26f, 0x0000); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x2c63); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x0c2b); + + /* EEE set because power down can't set tokenring */ + oldpage = phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5); + __mtk_tr_write(phydev, 0x0, 0x7, 0x15, 0x0055a0); + __mtk_tr_write(phydev, 0x0, 0x7, 0x17, 0x07ff3f); + __mtk_tr_write(phydev, 0x1, 0xf, 0x0, 0x00001e); + __mtk_tr_write(phydev, 0x1, 0xf, 0x1, 0x6fb90a); + __mtk_tr_write(phydev, 0x1, 0xf, 0x18, 0x0e2f00); + __mtk_tr_write(phydev, 0x1, 0xd, 0x26, 0x444444); + __mtk_tr_write(phydev, 0x2, 0xd, 0x3, 0x000004); + __mtk_tr_write(phydev, 0x2, 0xd, 0x6, 0x2ebaef); + __mtk_tr_write(phydev, 0x2, 0xd, 0x8, 0x00000b); + __mtk_tr_write(phydev, 0x2, 0xd, 0xc, 0x00504d); + __mtk_tr_write(phydev, 0x2, 0xd, 0xd, 0x02314f); + __mtk_tr_write(phydev, 0x2, 0xd, 0xf, 0x003028); + __mtk_tr_write(phydev, 0x2, 0xd, 0x10, 0x00000a); + __mtk_tr_write(phydev, 0x2, 0xd, 0x11, 0x040001); + __mtk_tr_write(phydev, 0x2, 0xd, 0x14, 0x00024a); + __mtk_tr_write(phydev, 0x2, 0xd, 0x1c, 0x003210); + phy_restore_page(phydev, oldpage, 0); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x120, 0x8014); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x122, 0xffff); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x144, 0x0200); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14a, 0xee20); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x19b, 0x0111); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x234, 0x1181); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x238, 0x0120); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x239, 0x0117); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x2d1, 0x0733); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x323, 0x0011); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x324, 0x013f); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x326, 0x0037); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x268, 0x07f4); + + if (shared->transformer_type[phy_offset] == TXMR) { + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0187); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cc); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0109); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x020b); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0202); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03c5); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c2); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0309); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x000e); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0002); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0880); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0880); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0880); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0880); + } else { + if (shared->soc_pdidr == 2) { + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x1114); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26a, 0x1113); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26f, 0x2000); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x4c88); + phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x0c2b); + + /* 100M sharp */ + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0190); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01c8); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0030); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0208); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0318); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03c8); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0230); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x0008); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0200); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0885); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0885); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0885); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0885); + } else { + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0190); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cf); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0030); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x020f); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0000); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0318); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03cf); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c0); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0230); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x000f); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0200); + + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0003); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0003); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0003); + phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0003); + } + } + + return 0; +} + static int an7581_phy_probe(struct phy_device *phydev) { + struct airoha_socphy_shared *shared; struct mtk_socphy_priv *priv; struct pinctrl *pinctrl; + int ret; /* Toggle pinctrl to enable PHY LED */ pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led"); @@ -18,10 +1521,29 @@ static int an7581_phy_probe(struct phy_device *phydev) dev_err(&phydev->mdio.bus->dev, "Failed to setup PHY LED pinctrl\n"); + ret = devm_phy_package_join(&phydev->mdio.dev, phydev, 0, + sizeof(struct airoha_socphy_shared)); + if (ret) + return ret; + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + shared = phy_package_get_priv(phydev); + if (phydev->mdio.addr == AIROHA_DEFAULT_PORT0_ADDR) + shared->phydev_p0 = phydev; + + /* + * Make sure PHY at port 0 is available for calibration. + * If port 0 is disabled in DT, probing will eventually fail + * after exhausting the deferred probe retries. + */ + if (!shared->phydev_p0) { + phydev_err(phydev, "PHY at port 0 is not available yet. (mandatory for calibration)\n"); + return -EPROBE_DEFER; + } + phydev->priv = priv; return 0; @@ -88,6 +1610,7 @@ static struct phy_driver mtk_socphy_driver[] = { { PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581), .name = "Airoha AN7581 PHY", + .config_init = an7581_phy_config_init, .config_intr = genphy_no_config_intr, .handle_interrupt = genphy_handle_interrupt_no_ack, .probe = an7581_phy_probe, @@ -97,6 +1620,8 @@ static struct phy_driver mtk_socphy_driver[] = { .led_hw_control_set = mt798x_phy_led_hw_control_set, .led_hw_control_get = mt798x_phy_led_hw_control_get, .led_polarity_set = an7581_phy_led_polarity_set, + .read_page = mtk_phy_read_page, + .write_page = mtk_phy_write_page, }, { PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583), @@ -124,5 +1649,6 @@ static const struct mdio_device_id __maybe_unused airoha_socphy_tbl[] = { MODULE_DESCRIPTION("Airoha SoC Gigabit Ethernet PHY driver"); MODULE_AUTHOR("Christian Marangi "); MODULE_LICENSE("GPL"); +MODULE_VERSION("01.01.20260306"); MODULE_DEVICE_TABLE(mdio, airoha_socphy_tbl); diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h index 419d87a341a0..d6baff245dea 100644 --- a/drivers/net/phy/mediatek/mtk-ge-soc.h +++ b/drivers/net/phy/mediatek/mtk-ge-soc.h @@ -13,6 +13,7 @@ #define MTK_EXT_PAGE_ACCESS 0x1f #define MTK_PHY_PAGE_STANDARD 0x0000 +#define MTK_PHY_PAGE_EXTENDED_1 0x0001 #define MTK_PHY_PAGE_EXTENDED_3 0x0003 #define MTK_PHY_LPI_REG_14 0x14 @@ -178,6 +179,7 @@ #define MTK_PHY_RG_ANA_CAL_RG0 0xdb #define MTK_PHY_RG_CAL_CKINV BIT(12) #define MTK_PHY_RG_ANA_CALEN BIT(8) +#define MTK_PHY_RG_REXT_CALEN BIT(4) #define MTK_PHY_RG_ZCALEN_A BIT(0) #define MTK_PHY_RG_ANA_CAL_RG1 0xdc @@ -188,6 +190,9 @@ #define MTK_PHY_RG_ANA_CAL_RG5 0xe0 #define MTK_PHY_RG_REXT_TRIM_MASK GENMASK(13, 8) +#define MTK_PHY_RG_REXT_ZCAL_CTRL_MASK GENMASK(5, 0) + +#define MTK_PHY_RG_ANA_CAL_RG6 0xe1 #define MTK_PHY_RG_TX_FILTER 0xfe @@ -204,10 +209,22 @@ #define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B 0x172 #define MTK_PHY_CR_TX_AMP_OFFSET_A_MASK GENMASK(13, 8) #define MTK_PHY_CR_TX_AMP_OFFSET_B_MASK GENMASK(6, 0) +#define AIROHA_PHY_CR_TX_AMP_OFFSET_B_MASK GENMASK(5, 0) #define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D 0x173 #define MTK_PHY_CR_TX_AMP_OFFSET_C_MASK GENMASK(13, 8) #define MTK_PHY_CR_TX_AMP_OFFSET_D_MASK GENMASK(6, 0) +#define AIROHA_PHY_CR_TX_AMP_OFFSET_D_MASK GENMASK(5, 0) + +#define MTK_PHY_RG_CR_TX_R50_OFFSET_A_B 0x174 +#define MTK_PHY_CR_TX_R50_OFFSET_A_MASK GENMASK(13, 8) +#define AIROHA_PHY_CR_TX_R50_OFFSET_A_MASK GENMASK(14, 8) +#define MTK_PHY_CR_TX_R50_OFFSET_B_MASK GENMASK(6, 0) + +#define MTK_PHY_RG_CR_TX_R50_OFFSET_C_D 0x175 +#define MTK_PHY_CR_TX_R50_OFFSET_C_MASK GENMASK(13, 8) +#define AIROHA_PHY_CR_TX_R50_OFFSET_C_MASK GENMASK(14, 8) +#define MTK_PHY_CR_TX_R50_OFFSET_D_MASK GENMASK(6, 0) #define MTK_PHY_RG_AD_CAL_COMP 0x17a #define MTK_PHY_AD_CAL_COMP_OUT_MASK GENMASK(8, 8) -- 2.55.0