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


_______________________________________________
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel

Reply via email to