From d6f61665d97cf1077ccbfff19c0df509b7c26aae Mon Sep 17 00:00:00 2001
From: Luo Jie <quic_luoj@quicinc.com>
Date: Wed, 29 Nov 2023 15:21:22 +0800
Subject: [PATCH] net: phy: qca808x: Add register access support routines for
 QCA8084

QCA8084 integrates clock controller and security control modules
besides of the PHY and PCS. The 32bit registers in these modules
are accessed using special MDIO sequences to read or write these
registers.

The MDIO address of PHY and PCS are configured by writing to the
security control register. The package mode for QCA8084 is also
configured in a similar manner.

Change-Id: I9317307ef9bbc738a6adcbc3ea1be8e6528d711e
Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
---
 drivers/net/phy/qcom/qca808x.c | 88 ++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

--- a/drivers/net/phy/qcom/qca808x.c
+++ b/drivers/net/phy/qcom/qca808x.c
@@ -111,6 +111,22 @@
 #define QCA8084_MMD7_IPG_OP			0x901d
 #define QCA8084_IPG_10_TO_11_EN			BIT(0)
 
+/* QCA8084 includes secure control module, which supports customizing the
+ * MDIO address of PHY device and PCS device and configuring package mode
+ * for the interface mode of PCS. The register of secure control is accessed
+ * by MDIO bus with the special MDIO sequences, where the 32 bits register
+ * address is split into 3 MDIO operations with 16 bits address.
+ */
+#define QCA8084_HIGH_ADDR_PREFIX		0x18
+#define QCA8084_LOW_ADDR_PREFIX			0x10
+
+/* Bottom two bits of REG must be zero */
+#define QCA8084_MII_REG_MASK			GENMASK(4, 0)
+#define QCA8084_MII_PHY_ADDR_MASK		GENMASK(7, 5)
+#define QCA8084_MII_PAGE_MASK			GENMASK(23, 8)
+#define QCA8084_MII_SW_ADDR_MASK		GENMASK(31, 24)
+#define QCA8084_MII_REG_DATA_UPPER_16_BITS	BIT(1)
+
 MODULE_DESCRIPTION("Qualcomm Atheros QCA808X PHY driver");
 MODULE_AUTHOR("Matus Ujhelyi, Luo Jie");
 MODULE_LICENSE("GPL");
@@ -120,6 +136,78 @@ struct qca808x_priv {
 	struct qcom_phy_hw_stats hw_stats;
 };
 
+static int __qca8084_set_page(struct mii_bus *bus, u16 sw_addr, u16 page)
+{
+	return __mdiobus_write(bus, QCA8084_HIGH_ADDR_PREFIX | (sw_addr >> 5),
+			       sw_addr & 0x1f, page);
+}
+
+static int __qca8084_mii_read(struct mii_bus *bus, u16 addr, u16 reg, u32 *val)
+{
+	int ret, data;
+
+	ret = __mdiobus_read(bus, addr, reg);
+	if (ret < 0)
+		return ret;
+
+	data = ret;
+	ret = __mdiobus_read(bus, addr,
+			     reg | QCA8084_MII_REG_DATA_UPPER_16_BITS);
+	if (ret < 0)
+		return ret;
+
+	*val =  data | ret << 16;
+
+	return 0;
+}
+
+static int __qca8084_mii_write(struct mii_bus *bus, u16 addr, u16 reg, u32 val)
+{
+	int ret;
+
+	ret = __mdiobus_write(bus, addr, reg, lower_16_bits(val));
+	if (!ret)
+		ret = __mdiobus_write(bus, addr,
+				      reg | QCA8084_MII_REG_DATA_UPPER_16_BITS,
+				      upper_16_bits(val));
+
+	return ret;
+}
+
+static int qca8084_mii_modify(struct phy_device *phydev, u32 regaddr,
+			      u32 clear, u32 set)
+{
+	u16 reg, addr, page, sw_addr;
+	struct mii_bus *bus;
+	u32 val;
+	int ret;
+
+	bus = phydev->mdio.bus;
+	mutex_lock(&bus->mdio_lock);
+
+	reg = FIELD_GET(QCA8084_MII_REG_MASK, regaddr);
+	addr = FIELD_GET(QCA8084_MII_PHY_ADDR_MASK, regaddr);
+	page = FIELD_GET(QCA8084_MII_PAGE_MASK, regaddr);
+	sw_addr = FIELD_GET(QCA8084_MII_SW_ADDR_MASK, regaddr);
+
+	ret = __qca8084_set_page(bus, sw_addr, page);
+	if (ret < 0)
+		goto qca8084_mii_modify_exit;
+
+	ret = __qca8084_mii_read(bus, QCA8084_LOW_ADDR_PREFIX | addr,
+				 reg, &val);
+	if (ret < 0)
+		goto qca8084_mii_modify_exit;
+
+	val &= ~clear;
+	val |= set;
+	ret = __qca8084_mii_write(bus, QCA8084_LOW_ADDR_PREFIX | addr,
+				  reg, val);
+qca8084_mii_modify_exit:
+	mutex_unlock(&bus->mdio_lock);
+	return ret;
+};
+
 static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
 {
 	int ret;
