[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