mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
236 lines
6.1 KiB
Diff
236 lines
6.1 KiB
Diff
From 38ef4ac1fa9bd9b0436311c6f8962646222c54f9 Mon Sep 17 00:00:00 2001
|
|
From: Tobias Waldekranz <tobias@waldekranz.com>
|
|
Date: Tue, 19 Sep 2023 18:38:10 +0200
|
|
Subject: [PATCH 01/50] net: phy: marvell10g: Support firmware loading on
|
|
88X3310
|
|
|
|
When probing, if a device is waiting for firmware to be loaded into
|
|
its RAM, ask userspace for the binary and load it over XMDIO.
|
|
|
|
We have no choice but to bail out of the probe if firmware is not
|
|
available, as the device does not have any built-in image on which to
|
|
fall back.
|
|
---
|
|
drivers/net/phy/marvell10g.c | 161 +++++++++++++++++++++++++++++++++++
|
|
1 file changed, 161 insertions(+)
|
|
|
|
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
|
|
index 8fd42131cdbf..44294519231e 100644
|
|
--- a/drivers/net/phy/marvell10g.c
|
|
+++ b/drivers/net/phy/marvell10g.c
|
|
@@ -25,6 +25,7 @@
|
|
#include <linux/bitfield.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/delay.h>
|
|
+#include <linux/firmware.h>
|
|
#include <linux/hwmon.h>
|
|
#include <linux/marvell_phy.h>
|
|
#include <linux/phy.h>
|
|
@@ -50,6 +51,10 @@ enum {
|
|
MV_PMA_21X0_PORT_CTRL_MACTYPE_10GBASER_RATE_MATCH = 0x6,
|
|
MV_PMA_BOOT = 0xc050,
|
|
MV_PMA_BOOT_FATAL = BIT(0),
|
|
+ MV_PMA_BOOT_PRGS_MASK = 0x0006,
|
|
+ MV_PMA_BOOT_PRGS_WAIT = 0x0002,
|
|
+ MV_PMA_BOOT_APP_STARTED = BIT(4),
|
|
+ MV_PMA_BOOT_APP_LOADED = BIT(6),
|
|
|
|
MV_PCS_BASE_T = 0x0000,
|
|
MV_PCS_BASE_R = 0x1000,
|
|
@@ -96,6 +101,12 @@ enum {
|
|
MV_PCS_PORT_INFO_NPORTS_MASK = 0x0380,
|
|
MV_PCS_PORT_INFO_NPORTS_SHIFT = 7,
|
|
|
|
+ /* Firmware downloading */
|
|
+ MV_PCS_FW_ADDR_LOW = 0xd0f0,
|
|
+ MV_PCS_FW_ADDR_HIGH = 0xd0f1,
|
|
+ MV_PCS_FW_DATA = 0xd0f2,
|
|
+ MV_PCS_FW_CSUM = 0xd0f3,
|
|
+
|
|
/* SerDes reinitialization 88E21X0 */
|
|
MV_AN_21X0_SERDES_CTRL2 = 0x800f,
|
|
MV_AN_21X0_SERDES_CTRL2_AUTO_INIT_DIS = BIT(13),
|
|
@@ -157,6 +168,8 @@ struct mv3310_chip {
|
|
const struct mv3310_mactype *mactypes;
|
|
size_t n_mactypes;
|
|
|
|
+ const char *firmware_path;
|
|
+
|
|
#ifdef CONFIG_HWMON
|
|
int (*hwmon_read_temp_reg)(struct phy_device *phydev);
|
|
#endif
|
|
@@ -487,6 +500,145 @@ static const struct sfp_upstream_ops mv3310_sfp_ops = {
|
|
.module_insert = mv3310_sfp_insert,
|
|
};
|
|
|
|
+struct mv3310_fw_hdr {
|
|
+ __le32 data_size;
|
|
+ __le32 data_addr;
|
|
+ __le16 data_csum;
|
|
+ __le16 flags;
|
|
+#define MV3310_FW_HDR_DATA_ONLY BIT(6)
|
|
+ __le32 next_hdr;
|
|
+ __le16 csum;
|
|
+
|
|
+ u8 pad[14];
|
|
+} __packed;
|
|
+
|
|
+struct mv3310_fw_sect_info {
|
|
+ const u8 *data;
|
|
+ u32 size;
|
|
+ u32 addr;
|
|
+ u16 csum;
|
|
+};
|
|
+
|
|
+static int mv3310_load_fw_sect(struct phy_device *phydev,
|
|
+ const struct mv3310_fw_sect_info *si)
|
|
+{
|
|
+ size_t i;
|
|
+ u16 csum;
|
|
+
|
|
+ dev_dbg(&phydev->mdio.dev, "Loading %u byte section at 0x%08x\n",
|
|
+ si->size, si->addr);
|
|
+
|
|
+ for (i = 0, csum = 0; i < si->size; i++)
|
|
+ csum += si->data[i];
|
|
+
|
|
+ if ((u16)~csum != si->csum) {
|
|
+ dev_err(&phydev->mdio.dev, "Corrupt section data\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Any existing checksum is cleared by a read */
|
|
+ __phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_CSUM);
|
|
+
|
|
+ __phy_write_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_ADDR_LOW, si->addr & 0xffff);
|
|
+ __phy_write_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_ADDR_HIGH, si->addr >> 16);
|
|
+
|
|
+ for (i = 0; i < si->size; i += 2) {
|
|
+ __phy_write_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_DATA,
|
|
+ (si->data[i + 1] << 8) | si->data[i]);
|
|
+ }
|
|
+
|
|
+ csum = __phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_FW_CSUM);
|
|
+ if ((u16)~csum != si->csum) {
|
|
+ dev_err(&phydev->mdio.dev, "Download failed\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mv3310_load_fw(struct phy_device *phydev)
|
|
+{
|
|
+ const struct mv3310_chip *chip = to_mv3310_chip(phydev);
|
|
+ struct mv3310_fw_sect_info si;
|
|
+ const struct firmware *fw;
|
|
+ struct mv3310_fw_hdr hdr;
|
|
+ bool need_boot = false;
|
|
+ const u8 *sect;
|
|
+ size_t i;
|
|
+ u32 next;
|
|
+ u16 csum;
|
|
+ int err;
|
|
+
|
|
+ if (!chip->firmware_path)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ err = request_firmware(&fw, chip->firmware_path, &phydev->mdio.dev);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ if (fw->size & 1) {
|
|
+ err = -EINVAL;
|
|
+ goto release;
|
|
+ }
|
|
+
|
|
+ phy_lock_mdio_bus(phydev);
|
|
+
|
|
+ for (sect = fw->data; (sect + sizeof(hdr)) < (fw->data + fw->size);) {
|
|
+ memcpy(&hdr, sect, sizeof(hdr));
|
|
+
|
|
+ for (i = 0, csum = 0; i < offsetof(struct mv3310_fw_hdr, csum); i++)
|
|
+ csum += sect[i];
|
|
+
|
|
+ if ((u16)~csum != le16_to_cpu(hdr.csum)) {
|
|
+ dev_err(&phydev->mdio.dev, "Corrupt section header\n");
|
|
+ err = -EINVAL;
|
|
+ goto unlock;
|
|
+ }
|
|
+
|
|
+ si.data = sect + sizeof(hdr);
|
|
+ si.size = le32_to_cpu(hdr.data_size);
|
|
+ si.addr = le32_to_cpu(hdr.data_addr);
|
|
+ si.csum = le16_to_cpu(hdr.data_csum);
|
|
+
|
|
+ if ((si.data + si.size) > (fw->data + fw->size)) {
|
|
+ dev_err(&phydev->mdio.dev, "Invalid section length\n");
|
|
+ err = -EINVAL;
|
|
+ goto unlock;
|
|
+ }
|
|
+
|
|
+ err = mv3310_load_fw_sect(phydev, &si);
|
|
+ if (err)
|
|
+ goto unlock;
|
|
+
|
|
+ if (!(le16_to_cpu(hdr.flags) & MV3310_FW_HDR_DATA_ONLY))
|
|
+ need_boot = true;
|
|
+
|
|
+ next = le32_to_cpu(hdr.next_hdr);
|
|
+ if (!next)
|
|
+ break;
|
|
+
|
|
+ sect = fw->data + next;
|
|
+ }
|
|
+
|
|
+ if (need_boot) {
|
|
+ __phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_BOOT, 0,
|
|
+ MV_PMA_BOOT_APP_LOADED);
|
|
+ mdelay(200);
|
|
+ if (!(__phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_PMA_BOOT) &
|
|
+ MV_PMA_BOOT_APP_STARTED)) {
|
|
+ dev_err(&phydev->mdio.dev, "Firmware boot failed\n");
|
|
+ err = -ENODEV;
|
|
+ }
|
|
+ }
|
|
+
|
|
+unlock:
|
|
+ phy_unlock_mdio_bus(phydev);
|
|
+
|
|
+release:
|
|
+ release_firmware(fw);
|
|
+ return err;
|
|
+}
|
|
+
|
|
static int mv3310_probe(struct phy_device *phydev)
|
|
{
|
|
const struct mv3310_chip *chip = to_mv3310_chip(phydev);
|
|
@@ -508,6 +660,12 @@ static int mv3310_probe(struct phy_device *phydev)
|
|
return -ENODEV;
|
|
}
|
|
|
|
+ if ((ret & MV_PMA_BOOT_PRGS_MASK) == MV_PMA_BOOT_PRGS_WAIT) {
|
|
+ ret = mv3310_load_fw(phydev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
@@ -1201,6 +1359,8 @@ static const struct mv3310_chip mv3310_type = {
|
|
.mactypes = mv3310_mactypes,
|
|
.n_mactypes = ARRAY_SIZE(mv3310_mactypes),
|
|
|
|
+ .firmware_path = "mrvl/x3310fw.hdr",
|
|
+
|
|
#ifdef CONFIG_HWMON
|
|
.hwmon_read_temp_reg = mv3310_hwmon_read_temp_reg,
|
|
#endif
|
|
@@ -1474,4 +1634,5 @@ static const struct mdio_device_id __maybe_unused mv3310_tbl[] = {
|
|
};
|
|
MODULE_DEVICE_TABLE(mdio, mv3310_tbl);
|
|
MODULE_DESCRIPTION("Marvell Alaska X/M multi-gigabit Ethernet PHY driver");
|
|
+MODULE_FIRMWARE("mrvl/x3310fw.hdr");
|
|
MODULE_LICENSE("GPL");
|