diff mbox series

[2/2] net: phy: add Qualcomm QCA807x driver

Message ID 20240522100638.224484-2-robert.marko@sartura.hr
State New
Delegated to: Caleb Connolly
Headers show
Series [1/2] net: phy: add PSGMII (Penta SGMII) mode | expand

Commit Message

Robert Marko May 22, 2024, 10:06 a.m. UTC
This adds driver for the Qualcomm QCA8072 and QCA8075 PHY-s.

They are 2 or 5 port IEEE 802.3 clause 22 compliant
10BASE-Te, 100BASE-TX and 1000BASE-T PHY-s.

They feature 2 SerDes, one for PSGMII or QSGMII connection with MAC,
while second one is SGMII for connection to MAC or fiber.

Both models have a combo port that supports 1000BASE-X and 100BASE-FX
fiber (Currently not supported in U-Boot)

Each PHY inside of QCA807x series has 2 digitally controlled output only
pins that natively drive LED-s.

These PHY-s are commonly used in Qualcomm IPQ40xx, IPQ60xx and IPQ807x
boards.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
---
 drivers/net/phy/Kconfig   |   6 ++
 drivers/net/phy/Makefile  |   1 +
 drivers/net/phy/qca807x.c | 211 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 218 insertions(+)
 create mode 100644 drivers/net/phy/qca807x.c
diff mbox series

Patch

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 3d96938eab..cc79043b97 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -243,6 +243,12 @@  config PHY_NXP_TJA11XX
 	help
 	  Currently supports the NXP TJA1100 and TJA1101 PHY.
 
+config PHY_QCA807X
+	bool "Qualcomm QCA807x PHYs support"
+	help
+	  Currently supports the Qualcomm QCA8072, QCA8075 and the PSGMII
+	  control PHY.
+
 config PHY_REALTEK
 	bool "Realtek Ethernet PHYs support"
 
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 2487f366e1..b2d31fe4f2 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -28,6 +28,7 @@  obj-$(CONFIG_PHY_MOTORCOMM) += motorcomm.o
 obj-$(CONFIG_PHY_NATSEMI) += natsemi.o
 obj-$(CONFIG_PHY_NXP_C45_TJA11XX) += nxp-c45-tja11xx.o
 obj-$(CONFIG_PHY_NXP_TJA11XX) += nxp-tja11xx.o
+obj-$(CONFIG_PHY_QCA807X) += qca807x.o
 obj-$(CONFIG_PHY_REALTEK) += realtek.o
 obj-$(CONFIG_PHY_SMSC) += smsc.o
 obj-$(CONFIG_PHY_TERANETICS) += teranetics.o
