[PATCH RFC] aquantia-firmware: package MediaTek's Aquantia AQR113C firmware

Qingfang DENG dqfext at gmail.com
Sun Feb 25 22:59:30 PST 2024


It's actually possible to access the flash from Linux, as I tested a
year ago.

---
 drivers/net/phy/aquantia_main.c | 462 +++++++++++++++++++++++++++++++-
 1 file changed, 461 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c
index 6ecbc3f33..6b3079b3a 100644
--- a/drivers/net/phy/aquantia_main.c
+++ b/drivers/net/phy/aquantia_main.c
@@ -12,6 +12,7 @@
 #include <linux/delay.h>
 #include <linux/bitfield.h>
 #include <linux/phy.h>
+#include <linux/mtd/spi-nor.h>
 
 #include "aquantia.h"
 
@@ -155,6 +156,39 @@
 #define MDIO_PHYXS_VEND_PROV2			0xc441
 #define MDIO_PHYXS_VEND_PROV2_USX_AN		BIT(3)
 
+// SPI NOR controller regs
+#define VEND1_GLOBAL_NVR_OP			0x100
+#define VEND1_GLOBAL_NVR_OP_START		BIT(15)
+#define VEND1_GLOBAL_NVR_OP_WR			BIT(14)
+#define VEND1_GLOBAL_NVR_OP_RESET_CRC		BIT(12)
+#define VEND1_GLOBAL_NVR_OP_BURST		BIT(10)
+#define VEND1_GLOBAL_NVR_OP_BUSY		BIT(8)
+#define VEND1_GLOBAL_NVR_OP_CODE		GENMASK(7, 0)
+
+#define VEND1_GLOBAL_NVR_MAILBOX_CRC		0x101
+#define VEND1_GLOBAL_NVR_ADDR_MSW		0x102
+#define VEND1_GLOBAL_NVR_ADDR_LSW		0x103
+#define VEND1_GLOBAL_NVR_DATA_MSW		0x104
+#define VEND1_GLOBAL_NVR_DATA_LSW		0x105
+#define VEND1_GLOBAL_NVR_PROV1			0xc450
+#define VEND1_GLOBAL_NVR_PROV1_DATA_LEN		GENMASK(10, 8)
+#define VEND1_GLOBAL_NVR_PROV1_DUMMY_LEN	GENMASK(6, 4)
+#define VEND1_GLOBAL_NVR_PROV1_ADDR_LEN		GENMASK(1, 0)
+
+#define VEND1_GLOBAL_NVR_PROV2			0xc451
+#define VEND1_GLOBAL_NVR_PROV2_ADDR_LEN_OVR	BIT(8)
+#define VEND1_GLOBAL_NVR_PROV2_CLK_DIV		GENMASK(7, 0)
+
+#define VEND1_GLOBAL_NVR_PROV3			0xc452
+#define VEND1_GLOBAL_NVR_PROV3_CLK_DIV_OVR	BIT(1)
+#define VEND1_GLOBAL_NVR_PROV3_DAISY_DIS	BIT(0)
+
+#define VEND1_GLOBAL_NVR_PROV4			0xc453
+#define VEND1_GLOBAL_NVR_PROV4_RESET_SPI	BIT(4)
+
+#define VEND1_GLOBAL_UP_CONTROL			0xc001
+#define VEND1_GLOBAL_UP_CONTROL_STALL		BIT(0)
+
 struct aqr107_hw_stat {
 	const char *name;
 	int reg;
@@ -180,6 +214,11 @@ struct aqr107_priv {
 	u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
 };
 
+struct aqr113c_priv {
+	struct aqr107_priv aqr107_priv; // Must be first
+	struct spi_nor nor;
+};
+
 static int aqr107_get_sset_count(struct phy_device *phydev)
 {
 	return AQR107_SGMII_STAT_SZ;
@@ -718,6 +757,426 @@ static int aqr107_probe(struct phy_device *phydev)
 	return aqr_hwmon_probe(phydev);
 }
 
+static int aqr113c_nor_poll(struct phy_device *phydev)
+{
+	int val;
+
+	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+					 VEND1_GLOBAL_NVR_OP, val,
+					 !(val & VEND1_GLOBAL_NVR_OP_BUSY), 0,
+					 100000, 0);
+}
+
+/**
+ * aqr113c_nor_read_chunk - read a 4-byte chunk from the flash
+ *
+ * Caller must configure data len in 0x1e.0xc450 to 4 prior to this function
+ */
+static int aqr113c_nor_read_chunk(struct phy_device *phydev, u8 opcode,
+				  u8 *buf)
+{
+	int ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_OP,
+			    VEND1_GLOBAL_NVR_OP_START |
+			    VEND1_GLOBAL_NVR_OP_BURST | opcode);
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_poll(phydev);
+	if (ret)
+		return ret;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_LSW);
+	if (ret < 0)
+		return ret;
+
+	buf[0] = ret;
+	buf[1] = (u16)ret >> 8;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_MSW);
+	if (ret < 0)
+		return ret;
+
+	buf[2] = ret;
+	buf[3] = (u16)ret >> 8;
+
+	return 0;
+}
+
+/**
+ * aqr113c_nor_write_chunk - write a 4-byte chunk
+ *
+ * Caller must configure data len in 0x1e.0xc450 to 4 prior to this function
+ */
+static int aqr113c_nor_write_chunk(struct phy_device *phydev, u8 opcode,
+				   const u8 *buf)
+{
+	int ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_LSW,
+			    get_unaligned((const u16 *)buf));
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_MSW,
+			    get_unaligned((const u16 *)buf + 1));
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_OP,
+			    VEND1_GLOBAL_NVR_OP_START |
+			    VEND1_GLOBAL_NVR_OP_BURST |
+			    VEND1_GLOBAL_NVR_OP_WR | opcode);
+	if (ret)
+		return ret;
+
+	return aqr113c_nor_poll(phydev);
+}
+
+static int aqr113c_nor_read_finish(struct phy_device *phydev, u8 opcode,
+				   u8 *buf, size_t len)
+{
+	int ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_OP,
+			    VEND1_GLOBAL_NVR_OP_START | opcode);
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_poll(phydev);
+	if (ret)
+		return ret;
+
+	if (!len)
+		return 0;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_LSW);
+	if (ret < 0)
+		return ret;
+
+	buf[0] = ret;
+	if (len > 1)
+		buf[1] = (u16)ret >> 8;
+	if (len <= 2)
+		return 0;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_MSW);
+	if (ret < 0)
+		return ret;
+
+	buf[2] = ret;
+	if (len > 3)
+		buf[3] = (u16)ret >> 8;
+
+	return 0;
+}
+
+static int aqr113c_nor_write_finish(struct phy_device *phydev, u8 opcode,
+				    const u8 *buf, size_t len)
+{
+	int ret;
+	u16 val;
+
+	if (!len)
+		goto write_op;
+
+	val = buf[0];
+
+	if (len > 1)
+		val |= buf[1] << 8;
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_LSW,
+			    val);
+	if (ret)
+		return ret;
+	if (len <= 2)
+		goto write_op;
+
+	val = buf[2];
+	if (len > 3)
+		val |= buf[3] << 8;
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_DATA_MSW, val);
+	if (ret)
+		return ret;
+
+write_op:
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_OP,
+			    VEND1_GLOBAL_NVR_OP_START |
+			    VEND1_GLOBAL_NVR_OP_WR | opcode);
+	if (ret)
+		return ret;
+
+	return aqr113c_nor_poll(phydev);
+}
+
+static int aqr113c_nor_acquire(struct phy_device *phydev)
+{
+	int ret, val;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV3,
+			    VEND1_GLOBAL_NVR_PROV3_DAISY_DIS |
+			    VEND1_GLOBAL_NVR_PROV3_CLK_DIV_OVR);
+	if (ret)
+		return ret;
+
+	ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_UP_CONTROL,
+			       VEND1_GLOBAL_UP_CONTROL_STALL);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV4,
+			    VEND1_GLOBAL_NVR_PROV4_RESET_SPI);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV4, 0);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV2,
+			    VEND1_GLOBAL_NVR_PROV2_ADDR_LEN_OVR | 0x50);
+	return ret;
+}
+
+static int aqr113c_nor_release(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV2, 0xa0);
+	if (ret)
+		return ret;
+
+	ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_UP_CONTROL,
+				 VEND1_GLOBAL_UP_CONTROL_STALL);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV3, 0);
+	return ret;
+}
+
+static int aqr113c_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
+				size_t len)
+{
+	struct phy_device *phydev = nor->priv;
+	int ret;
+
+	ret = aqr113c_nor_acquire(phydev);
+	if (ret)
+		return ret;
+
+	if (len > 4) {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, 4));
+		if (ret)
+			return ret;
+
+		for (; len > 4; len -= 4, buf += 4) {
+			ret = aqr113c_nor_read_chunk(phydev, opcode, buf);
+			if (ret)
+				return ret;
+		}
+	}
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, len));
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_read_finish(phydev, opcode, buf, len);
+	if (ret)
+		return ret;
+
+	return aqr113c_nor_release(phydev);
+}
+
+static int aqr113c_nor_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf,
+				 size_t len)
+{
+	struct phy_device *phydev = nor->priv;
+	int ret;
+
+	ret = aqr113c_nor_acquire(phydev);
+	if (ret)
+		return ret;
+
+	if (len > 4) {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, 4));
+		if (ret)
+			return ret;
+
+		for (; len > 4; len -= 4, buf += 4) {
+			ret = aqr113c_nor_write_chunk(phydev, opcode, buf);
+			if (ret)
+				return ret;
+		}
+	}
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, len));
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_write_finish(phydev, opcode, buf, len);
+	if (ret)
+		return ret;
+
+	return aqr113c_nor_release(phydev);
+}
+
+static ssize_t aqr113c_nor_read(struct spi_nor *nor, loff_t from, size_t len,
+				u8 *buf)
+{
+	struct phy_device *phydev = nor->priv;
+	size_t _len = len;
+	int ret;
+
+	ret = aqr113c_nor_acquire(phydev);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_ADDR_LSW,
+			    from);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_ADDR_MSW,
+			    (u32)from >> 16);
+	if (ret)
+		return ret;
+
+	if (len > 4) {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, 4) |
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_ADDR_LEN, nor->addr_width) |
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DUMMY_LEN, nor->read_dummy));
+		if (ret)
+			return ret;
+
+		for (; len > 4; len -= 4, buf += 4) {
+			ret = aqr113c_nor_read_chunk(phydev, nor->read_opcode, buf);
+			if (ret)
+				return ret;
+		}
+	}
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, len) |
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_ADDR_LEN, nor->addr_width) |
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DUMMY_LEN, nor->read_dummy));
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_read_finish(phydev, nor->read_opcode, buf, len);
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_release(phydev);
+	if (ret)
+		return ret;
+
+	return _len;
+}
+
+static ssize_t aqr113c_nor_write(struct spi_nor *nor, loff_t to, size_t len,
+				 const u8 *buf)
+{
+	struct phy_device *phydev = nor->priv;
+	size_t _len = len;
+	int ret;
+
+	ret = aqr113c_nor_acquire(phydev);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_ADDR_LSW,
+			    to);
+	if (ret)
+		return ret;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_ADDR_MSW,
+			    (u32)to >> 16);
+	if (ret)
+		return ret;
+
+	if (len > 4) {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, 4) |
+				    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_ADDR_LEN, nor->addr_width));
+		if (ret)
+			return ret;
+
+		for (; len > 4; len -= 4, buf += 4) {
+			ret = aqr113c_nor_write_chunk(phydev, nor->program_opcode, buf);
+			if (ret)
+				return ret;
+		}
+	}
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_NVR_PROV1,
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_DATA_LEN, len) |
+			    FIELD_PREP(VEND1_GLOBAL_NVR_PROV1_ADDR_LEN, nor->addr_width));
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_write_finish(phydev, nor->program_opcode, buf, len);
+	if (ret)
+		return ret;
+
+	ret = aqr113c_nor_release(phydev);
+	if (ret)
+		return ret;
+
+	return _len;
+}
+
+static const struct spi_nor_controller_ops aqr113c_nor_ops = {
+	.read_reg = aqr113c_nor_read_reg,
+	.write_reg = aqr113c_nor_write_reg,
+	.read = aqr113c_nor_read,
+	.write = aqr113c_nor_write,
+};
+
+static int aqr113c_probe(struct phy_device *phydev)
+{
+	static const struct spi_nor_hwcaps hwcaps = {
+		.mask = SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST |
+			SNOR_HWCAPS_PP,
+	};
+	struct aqr113c_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct aqr113c_priv),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	ret = aqr_hwmon_probe(phydev);
+	if (ret)
+		return ret;
+	priv->nor.dev = &phydev->mdio.dev;
+	priv->nor.priv = phydev;
+	priv->nor.controller_ops = &aqr113c_nor_ops;
+	priv->nor.mtd.name = "aqr113c_fw";
+	spi_nor_set_flash_node(&priv->nor, phydev->mdio.dev.of_node);
+
+	ret = spi_nor_scan(&priv->nor, NULL, &hwcaps);
+	if (ret)
+		return ret;
+
+	return mtd_device_register(&priv->nor.mtd, NULL, 0);
+}
+
+static void aqr113c_remove(struct phy_device *phydev)
+{
+	struct aqr113c_priv *priv = phydev->priv;
+
+	mtd_device_unregister(&priv->nor.mtd);
+}
+
 static struct phy_driver aqr_driver[] = {
 {
 	PHY_ID_MATCH_MODEL(PHY_ID_AQ1202),
@@ -774,7 +1233,8 @@ static struct phy_driver aqr_driver[] = {
 {
 	PHY_ID_MATCH_MODEL(PHY_ID_AQR113C),
 	.name		= "Aquantia AQR113C",
-	.probe		= aqr107_probe,
+	.probe		= aqr113c_probe,
+	.remove		= aqr113c_remove,
 	.config_init	= aqr107_config_init,
 	.config_aneg    = aqr113c_config_aneg,
 	.config_intr	= aqr_config_intr,
-- 
2.34.1




More information about the openwrt-devel mailing list