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