diff --git a/drivers/net/phy/qca807x.c b/drivers/net/phy/qca807x.c
new file mode 100644
index 0000000000..a851ff34a0
--- /dev/null
+++ b/drivers/net/phy/qca807x.c
@@ -0,0 +1,211 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 Sartura Ltd.
+ *
+ * Author: Robert Marko <robert.marko@sartura.hr>
+ *
+ * Qualcomm QCA8072 and QCA8075 PHY driver
+ */
+
+#include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <phy.h>
+
+#define PHY_ID_QCA8072			0x004dd0b2
+#define PHY_ID_QCA8075			0x004dd0b1
+#define PHY_ID_QCA807X_PSGMII		0x06820805
+#define PHY_ID_QCA807X_MASK		GENMASK(31, 0)
+
+#define QCA807X_CHIP_CONFIGURATION 				0x1f
+#define QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH	0x801a
+#define QCA807X_CONTROL_DAC_MASK				GENMASK(2, 0)
+/* List of tweaks enabled by this bit:
+ * - With both FULL amplitude and FULL bias current: bias current
+ *   is set to half.
+ * - With only DSP amplitude: bias current is set to half and
+ *   is set to 1/4 with cable < 10m.
+ * - With DSP bias current (included both DSP amplitude and
+ *   DSP bias current): bias current is half the detected current
+ *   with cable < 10m.
+ */
+#define QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK			BIT(2)
+#define QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT			BIT(1)
+#define QCA807X_CONTROL_DAC_DSP_AMPLITUDE			BIT(0)
+
+#define QCA807X_MMD7_LED_100N_1 	0x8074
+#define QCA807X_MMD7_LED_100N_2 	0x8075
+#define QCA807X_MMD7_LED_1000N_1 	0x8076
+#define QCA807X_MMD7_LED_1000N_2 	0x8077
+#define QCA807X_LED_TXACT_BLK_EN_2 	BIT(10)
+#define QCA807X_LED_RXACT_BLK_EN_2 	BIT(9)
+#define QCA807X_LED_GT_ON_EN_2 		BIT(6)
+#define QCA807X_LED_HT_ON_EN_2 		BIT(5)
+#define QCA807X_LED_BT_ON_EN_2 		BIT(4)
+
+/* PSGMII PHY specific */
+#define PSGMII_QSGMII_DRIVE_CONTROL_1 		0xb
+#define PSGMII_QSGMII_TX_DRIVER_MASK		GENMASK(7, 4)
+#define PQSGMII_TX_DRIVER_140MV				0x0
+#define PQSGMII_TX_DRIVER_160MV				0x1
+#define PQSGMII_TX_DRIVER_180MV				0x2
+#define PQSGMII_TX_DRIVER_200MV				0x3
+#define PQSGMII_TX_DRIVER_220MV				0x4
+#define PQSGMII_TX_DRIVER_240MV				0x5
+#define PQSGMII_TX_DRIVER_260MV				0x6
+#define PQSGMII_TX_DRIVER_280MV				0x7
+#define PQSGMII_TX_DRIVER_300MV				0x8
+#define PQSGMII_TX_DRIVER_320MV				0x9
+#define PQSGMII_TX_DRIVER_400MV				0xa
+#define PQSGMII_TX_DRIVER_500MV				0xb
+#define PQSGMII_TX_DRIVER_600MV				0xc
+#define PSGMII_MODE_CTRL 			0x6d
+#define PSGMII_MODE_CTRL_AZ_WORKAROUND_MASK 	GENMASK(3, 0)
+#define PSGMII_MMD3_SERDES_CONTROL		0x805a
+
+static int qca807x_config(struct phy_device *phydev)
+{
+	int control_dac, ret = 0;
+	bool dac_full_amplitude;
+	bool dac_full_bias_current;
+	bool dac_disable_bias_current_tweak;
+	ofnode node;
+
+	node = phy_get_ofnode(phydev);
+	if (!ofnode_valid(node))
+		return -EINVAL;
+
+	dac_full_amplitude = ofnode_read_bool(node, "qcom,dac-full-amplitude");
+	dac_full_bias_current = ofnode_read_bool(node, "qcom,dac-full-bias-current");
+	dac_disable_bias_current_tweak = ofnode_read_bool(node, "qcom,dac-disable-bias-current-tweak");
+
+	/* Check for Combo port */
+	if(phy_read(phydev, MDIO_DEVAD_NONE, QCA807X_CHIP_CONFIGURATION)) {
+		int psgmii_serdes;
+
+		/* Prevent PSGMII going into hibernation via PSGMII self test */
+		psgmii_serdes = phy_read_mmd(phydev, MDIO_MMD_PCS, PSGMII_MMD3_SERDES_CONTROL);
+		psgmii_serdes &= ~BIT(1);
+		ret = phy_write_mmd(phydev, MDIO_MMD_PCS, PSGMII_MMD3_SERDES_CONTROL, psgmii_serdes);
+	}
+
+	control_dac = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH);
+	control_dac &= ~QCA807X_CONTROL_DAC_MASK;
+	if (!dac_full_amplitude)
+		control_dac |= QCA807X_CONTROL_DAC_DSP_AMPLITUDE;
+	if (!dac_full_amplitude)
+		control_dac |= QCA807X_CONTROL_DAC_DSP_BIAS_CURRENT;
+	if (!dac_disable_bias_current_tweak)
+		control_dac |= QCA807X_CONTROL_DAC_BIAS_CURRENT_TWEAK;
+	ret = phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH, control_dac);
+
+	phydev->supported = phydev->drv->features;
+	phydev->advertising = phydev->drv->features;
+
+	genphy_config_aneg(phydev);
+	genphy_restart_aneg(phydev);
+
+	return ret;
+}
+
+static int qca807x_psgmii_config(struct phy_device *phydev)
+{
+	int psgmii_az, tx_amp, ret = 0;
+	u32 tx_drive_strength;
+	ofnode node;
+
+	node = phy_get_ofnode(phydev);
+	if (!ofnode_valid(node))
+		return -EINVAL;
+
+	/* Default to 600mw if not defined */
+	if (ofnode_read_u32(node, "qcom,tx-drive-strength-milliwatt",
+	                    &tx_drive_strength))
+		tx_drive_strength = 600;
+
+	switch (tx_drive_strength) {
+	case 140:
+		tx_drive_strength = PQSGMII_TX_DRIVER_140MV;
+		break;
+	case 160:
+		tx_drive_strength = PQSGMII_TX_DRIVER_160MV;
+		break;
+	case 180:
+		tx_drive_strength = PQSGMII_TX_DRIVER_180MV;
+		break;
+	case 200:
+		tx_drive_strength = PQSGMII_TX_DRIVER_200MV;
+		break;
+	case 220:
+		tx_drive_strength = PQSGMII_TX_DRIVER_220MV;
+		break;
+	case 240:
+		tx_drive_strength = PQSGMII_TX_DRIVER_240MV;
+		break;
+	case 260:
+		tx_drive_strength = PQSGMII_TX_DRIVER_260MV;
+		break;
+	case 280:
+		tx_drive_strength = PQSGMII_TX_DRIVER_280MV;
+		break;
+	case 300:
+		tx_drive_strength = PQSGMII_TX_DRIVER_300MV;
+		break;
+	case 320:
+		tx_drive_strength = PQSGMII_TX_DRIVER_320MV;
+		break;
+	case 400:
+		tx_drive_strength = PQSGMII_TX_DRIVER_400MV;
+		break;
+	case 500:
+		tx_drive_strength = PQSGMII_TX_DRIVER_500MV;
+		break;
+	case 600:
+		tx_drive_strength = PQSGMII_TX_DRIVER_600MV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Workaround to enable AZ transmitting ability */
+	psgmii_az = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PSGMII_MODE_CTRL);
+	psgmii_az &= ~PSGMII_MODE_CTRL_AZ_WORKAROUND_MASK;
+	psgmii_az |= FIELD_PREP(PSGMII_MODE_CTRL_AZ_WORKAROUND_MASK, 0xc);
+	ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, PSGMII_MODE_CTRL, psgmii_az);
+	psgmii_az = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PSGMII_MODE_CTRL);
+
+
+	tx_amp = phy_read(phydev, MDIO_DEVAD_NONE, PSGMII_QSGMII_DRIVE_CONTROL_1);
+	tx_amp &= ~PSGMII_QSGMII_TX_DRIVER_MASK;
+	tx_amp |= FIELD_PREP(PSGMII_QSGMII_TX_DRIVER_MASK, tx_drive_strength);
+	ret = phy_write(phydev, MDIO_DEVAD_NONE, PSGMII_QSGMII_DRIVE_CONTROL_1, tx_amp);
+
+	return ret;
+}
+
+U_BOOT_PHY_DRIVER(QCA8072_driver) =  {
+	.name = "Qualcomm QCA8072",
+	.uid = PHY_ID_QCA8072,
+	.mask = PHY_ID_QCA807X_MASK,
+	.features = PHY_GBIT_FEATURES,
+	.config = qca807x_config,
+	.startup = genphy_startup,
+	.shutdown = genphy_shutdown,
+};
+
+U_BOOT_PHY_DRIVER(QCA8075_driver) =  {
+	.name = "Qualcomm QCA8075",
+	.uid = PHY_ID_QCA8075,
+	.mask = PHY_ID_QCA807X_MASK,
+	.features = PHY_GBIT_FEATURES,
+	.config = qca807x_config,
+	.startup = genphy_startup,
+	.shutdown = genphy_shutdown,
+};
+
+U_BOOT_PHY_DRIVER(QCA807X_PSGMII_driver) =  {
+	.name = "Qualcomm QCA807x PSGMII",
+	.uid = PHY_ID_QCA807X_PSGMII,
+	.mask = PHY_ID_QCA807X_MASK,
+	.config = qca807x_psgmii_config,
+};