[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <31f9d27ed19e074db87307c319c4d07309acecae.1587363707.git.baruch@tkos.co.il>
Date: Mon, 20 Apr 2020 09:21:47 +0300
From: Baruch Siach <baruch@...s.co.il>
To: Russell King <linux@...linux.org.uk>
Cc: netdev@...r.kernel.org, Andrew Lunn <andrew@...n.ch>,
Florian Fainelli <f.fainelli@...il.com>,
Heiner Kallweit <hkallweit1@...il.com>,
Baruch Siach <baruch@...s.co.il>
Subject: [PATCH net-next v2] net: phy: marvell10g: add firmware load support
When Marvell 88X3310 and 88E2110 hardware configuration SPI_CONFIG strap
bit is pulled up, the host must load firmware to the PHY volatile RAM
after reset over MDIO. Add support for loading firmware at run-time.
The code loads the firmware only when the PHY is in MV_PMA_BOOT_WAITING
state at probe time, i.e. PHY is waiting for host to load firmware. The
code will not update currently running PHY firmware.
This patch does not include support for writing firmware into the PHY
non-volatile SPI flash (when SPI_CONFIG strap is pulled down).
Firmware files are available from Marvell under NDA.
Signed-off-by: Baruch Siach <baruch@...s.co.il>
---
Kernel Ethernet PHY maintainers are unlikely to take this patch
upstream. This is because the linux-firmware repository can not accept
the firmware files since they are not legally distributable. I post the
code here as reference to anyone who needs firmware load functionality
to make this hardware design work.
This patch applies on top of kernel v5.7-rc2.
v2:
* Declare firmware files with MODULE_FIRMWARE() (Heiner Kallweit)
* Bail out of firmware load loop on PHY write error (Florian Fainelli)
* Release firmware file on load error (Florian Fainelli)
* Drop mv3310_report_firmware_rev(); rely on recently introduced
firmware version report instead
* Extend commit log
---
drivers/net/phy/marvell10g.c | 102 +++++++++++++++++++++++++++++++++++
1 file changed, 102 insertions(+)
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index d3cb88651ad2..f4eb24be77d0 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -28,15 +28,26 @@
#include <linux/marvell_phy.h>
#include <linux/phy.h>
#include <linux/sfp.h>
+#include <linux/firmware.h>
#define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe
#define MV_PHY_ALASKA_NBT_QUIRK_REV (MARVELL_PHY_ID_88X3310 | 0xa)
+#define MV_FIRMWARE_HEADER_SIZE 32
+
enum {
MV_PMA_FW_VER0 = 0xc011,
MV_PMA_FW_VER1 = 0xc012,
MV_PMA_BOOT = 0xc050,
MV_PMA_BOOT_FATAL = BIT(0),
+ MV_PMA_BOOT_PROGRESS_MASK = 0x0006,
+ MV_PMA_BOOT_WAITING = 0x0002,
+ MV_PMA_BOOT_FW_LOADED = BIT(6),
+
+ MV_PCS_FW_LOW_WORD = 0xd0f0,
+ MV_PCS_FW_HIGH_WORD = 0xd0f1,
+ MV_PCS_RAM_DATA = 0xd0f2,
+ MV_PCS_RAM_CHECKSUM = 0xd0f3,
MV_PCS_BASE_T = 0x0000,
MV_PCS_BASE_R = 0x1000,
@@ -354,6 +365,89 @@ static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
return 0;
}
+static int mv3310_write_firmware(struct phy_device *phydev, const u8 *data,
+ unsigned int size)
+{
+ unsigned int low_byte, high_byte;
+ u16 checksum = 0, ram_checksum;
+ unsigned int i = 0;
+ int ret;
+
+ while (i < size) {
+ low_byte = data[i++];
+ high_byte = data[i++];
+ checksum += low_byte + high_byte;
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_PCS_RAM_DATA,
+ (high_byte << 8) | low_byte);
+ if (ret < 0)
+ return ret;
+ cond_resched();
+ }
+
+ ram_checksum = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_RAM_CHECKSUM);
+ if (ram_checksum != checksum) {
+ dev_err(&phydev->mdio.dev, "firmware checksum failed");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mv3310_load_firmware(struct phy_device *phydev)
+{
+ const struct firmware *fw_entry;
+ char *fw_file;
+ int ret;
+
+ switch (phydev->drv->phy_id) {
+ case MARVELL_PHY_ID_88X3310:
+ fw_file = "mrvl/x3310fw.hdr";
+ break;
+ case MARVELL_PHY_ID_88E2110:
+ fw_file = "mrvl/e21x0fw.hdr";
+ break;
+ default:
+ dev_warn(&phydev->mdio.dev, "unknown firmware file for %s PHY",
+ phydev->drv->name);
+ return -EINVAL;
+ }
+
+ ret = request_firmware(&fw_entry, fw_file, &phydev->mdio.dev);
+ if (ret < 0)
+ return ret;
+
+ /* Firmware size must be larger than header, and even */
+ if (fw_entry->size <= MV_FIRMWARE_HEADER_SIZE ||
+ (fw_entry->size % 2) != 0) {
+ dev_err(&phydev->mdio.dev, "firmware file invalid");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Clear checksum register */
+ phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_RAM_CHECKSUM);
+
+ /* Set firmware load address */
+ phy_write_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_LOW_WORD, 0);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_HIGH_WORD, 0x0010);
+
+ ret = mv3310_write_firmware(phydev,
+ fw_entry->data + MV_FIRMWARE_HEADER_SIZE,
+ fw_entry->size - MV_FIRMWARE_HEADER_SIZE);
+ if (ret < 0)
+ goto out;
+
+ phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_BOOT,
+ MV_PMA_BOOT_FW_LOADED, MV_PMA_BOOT_FW_LOADED);
+
+ msleep(100);
+
+out:
+ release_firmware(fw_entry);
+
+ return ret;
+}
+
static const struct sfp_upstream_ops mv3310_sfp_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
@@ -380,6 +474,12 @@ static int mv3310_probe(struct phy_device *phydev)
return -ENODEV;
}
+ if ((ret & MV_PMA_BOOT_PROGRESS_MASK) == MV_PMA_BOOT_WAITING) {
+ ret = mv3310_load_firmware(phydev);
+ if (ret < 0)
+ return ret;
+ }
+
priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@@ -779,6 +879,8 @@ static struct mdio_device_id __maybe_unused mv3310_tbl[] = {
{ MARVELL_PHY_ID_88E2110, MARVELL_PHY_ID_MASK },
{ },
};
+MODULE_FIRMWARE("mrvl/x3310fw.hdr");
+MODULE_FIRMWARE("mrvl/e21x0fw.hdr");
MODULE_DEVICE_TABLE(mdio, mv3310_tbl);
MODULE_DESCRIPTION("Marvell Alaska X 10Gigabit Ethernet PHY driver (MV88X3310)");
MODULE_LICENSE("GPL");
--
2.25.1
Powered by blists - more mailing lists