blob: 3d1f602c7e85e5663a5ba875bbfd971b2ffa5291 [file] [log] [blame]
From 2fd17c806f5ad23f9958c358cdc26f618f05d5df Mon Sep 17 00:00:00 2001
From: Sam Shih <sam.shih@mediatek.com>
Date: Fri, 2 Jun 2023 13:06:32 +0800
Subject: [PATCH]
[networking][999-2719-net-phy-aquantia-add-firmware-download.patch]
---
drivers/net/phy/Kconfig | 33 +-
drivers/net/phy/Makefile | 3 +
drivers/net/phy/aquantia.h | 64 ++
drivers/net/phy/aquantia_firmware.c | 1090 +++++++++++++++++++++++++++
drivers/net/phy/aquantia_main.c | 92 ++-
5 files changed, 1241 insertions(+), 41 deletions(-)
create mode 100644 drivers/net/phy/aquantia_firmware.c
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index c0e09c99d..d467834eb 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -372,7 +372,38 @@ config AMD_PHY
config AQUANTIA_PHY
tristate "Aquantia PHYs"
---help---
- Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405
+ Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405, AQR113C
+
+config AQUANTIA_PHY_FW_DOWNLOAD
+ tristate "Firmware Download Enable"
+ depends on AQUANTIA_PHY
+ ---help---
+ Currently supports the Aquantia AQR113C
+
+choice
+ prompt "Download mode"
+ default AQUANTIA_PHY_FW_DOWNLOAD_GANG
+ depends on AQUANTIA_PHY_FW_DOWNLOAD
+
+ config AQUANTIA_PHY_FW_DOWNLOAD_SINGLE
+ bool "Single"
+ ---help---
+ If you would like to download firmware in sequential way,
+ please select this option.
+
+ config AQUANTIA_PHY_FW_DOWNLOAD_GANG
+ bool "Gang"
+ ---help---
+ If you would like to download firmware in parallel way,
+ please select this option.
+endchoice
+
+config AQUANTIA_PHY_FW_FILE
+ string "FW File"
+ depends on AQUANTIA_PHY
+ default "Rhe-05.06-Candidate7-AQR_Mediatek_23B_StartOff_ID45623_VER36657.cld"
+ ---help---
+ Currently supports the Aquantia AQR113c
config AX88796B_PHY
tristate "Asix PHYs"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 8b57d6105..e9653de36 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -68,6 +68,9 @@ aquantia-objs += aquantia_main.o
ifdef CONFIG_HWMON
aquantia-objs += aquantia_hwmon.o
endif
+ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD
+aquantia-objs += aquantia_firmware.o
+endif
obj-$(CONFIG_AIROHA_EN8801SC_PHY) += en8801sc.o
obj-$(CONFIG_AIROHA_EN8811H_PHY) += air_en8811h.o
obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
diff --git a/drivers/net/phy/aquantia.h b/drivers/net/phy/aquantia.h
index 5a16caab7..ab1c241d3 100644
--- a/drivers/net/phy/aquantia.h
+++ b/drivers/net/phy/aquantia.h
@@ -9,8 +9,72 @@
#include <linux/device.h>
#include <linux/phy.h>
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
+
+#define PMAPMD_RSVD_VEND_PROV 0xe400
+#define PMAPMD_RSVD_VEND_PROV_MDI_CONF BIT(0)
+
+/* MDIO_MMD_C22EXT */
+#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
+#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
+#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
+#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
+#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
+#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
+#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
+#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
+#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
+#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
+
+struct aqr107_hw_stat {
+ const char *name;
+ int reg;
+ int size;
+};
+
+#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
+static const struct aqr107_hw_stat aqr107_hw_stats[] = {
+ SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
+ SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
+ SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
+ SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
+ SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
+ SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
+ SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
+ SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
+ SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
+ SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
+};
+#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
+
+struct aqr107_priv {
+ u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD
+ struct phy_device *phydevs[1];
+ struct task_struct *heartbeat_thread;
+ spinlock_t lock;
+ bool fw_initialized;
+ int fw_dl_mode;
+ u16 heartbeat;
+#endif
+};
+
+int aqr107_set_downshift(struct phy_device *phydev, u8 cnt);
+void aqr107_chip_info(struct phy_device *phydev);
+int aqr107_config_mdi(struct phy_device *phydev);
+
#if IS_REACHABLE(CONFIG_HWMON)
int aqr_hwmon_probe(struct phy_device *phydev);
#else
static inline int aqr_hwmon_probe(struct phy_device *phydev) { return 0; }
#endif
+
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD
+enum aqr_fw_dl_mode {
+ FW_DL_SINGLE = 0,
+ FW_DL_GNAGLOAD,
+};
+
+int aqr_firmware_heartbeat_thread(void *data);
+int aqr_firmware_download(struct phy_device *phydev);
+#endif
diff --git a/drivers/net/phy/aquantia_firmware.c b/drivers/net/phy/aquantia_firmware.c
new file mode 100644
index 000000000..d2828aad4
--- /dev/null
+++ b/drivers/net/phy/aquantia_firmware.c
@@ -0,0 +1,1110 @@
+// SPDX-License-Identifier: GPL-2.0
+/* FW download driver for Aquantia PHY
+ */
+
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+
+#include "aquantia.h"
+
+#undef AQ_VERBOSE
+
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD_SINGLE
+#define MAX_GANGLOAD_DEVICES 1
+#elif CONFIG_AQUANTIA_PHY_FW_DOWNLOAD_GANG
+#define MAX_GANGLOAD_DEVICES 2
+#endif
+
+#define AQR_FIRMWARE CONFIG_AQUANTIA_PHY_FW_FILE
+
+/* Vendor specific 1, MDIO_MMD_VEND1 */
+#define VEND1_STD_CONTROL1 0x0000
+#define VEND1_STD_CONTROL1_SOFT_RESET BIT(15)
+
+#define VEND1_MAILBOX_INTERFACE1 0x0200
+#define VEND1_MAILBOX_INTERFACE1_START BIT(15)
+#define VEND1_MAILBOX_INTERFACE1_WRITE FIELD_PREP(BIT(14), 1)
+#define VEND1_MAILBOX_INTERFACE1_READ FIELD_PREP(BIT(14), 0)
+#define VEND1_MAILBOX_INTERFACE1_RESET_CRC BIT(12)
+
+#define VEND1_MAILBOX_INTERFACE2 0x0201
+#define VEND1_MAILBOX_INTERFACE2_CRC GENMASK(15, 0)
+
+#define VEND1_MAILBOX_INTERFACE3 0x0202
+#define VEND1_MAILBOX_INTERFACE3_ADDR_MSW GENMASK(15, 0)
+
+#define VEND1_MAILBOX_INTERFACE4 0x0203
+#define VEND1_MAILBOX_INTERFACE4_ADDR_LSW GENMASK(15, 2)
+
+#define VEND1_MAILBOX_INTERFACE5 0x0204
+#define VEND1_MAILBOX_INTERFACE5_DATA_MSW GENMASK(15, 0)
+
+#define VEND1_MAILBOX_INTERFACE6 0x0205
+#define VEND1_MAILBOX_INTERFACE6_DATA_LSW GENMASK(15, 0)
+
+#define VEND1_CONTROL2 0xc001
+#define VEND1_CONTROL2_UP_RESET BIT(15)
+#define VEND1_CONTROL2_UP_RUNSTALL_OVERRIDE BIT(6)
+#define VEND1_CONTROL2_UP_RUNSTALL BIT(0)
+
+#define VEND1_RESET_CONTROL 0xc006
+#define VEND1_RESET_CONTROL_MMD_RESET_DISABLE BIT(14)
+
+#define VEND1_GENERAL_PROV2 0xc441
+#define VEND1_GENERAL_PROV2_MDIO_BROADCAST_ENABLE BIT(14)
+
+#define VEND1_GENERAL_PROV8 0xc447
+#define VEND1_GENERAL_PROV8_MDIO_BROADCAST_ADDRESS GENMASK(4, 0)
+
+#define VEND1_NVR_PROV3 0xc452
+#define VEND1_NVR_PROV3_DAISYCHAIN_DISABLE BIT(0)
+
+#define VEND1_RSVD_PROV2 0xc471
+#define VEND1_RSVD_PROV2_DAISYCHAIN_HOPCOUNT GENMASK(5, 0)
+#define VEND1_RSVD_PROV2_DAISYCHAIN_HOPCOUNT_OVERRIDE BIT(6)
+
+#define VEND1_GLOBAL_RSVD_STAT2 0xc886
+
+/*! The byte address, in processor memory, of the start of the IRAM segment. */
+#define AQ_IRAM_BASE_ADDRESS 0x40000000
+
+/*! The byte address, in processor memory, of the start of the DRAM segment. */
+#define AQ_DRAM_BASE_ADDRESS 0x3FFE0000
+
+/*! The byte offset from the top of the PHY image to the header content (HHD & EUR devices). */
+#define AQ_PHY_IMAGE_HEADER_CONTENT_OFFSET_HHD 0x300
+
+/*! The offset, from the start of DRAM, where the provisioning block begins. */
+#define AQ_PHY_IMAGE_PROVTABLE_OFFSET 0x680
+
+/*! The offset, from the start of DRAM, where the provisioning block's ending address is recorded. */
+#define AQ_PHY_IMAGE_PROVTABLE_TERM_OFFSET 0x028C
+
+/*! The size of the space alloted within the PHY image for the provisioning table. */
+#define AQ_PHY_IMAGE_PROVTABLE_MAXSIZE 0x800
+
+/*! The maximum number of ports that can be MDIO bootloaded at once. */
+#define AQ_MAX_NUM_PHY_IDS 48
+
+/*! This enumeration is used to describe the different types of
+ Aquantia PHY.*/
+typedef enum
+{
+ /*! 1/2/4-port package, 40nm architechture.*/
+ AQ_DEVICE_APPIA,
+ /*! 1/2/4-port package, first-generation 28nm architechture.*/
+ AQ_DEVICE_HHD,
+ /*! 1/2/4-port package, second-generation 28nm architechture.*/
+ AQ_DEVICE_EUR,
+ /*! 1/2/4-port package, third-generation 28nm architechture.*/
+ AQ_DEVICE_CAL,
+ /*! 1/2/4/8-port package, forth-generation 14nm architechture.*/
+ AQ_DEVICE_RHEA,
+ /*! 8-port package, fifth-generation 14nm architechture.*/
+ AQ_DEVICE_DIONE
+} AQ_API_Device;
+
+/*! The table used to compute CRC's within the PHY. */
+const uint16_t AQ_CRC16Table[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
+ 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+ 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
+ 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+ 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
+ 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
+ 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
+ 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+ 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
+ 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+ 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+ 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
+ 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
+ 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
+ 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+ 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
+ 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
+ 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+ 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+ 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
+ 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+ 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
+ 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0};
+
+struct task_struct *gangload_kthread = NULL;
+struct phy_device *gangload_phydevs[MAX_GANGLOAD_DEVICES];
+static unsigned long gangload_timeout = 0;
+static int gangload = 0;
+static DEFINE_SPINLOCK(gangload_lock);
+
+static int aqr_firmware_download_single(struct phy_device *phydev, bool force_reload);
+
+void AQ_API_EnableMDIO_BootLoadMode
+(
+ /*! The target PHY port.*/
+ struct phy_device *phydev,
+ /*! The provisioning address to use when the FW starts and applies the
+ * bootloaded image's provisioned values. */
+ unsigned int provisioningAddress
+)
+{
+ uint16_t globalNvrProvisioning;
+ uint16_t globalReservedProvisioning;
+
+ /* disable the daisy-chain */
+ globalNvrProvisioning = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_NVR_PROV3);
+ globalNvrProvisioning |= VEND1_NVR_PROV3_DAISYCHAIN_DISABLE;
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_NVR_PROV3, globalNvrProvisioning);
+
+ /* override the hop-count */
+ globalReservedProvisioning = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_RSVD_PROV2);
+ globalReservedProvisioning &= ~VEND1_RSVD_PROV2_DAISYCHAIN_HOPCOUNT;
+ globalReservedProvisioning |= FIELD_PREP(VEND1_RSVD_PROV2_DAISYCHAIN_HOPCOUNT,
+ provisioningAddress);
+ globalReservedProvisioning |= VEND1_RSVD_PROV2_DAISYCHAIN_HOPCOUNT_OVERRIDE;
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_RSVD_PROV2, globalReservedProvisioning);
+
+ return;
+}
+
+/*! Prepare the specified port for MDIO bootloading, and set the temporary MDIO
+ * address to be used during the bootload process. Disables the daisy-chain,
+ * and explicitly sets the port's provisioningAddress. */
+void AQ_API_EnableGangLoadMode
+(
+ /*! The target PHY port.*/
+ struct phy_device *phydev,
+ /*! The PHY's MDIO address will be changed to this value during the
+ * bootload process. */
+ unsigned int gangLoadAddress
+)
+{
+ uint16_t globalGeneralProvisioning;
+
+ /* Enable gangload mode. After doing this, the PHY will be
+ * addressable at the MDIO address indicated by gangLoadAddress.
+ * Now that the PHY is in gangload mode, MDIO reads are prohibited
+ * until AQ_API_DisableGangLoadMode is called. */
+ globalGeneralProvisioning = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GENERAL_PROV8);
+ globalGeneralProvisioning &= ~VEND1_GENERAL_PROV8_MDIO_BROADCAST_ADDRESS;
+ globalGeneralProvisioning |= FIELD_PREP(VEND1_GENERAL_PROV8_MDIO_BROADCAST_ADDRESS,
+ gangLoadAddress);
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GENERAL_PROV8, globalGeneralProvisioning);
+
+ globalGeneralProvisioning = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GENERAL_PROV2);
+ globalGeneralProvisioning |= VEND1_GENERAL_PROV2_MDIO_BROADCAST_ENABLE;
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GENERAL_PROV2, globalGeneralProvisioning);
+
+ return;
+}
+
+/*! Restore the PHY's MDIO address to the pin-specified value. Should be
+ * called when MDIO bootloading is complete, to return to normal MDIO
+ * addressing.
+ * <b>This is a gang-load function, hence write-only!</b> */
+void AQ_API_DisableGangLoadMode
+(
+ /*! The target PHY port.*/
+ struct phy_device *phydev,
+ /*! The value to write to of AQ_GlobalGeneralProvisioning.u1.word_1. */
+ uint16_t origVal_GGP1
+)
+{
+ uint16_t globalGeneralProvisioning;
+
+ /* Restore the original value of globalGeneralProvisioning.u1, and set
+ * the MDIO address reset bit. This will cause the MDIO address to be
+ * reset to the value indicated by the pins. */
+ globalGeneralProvisioning = origVal_GGP1;
+ globalGeneralProvisioning &= ~VEND1_GENERAL_PROV2_MDIO_BROADCAST_ENABLE;
+ phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GENERAL_PROV2, globalGeneralProvisioning);
+
+ /* The PHY has now exited gang-load mode. */
+ return;
+}
+
+/* Common implementation of MDIO bootload routine, for the entry points:
+ * AQ_API_WriteBootLoadImage
+ * AQ_API_WriteBootLoadImageWithProvTable
+ * AQ_API_WriteBootLoadImageDRAMOnly
+ * AQ_API_WriteBootLoadImageWithProvTableDRAMOnly */
+static int AQ_API_WriteBootLoadImage_impl
+(
+ struct phy_device **phydevs,
+ int num_phydevs,
+ struct phy_device *gandload_phydev,
+ int *result,
+ const uint32_t* imageSizePointer,
+ const uint8_t* image,
+ const uint32_t* provTableSizePointer,
+ const uint8_t* provTableImage,
+ bool dramOnly
+)
+{
+ uint32_t primaryHeaderPtr = 0x00000000;
+ uint32_t primaryIramPtr = 0x00000000;
+ uint32_t primaryDramPtr = 0x00000000;
+ uint32_t primaryIramSize = 0x00000000;
+ uint32_t primaryDramSize = 0x00000000;
+ uint32_t terminatorPtr = 0x00000000;
+ uint32_t phyImageHeaderContentOffset = 0x00000000;
+ uint32_t i, j;
+ uint32_t imageSize;
+ uint32_t provTableImageSize = 0;
+ uint32_t bytePointer;
+ uint32_t byteSize;
+ uint32_t dWordSize;
+#ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ uint32_t countPendingOps; /* A count of block MDIO operation pending... necessary to keep a count
+ in order to ensure we don't exceed the maximum pending operations. */
+#endif
+ uint16_t globalControl;
+ uint16_t msw;
+ uint16_t lsw;
+ uint16_t crc16Calculated;
+ uint16_t provTableCrc16Calculated;
+ uint16_t fileCRC;
+ uint16_t provTableFileCRC;
+ uint16_t mailboxCRC;
+ uint16_t mailboxWrite;
+ uint16_t recordedGGP1Values[AQ_MAX_NUM_PHY_IDS]; /* When entering/exiting gangload mode, we record and restore
+ the AQ_GlobalGeneralProvisioning.u1 register values. */
+
+ /* store the CRC-16 for the image, which is the last two bytes */
+ imageSize = *imageSizePointer;
+
+ /*
+ * If the imageSize is less than 2, we don't do anything
+ */
+ if (imageSize < 2) {
+ result[0] = -EINVAL;
+ return -EINVAL;
+ }
+
+ fileCRC = image[imageSize-2] << 8 | image[imageSize-1];
+
+ /*------------------------------------- Check the image integrity ------------------------------------------------*/
+ crc16Calculated = 0x0000;
+ for (i = 0; i < imageSize-2; i++)
+ {
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ image[i]];
+ }
+
+ if (crc16Calculated != fileCRC)
+ {
+ phydev_err(phydevs[0], "CRC check failed on image file (expected 0x%X, found 0x%X)\n",
+ fileCRC, crc16Calculated);
+ result[0] = -EINVAL;
+ return -EINVAL;
+ }
+ else
+ {
+ phydev_info(phydevs[0], "CRC check good on image file (0x%04X)\n", crc16Calculated);
+ }
+
+ /*-------------------------------- Check the provisioning table image integrity ----------------------------------*/
+ if (provTableSizePointer != NULL && provTableImage != NULL)
+ {
+ provTableImageSize = (*provTableSizePointer) - 2;
+ provTableFileCRC = provTableImage[provTableImageSize + 1] << 8 |
+ provTableImage[provTableImageSize];
+
+ provTableCrc16Calculated = 0x0000;
+ for (i = 0; i < provTableImageSize; i++)
+ {
+ provTableCrc16Calculated = ((provTableCrc16Calculated & 0xFF) << 8) ^
+ AQ_CRC16Table[(provTableCrc16Calculated >> 8) ^ provTableImage[i]];
+ }
+
+ if (provTableCrc16Calculated != provTableFileCRC)
+ {
+ phydev_err(phydevs[0], "CRC check failed on provisioning table file (expected 0x%X, found 0x%X)\n",
+ provTableFileCRC, provTableCrc16Calculated);
+ result[0] = -EINVAL;
+ return -EINVAL;
+ }
+ else
+ {
+ phydev_info(phydevs[0], "CRC check good on provisioning table file (0x%04X)\n",
+ provTableCrc16Calculated);
+ }
+ }
+
+ /*--------------------------- Store 1E.C441 values for later use. Enforce uniformity. ---------------------------*/
+ for (j = 0; j < num_phydevs; j++)
+ {
+ /* Record the original value of AQ_GlobalGeneralProvisioning.u1.word_1,
+ * so that we can restore it later after exiting gangload mode. */
+ recordedGGP1Values[j] = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_GENERAL_PROV2);
+
+ /* If any of the PHYs' GGP1 values don't match the others, set the appropriate
+ * error code and return. */
+ if (j > 0 && recordedGGP1Values[j] != recordedGGP1Values[0])
+ {
+ phydev_err(phydevs[j], "Non-uniform value of 1E.C441 found (expected 0x%X, found 0x%X)\n",
+ recordedGGP1Values[0], recordedGGP1Values[j]);
+ result[j] = -EINVAL;
+ return -EINVAL;
+ }
+ }
+
+ /*--------------------------- Put each PHY into gangload mode at the specified address ---------------------------*/
+ for (j = 0; j < num_phydevs; j++) {
+ AQ_API_EnableMDIO_BootLoadMode(phydevs[j], 0);
+ AQ_API_EnableGangLoadMode(phydevs[j], gandload_phydev->mdio.addr);
+ }
+
+ /*------------------------------------- Stall the uP ------------------------------------------------------------*/
+ globalControl = 0x0000;
+ globalControl |= VEND1_CONTROL2_UP_RUNSTALL_OVERRIDE;
+ globalControl |= VEND1_CONTROL2_UP_RUNSTALL;
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_CONTROL2, globalControl);
+
+ /*------------------------------------- Initialize the mailbox write command -------------------------------------*/
+ mailboxWrite = 0x0000;
+ mailboxWrite |= VEND1_MAILBOX_INTERFACE1_WRITE;
+ mailboxWrite |= VEND1_MAILBOX_INTERFACE1_START;
+
+ /*------------------------------------- Read the segment addresses and sizes -------------------------------------*/
+ primaryHeaderPtr = (((image[0x9] & 0x0F) << 8) | image[0x8]) << 12;
+
+ /* setup image header content offset for HHD/EUR/CAL/RHEA */
+ phyImageHeaderContentOffset = AQ_PHY_IMAGE_HEADER_CONTENT_OFFSET_HHD;
+
+ primaryIramPtr = (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0x4 + 2] << 16) |
+ (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0x4 + 1] << 8) |
+ image[primaryHeaderPtr + phyImageHeaderContentOffset + 0x4];
+ primaryIramSize = (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0x7 + 2] << 16) |
+ (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0x7 + 1] << 8) |
+ image[primaryHeaderPtr + phyImageHeaderContentOffset + 0x7];
+ primaryDramPtr = (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0xA + 2] << 16) |
+ (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0xA + 1] << 8) |
+ image[primaryHeaderPtr + phyImageHeaderContentOffset + 0xA];
+ primaryDramSize = (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0xD + 2] << 16) |
+ (image[primaryHeaderPtr + phyImageHeaderContentOffset + 0xD + 1] << 8) |
+ image[primaryHeaderPtr + phyImageHeaderContentOffset + 0xD];
+
+ /* setup primary image pointer for HHD/EUR/CAL/RHEA */
+ primaryIramPtr += primaryHeaderPtr;
+ primaryDramPtr += primaryHeaderPtr;
+
+ phydev_info(gandload_phydev, "Segment Addresses and Sizes as read from the PHY ROM image header:\n");
+ phydev_info(gandload_phydev, "Primary Iram Address: 0x%x\n", primaryIramPtr);
+ phydev_info(gandload_phydev, "Primary Iram Size: 0x%x\n", primaryIramSize);
+ phydev_info(gandload_phydev, "Primary Dram Address: 0x%x\n", primaryDramPtr);
+ phydev_info(gandload_phydev, "Primary Dram Size: 0x%x\n", primaryDramSize);
+
+ /*------------------ Prepare to merge the provisioning table into the main image ---------------------------------*/
+ if (provTableSizePointer != NULL && provTableImage != NULL)
+ {
+ /* Locate the terminator of the built-in provisioning table */
+ terminatorPtr = primaryDramPtr +
+ ((image[primaryDramPtr + AQ_PHY_IMAGE_PROVTABLE_TERM_OFFSET + 1] << 8) |
+ image[primaryDramPtr + AQ_PHY_IMAGE_PROVTABLE_TERM_OFFSET]);
+
+ phydev_info(gandload_phydev, "Supplied Provisioning Table At Address: 0x%x\n\n", terminatorPtr);
+
+ /* Check that the supplied provisioning table will fit within the alloted
+ * space. */
+ if (terminatorPtr - (primaryDramPtr + AQ_PHY_IMAGE_PROVTABLE_OFFSET) +
+ provTableImageSize > AQ_PHY_IMAGE_PROVTABLE_MAXSIZE)
+ {
+ result[0] = -EINVAL;
+ return -EINVAL;
+ }
+ }
+
+ /*------------------------------------- Load IRAM and DRAM -------------------------------------------------------*/
+ /* clear the mailbox CRC */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, VEND1_MAILBOX_INTERFACE1_RESET_CRC);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, 0x0000);
+
+ crc16Calculated = 0; /* This is to calculate what was written through the mailbox */
+
+ if (!dramOnly)
+ {
+ /* load the IRAM */
+ phydev_info(gandload_phydev, "Loading IRAM:\n");
+
+ /* dWord align the address: note the image addressing is byte based, but is properly aligned on dWord
+ boundaries, so the 2 LSbits of the block start are always zero. */
+ msw = (uint16_t) (AQ_IRAM_BASE_ADDRESS >> 16);
+ lsw = (AQ_IRAM_BASE_ADDRESS & 0xFFFF) >> 2;
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE3, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE4, lsw);
+
+ /* set block size so that there are from 0-3 bytes remaining */
+ byteSize = primaryIramSize;
+ dWordSize = byteSize >> 2;
+
+ bytePointer = primaryIramPtr;
+ #ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ countPendingOps = 0;
+ #endif
+ for (i = 0; i < dWordSize; i++)
+ {
+ /* write 4 bytes of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ lsw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ lsw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ msw = provTableImage[bytePointer - terminatorPtr];
+ else
+ msw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ msw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ msw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ #ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+
+ countPendingOps += 3;
+ /* Check if we've filled our output buffer, and if so, flush. */
+ #ifdef AQ_EXTRA_FLAGS
+ if (countPendingOps >= AQ_API_MDIO_MaxBlockOperations(0) - 3)
+ #else
+ if (countPendingOps >= AQ_API_MDIO_MaxBlockOperations() - 3)
+ #endif
+ {
+ AQ_API_MDIO_BlockOperationExecute(gandload_phydev);
+ countPendingOps = 0;
+ }
+ #else
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ #endif
+
+ /* update the calculated CRC */
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw & 0xFF)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw & 0xFF)];
+
+ #ifdef AQ_VERBOSE
+ if (i && ((i % 512) == 0)) phydev_info(gandload_phydev, " Byte: %X:\n", i << 2);
+ #endif
+ }
+
+ #ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ /* flush the output buffer one last time. */
+ AQ_API_MDIO_BlockOperationExecute(gandload_phydev);
+ countPendingOps = 0;
+ #endif
+
+ /* Note: this final write right-justifies non-dWord data in the final dWord */
+ switch (byteSize & 0x3)
+ {
+ case 0x1:
+ /* write 1 byte of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ bytePointer += 1;
+
+ msw = 0x0000;
+
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+
+ /* no polling */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ break;
+
+ case 0x2:
+ /* write 2 bytes of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ lsw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ lsw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ msw = 0x0000;
+
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+
+ /* no polling */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ break;
+
+ case 0x3:
+ /* write 3 bytes of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ lsw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ lsw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ msw = provTableImage[bytePointer - terminatorPtr];
+ else
+ msw = image[bytePointer];
+
+ bytePointer += 1;
+
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+
+ /* no polling */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ break;
+ }
+
+ if (byteSize & 0x3)
+ {
+ /* update the calculated CRC */
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw & 0xFF)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw & 0xFF)];
+ }
+
+ phydev_info(gandload_phydev, "CRC-16 after loading IRAM: 0x%X\n", crc16Calculated);
+ }
+
+ /* load the DRAM */
+ phydev_info(gandload_phydev, "Loading DRAM:\n");
+
+ /* dWord align the address: note the image addressing is byte based, but is properly aligned on dWord
+ boundaries, so the 2 LSbits of the block start are always zero. */
+ msw = (uint16_t) (AQ_DRAM_BASE_ADDRESS >> 16);
+ lsw = (AQ_DRAM_BASE_ADDRESS & 0xFFFF) >> 2;
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE3, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE4, lsw);
+
+ /* set block size so that there are from 0-3 bytes remaining */
+ byteSize = primaryDramSize;
+ dWordSize = byteSize >> 2;
+
+ bytePointer = primaryDramPtr;
+#ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ countPendingOps = 0;
+#endif
+ for (i = 0; i < dWordSize; i++)
+ {
+ /* write 4 bytes of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ lsw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ lsw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ msw = provTableImage[bytePointer - terminatorPtr];
+ else
+ msw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ msw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ msw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ #ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+
+ countPendingOps += 3;
+ /* Check if we've filled our output buffer, and if so, flush. */
+ #ifdef AQ_EXTRA_FLAGS
+ if (countPendingOps >= AQ_API_MDIO_MaxBlockOperations(0) - 3)
+ #else
+ if (countPendingOps >= AQ_API_MDIO_MaxBlockOperations() - 3)
+ #endif
+ {
+ AQ_API_MDIO_BlockOperationExecute(gandload_phydev);
+ countPendingOps = 0;
+ }
+ #else
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ #endif
+
+ /* update the calculated CRC */
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw & 0xFF)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw & 0xFF)];
+
+ #ifdef AQ_VERBOSE
+ if (i && ((i % 512) == 0)) phydev_info(gandload_phydev, " Byte: %X:\n", i << 2);
+ #endif
+ }
+
+ #ifdef AQ_PHY_SUPPORTS_BLOCK_READ_WRITE
+ /* flush the output buffer one last time. */
+ AQ_API_MDIO_BlockOperationExecute(gandload_phydev);
+ countPendingOps = 0;
+ #endif
+
+ /* Note: this final write right-justifies non-dWord data in the final dWord */
+ switch (byteSize & 0x3)
+ {
+ case 0x1:
+ /* write 1 byte of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ bytePointer += 1;
+
+ msw = 0x0000;
+
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+
+ /* no polling */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ break;
+
+ case 0x2:
+ /* write 2 bytes of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ lsw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ lsw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ msw = 0x0000;
+
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+
+ /* no polling */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ break;
+
+ case 0x3:
+ /* write 3 bytes of data */
+ if (terminatorPtr && (bytePointer >= terminatorPtr) && (bytePointer < terminatorPtr + provTableImageSize))
+ lsw = provTableImage[bytePointer - terminatorPtr];
+ else
+ lsw = image[bytePointer];
+
+ if (terminatorPtr && ((bytePointer+1) >= terminatorPtr) && ((bytePointer+1) < terminatorPtr + provTableImageSize))
+ lsw |= provTableImage[bytePointer + 1 - terminatorPtr] << 8;
+ else
+ lsw |= image[bytePointer+1] << 8;
+
+ bytePointer += 2;
+
+ msw = image[bytePointer];
+ bytePointer += 1;
+
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE5, msw);
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE6, lsw);
+
+ /* no polling */
+ phy_write_mmd(gandload_phydev, MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE1, mailboxWrite);
+ break;
+ }
+
+ if (byteSize & 0x3)
+ {
+ /* update the calculated CRC */
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (msw & 0xFF)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw >> 8)];
+ crc16Calculated = ((crc16Calculated & 0xFF) << 8) ^ AQ_CRC16Table[(crc16Calculated >> 8) ^ (lsw & 0xFF)];
+ }
+
+ /*------------------------------------- Exit gangload mode -------------------------------------------------------*/
+ AQ_API_DisableGangLoadMode(gandload_phydev, recordedGGP1Values[0]);
+
+ /*------------------------------------- Check mailbox CRCs -------------------------------------------------------*/
+ /* check to make sure the mailbox CRC matches the calculated CRC */
+ for (j = 0; j < num_phydevs; j++) {
+ mailboxCRC = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_MAILBOX_INTERFACE2);
+ if (mailboxCRC != crc16Calculated)
+ {
+ phydev_err(phydevs[j], "%uth port: Mailbox CRC-16 (0x%X) does not match calculated CRC-16 (0x%X)\n",
+ j, mailboxCRC, crc16Calculated);
+ result[j] = -EIO;
+ }
+ else
+ {
+ phydev_info(phydevs[j], "%uth port: Image load good - mailbox CRC-16 matches (0x%X)\n",
+ j, mailboxCRC);
+ }
+ }
+
+ /*------------------------------------- Clear any resets ---------------------------------------------------------*/
+ for (j = 0; j < num_phydevs; j++) {
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1, 0x0000);
+ }
+
+ /*------------------------------------- Release the uP -----------------------------------------------------------*/
+ globalControl = 0x0000;
+ globalControl |= VEND1_CONTROL2_UP_RUNSTALL_OVERRIDE;
+ globalControl |= VEND1_CONTROL2_UP_RUNSTALL;
+ for (j = 0; j < num_phydevs; j++) {
+ globalControl &= ~VEND1_CONTROL2_UP_RESET;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_CONTROL2, globalControl);
+ globalControl |= VEND1_CONTROL2_UP_RESET;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_CONTROL2, globalControl);
+ }
+
+ /* Need to wait at least 100us. */
+ udelay(100);
+
+ globalControl &= ~VEND1_CONTROL2_UP_RESET;
+ globalControl &= ~VEND1_CONTROL2_UP_RUNSTALL;
+ /* For post-APPIA devices, always set the uP stall override bit to
+ * smooth over any packaging differences WRT the boot load pin. */
+ /* REGDOC: Assign to local representation of bitfield (HHD/APPIA/EUR/CAL/RHEA: 1E.C001.6) */
+ globalControl |= VEND1_CONTROL2_UP_RUNSTALL_OVERRIDE;
+
+ for (j = 0; j < num_phydevs; j++) {
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_CONTROL2, globalControl);
+ }
+
+ /* NOTE!!! We can't re-enable the daisy-chain here, as this will overwrite the IRAM and DRAM with the FLASH contents*/
+
+ /* If any of the ports was not bootloaded successfully, return AQ_RET_ERROR */
+ for (j = 0; j < num_phydevs; j++) {
+ if (result[j] != 0)
+ return -EIO;
+ }
+
+ /* All ports were bootloaded successfully. */
+ return 0;
+}
+
+static int AQ_API_WriteBootLoadImage(
+ struct phy_device **phydevs,
+ int num_phydevs,
+ struct phy_device *gandload_phydev,
+ int *result,
+ const uint8_t* data,
+ size_t size)
+{
+ unsigned int val;
+ int j;
+
+ for (j = 0; j < num_phydevs; j++) {
+ /* stall the uP */
+ val = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_CONTROL2);
+ val |= VEND1_CONTROL2_UP_RUNSTALL_OVERRIDE;
+ val |= VEND1_CONTROL2_UP_RUNSTALL;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_CONTROL2, val);
+
+ /* disable the S/W reset to the Global MMD registers */
+ val = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_RESET_CONTROL);
+ val |= VEND1_RESET_CONTROL_MMD_RESET_DISABLE;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_RESET_CONTROL, val);
+
+ /* de-assert Global S/W reset */
+ val = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1);
+ val &= ~VEND1_STD_CONTROL1_SOFT_RESET;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1, val);
+
+ /* assert Global S/W reset */
+ val = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1);
+ val |= VEND1_STD_CONTROL1_SOFT_RESET;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1, val);
+
+ /* de-assert Global S/W reset */
+ val = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1);
+ val &= ~VEND1_STD_CONTROL1_SOFT_RESET;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_STD_CONTROL1, val);
+
+ /* wait 100ms */
+ mdelay(100);
+
+ /* enable the S/W reset to the Global MMD registers */
+ val = phy_read_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_RESET_CONTROL);
+ val &= ~VEND1_RESET_CONTROL_MMD_RESET_DISABLE;
+ phy_write_mmd(phydevs[j], MDIO_MMD_VEND1, VEND1_RESET_CONTROL, val);
+ }
+
+ return AQ_API_WriteBootLoadImage_impl(phydevs, num_phydevs, gandload_phydev,
+ result, (const uint32_t *)&size, data,
+ NULL, NULL, 0);
+}
+
+static int aqr_firmware_check_heartbeat(struct phy_device *phydev)
+{
+ struct aqr107_priv *priv = phydev->priv;
+ int stopped = 0, ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_RSVD_STAT2);
+ if (ret < 0)
+ return ret;
+
+ /* heartbeat stopped if the current heartbeat is equal to the previous */
+ if (priv->heartbeat == ret)
+ stopped = 1;
+
+ /* update heartbeat to the private data */
+ priv->heartbeat = ret;
+
+ return stopped;
+}
+
+int aqr_firmware_heartbeat_thread(void *data)
+{
+ struct phy_device *phydev = data;
+ struct aqr107_priv *priv = phydev->priv;
+ struct device *dev;
+ int ret = 0;
+
+ dev = &phydev->mdio.dev;
+
+ for (;;) {
+ if (kthread_should_stop())
+ break;
+
+ if (priv->fw_initialized == true &&
+ aqr_firmware_check_heartbeat(phydev) == 1) {
+ dev_err(dev, "Detect heartbeat stopped, start to realod firmware...\n");
+ aqr_firmware_download_single(phydev, true);
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(2 * HZ);
+ }
+
+ return ret;
+}
+
+static void aqr_firmware_download_cb(const struct firmware *fw, void *context)
+{
+ struct phy_device **phydevs = context;
+ struct phy_device *gandload_phydev = phydevs[0];
+ struct device *dev = &phydevs[0]->mdio.dev;
+ struct aqr107_priv *priv = phydevs[0]->priv;
+ int result[MAX_GANGLOAD_DEVICES];
+ int i, num_phydevs = 0, ret = 0;
+
+ if (!fw)
+ return;
+
+ spin_lock(&gangload_lock);
+
+ num_phydevs = priv->fw_dl_mode == FW_DL_GNAGLOAD ?
+ gangload : 1;
+
+retry:
+ if (gandload_phydev->state == PHY_HALTED && priv->fw_initialized == true) {
+ dev_info(dev, "Detect PHY power down, stop to download firmware...\n");
+ goto out;
+ }
+
+ memset(result, 0, sizeof(result));
+
+ ret = AQ_API_WriteBootLoadImage(phydevs, num_phydevs, gandload_phydev,
+ result, fw->data, fw->size);
+ if (ret) {
+ for (i = 0; i < num_phydevs; i++) {
+ if (result[i] == 0)
+ continue;
+
+ dev = &phydevs[i]->mdio.dev;
+ dev_err(dev, "failed to download firmware %s, ret: %d\n",
+ AQR_FIRMWARE, ret);
+ goto retry;
+ }
+ }
+
+ /* wait firmware initialization completed */
+ mdelay(250);
+
+ for (i = 0; i < num_phydevs; i++) {
+ if (result[i] == 0) {
+ dev = &phydevs[i]->mdio.dev;
+ priv = phydevs[i]->priv;
+ priv->fw_initialized = true;
+
+ aqr107_chip_info(phydevs[i]);
+
+ aqr107_config_mdi(phydevs[i]);
+
+ aqr107_set_downshift(phydevs[i],
+ MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
+
+ if (phy_is_started(phydevs[i])) {
+ phydevs[i]->state = PHY_UP;
+ phy_queue_state_machine(phydevs[i], 0);
+ }
+ }
+ }
+
+out:
+ release_firmware(fw);
+
+ spin_unlock(&gangload_lock);
+}
+
+static int aqr_firmware_download_single(struct phy_device *phydev, bool force_reload)
+{
+ struct aqr107_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ const struct firmware *fw;
+ int ret = 0;
+
+ if (priv->fw_initialized == true && force_reload == false)
+ return 0;
+
+ if (priv->phydevs[0] != phydev) {
+ priv->phydevs[0] = phydev;
+ spin_lock_init(&priv->lock);
+ }
+ priv->fw_dl_mode = FW_DL_SINGLE;
+ priv->heartbeat = -1;
+
+ ret = request_firmware(&fw, AQR_FIRMWARE, dev);
+ if (ret) {
+ dev_err(dev, "failed to request firmware %s, ret: %d\n",
+ AQR_FIRMWARE, ret);
+ }
+
+ aqr_firmware_download_cb(fw, priv->phydevs);
+
+ return ret;
+}
+
+static int aqr_firmware_gandload_thread(void *data)
+{
+ struct phy_device **phydevs = data;
+ struct device *dev = &phydevs[0]->mdio.dev;
+ int ret = 0;
+
+ for (;;) {
+ if (kthread_should_stop())
+ break;
+
+ /* either maximum gangload phy devices or timeout is reached */
+ if (gangload == MAX_GANGLOAD_DEVICES ||
+ time_after(jiffies, gangload_timeout)) {
+ ret = request_firmware_nowait(THIS_MODULE, true, AQR_FIRMWARE, dev,
+ GFP_KERNEL, phydevs, aqr_firmware_download_cb);
+ if (ret) {
+ dev_err(dev, "failed to request firmware %s, ret: %d\n",
+ AQR_FIRMWARE, ret);
+ }
+ break;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ msleep(1);
+ }
+
+ return ret;
+}
+
+static int aqr_firmware_download_gang(struct phy_device *phydev)
+{
+ struct aqr107_priv *priv = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ int i;
+
+ if (priv->fw_initialized == true)
+ return 0;
+
+ if (!gangload_kthread) {
+ /* setup a maximum wait time limit for gangload mode */
+ gangload_timeout = jiffies + 5 * HZ;
+
+ /* create a thread for monitor gangload devices */
+ gangload_kthread = kthread_create(aqr_firmware_gandload_thread,
+ gangload_phydevs,
+ "aqr_firmware_gandload_thread");
+ if (IS_ERR(gangload_kthread)) {
+ dev_err(dev,
+ "failed to create aqr_firmware_gandload_thread(%ld)\n",
+ PTR_ERR(gangload_kthread));
+ return PTR_ERR(gangload_kthread);
+ }
+ }
+
+ for (i = 0; i < gangload; i++) {
+ if (gangload_phydevs[i] == phydev) {
+ dev_warn(dev, "Detect duplicate gangload phydev\n");
+ return 0;
+ }
+ }
+
+ /* fall back to single mode if timeout is reached */
+ if (time_after(jiffies, gangload_timeout))
+ return aqr_firmware_download_single(phydev, false);
+
+ if (priv->phydevs[0] != phydev) {
+ priv->phydevs[0] = phydev;
+ spin_lock_init(&priv->lock);
+ }
+ priv->fw_dl_mode = FW_DL_GNAGLOAD;
+ priv->heartbeat = -1;
+ gangload_phydevs[gangload] = phydev;
+ gangload++;
+
+ wake_up_process(gangload_kthread);
+
+ return 0;
+}
+
+int aqr_firmware_download(struct phy_device *phydev)
+{
+ int ret = 0;
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD_SINGLE
+ ret = aqr_firmware_download_single(phydev, false);
+#elif CONFIG_AQUANTIA_PHY_FW_DOWNLOAD_GANG
+ ret = aqr_firmware_download_gang(phydev);
+#endif
+ return ret;
+}
diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c
index e7495c9a7..36f1aac4f 100644
--- a/drivers/net/phy/aquantia_main.c
+++ b/drivers/net/phy/aquantia_main.c
@@ -8,10 +8,12 @@
*/
#include <linux/kernel.h>
+#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/bitfield.h>
#include <linux/phy.h>
+#include <linux/of.h>
#include "aquantia.h"
@@ -39,7 +41,6 @@
#define MDIO_AN_VEND_PROV_2500BASET_FULL BIT(10)
#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN BIT(4)
#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK GENMASK(3, 0)
-#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT 4
#define MDIO_AN_TX_VEND_STATUS1 0xc800
#define MDIO_AN_TX_VEND_STATUS1_RATE_MASK GENMASK(3, 1)
@@ -73,18 +74,6 @@
#define MDIO_AN_RX_VEND_STAT3 0xe832
#define MDIO_AN_RX_VEND_STAT3_AFR BIT(0)
-/* MDIO_MMD_C22EXT */
-#define MDIO_C22EXT_STAT_SGMII_RX_GOOD_FRAMES 0xd292
-#define MDIO_C22EXT_STAT_SGMII_RX_BAD_FRAMES 0xd294
-#define MDIO_C22EXT_STAT_SGMII_RX_FALSE_CARRIER 0xd297
-#define MDIO_C22EXT_STAT_SGMII_TX_GOOD_FRAMES 0xd313
-#define MDIO_C22EXT_STAT_SGMII_TX_BAD_FRAMES 0xd315
-#define MDIO_C22EXT_STAT_SGMII_TX_FALSE_CARRIER 0xd317
-#define MDIO_C22EXT_STAT_SGMII_TX_COLLISIONS 0xd318
-#define MDIO_C22EXT_STAT_SGMII_TX_LINE_COLLISIONS 0xd319
-#define MDIO_C22EXT_STAT_SGMII_TX_FRAME_ALIGN_ERR 0xd31a
-#define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b
-
/* Vendor specific 1, MDIO_MMD_VEND1 */
#define VEND1_GLOBAL_FW_ID 0x0020
#define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8)
@@ -124,31 +113,6 @@
#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL2 BIT(1)
#define VEND1_GLOBAL_INT_VEND_MASK_GLOBAL3 BIT(0)
-struct aqr107_hw_stat {
- const char *name;
- int reg;
- int size;
-};
-
-#define SGMII_STAT(n, r, s) { n, MDIO_C22EXT_STAT_SGMII_ ## r, s }
-static const struct aqr107_hw_stat aqr107_hw_stats[] = {
- SGMII_STAT("sgmii_rx_good_frames", RX_GOOD_FRAMES, 26),
- SGMII_STAT("sgmii_rx_bad_frames", RX_BAD_FRAMES, 26),
- SGMII_STAT("sgmii_rx_false_carrier_events", RX_FALSE_CARRIER, 8),
- SGMII_STAT("sgmii_tx_good_frames", TX_GOOD_FRAMES, 26),
- SGMII_STAT("sgmii_tx_bad_frames", TX_BAD_FRAMES, 26),
- SGMII_STAT("sgmii_tx_false_carrier_events", TX_FALSE_CARRIER, 8),
- SGMII_STAT("sgmii_tx_collisions", TX_COLLISIONS, 8),
- SGMII_STAT("sgmii_tx_line_collisions", TX_LINE_COLLISIONS, 8),
- SGMII_STAT("sgmii_tx_frame_alignment_err", TX_FRAME_ALIGN_ERR, 16),
- SGMII_STAT("sgmii_tx_runt_frames", TX_RUNT_FRAMES, 22),
-};
-#define AQR107_SGMII_STAT_SZ ARRAY_SIZE(aqr107_hw_stats)
-
-struct aqr107_priv {
- u64 sgmii_stats[AQR107_SGMII_STAT_SZ];
-};
-
static int aqr107_get_sset_count(struct phy_device *phydev)
{
return AQR107_SGMII_STAT_SZ;
@@ -398,7 +362,7 @@ static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
return 0;
}
-static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
+int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
{
int val = 0;
@@ -458,7 +422,7 @@ static int aqr107_wait_reset_complete(struct phy_device *phydev)
return val ? 0 : -ETIMEDOUT;
}
-static void aqr107_chip_info(struct phy_device *phydev)
+void aqr107_chip_info(struct phy_device *phydev)
{
u8 fw_major, fw_minor, build_id, prov_id;
int val;
@@ -481,6 +445,21 @@ static void aqr107_chip_info(struct phy_device *phydev)
fw_major, fw_minor, build_id, prov_id);
}
+int aqr107_config_mdi(struct phy_device *phydev)
+{
+ struct device_node *np = phydev->mdio.dev.of_node;
+ u16 val;
+
+ if (of_property_read_u16(np, "mdi-reversal", &val))
+ return -ENOENT;
+
+ if (!FIELD_FIT(PMAPMD_RSVD_VEND_PROV_MDI_CONF, val))
+ return -E2BIG;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV,
+ PMAPMD_RSVD_VEND_PROV_MDI_CONF, val);
+}
+
static int aqr107_config_init(struct phy_device *phydev)
{
int ret;
@@ -499,6 +478,12 @@ static int aqr107_config_init(struct phy_device *phydev)
ret = aqr107_wait_reset_complete(phydev);
if (!ret)
aqr107_chip_info(phydev);
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD
+ else
+ return aqr_firmware_download(phydev);
+#endif
+
+ aqr107_config_mdi(phydev);
return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
}
@@ -574,12 +559,43 @@ static void aqr107_link_change_notify(struct phy_device *phydev)
static int aqr107_suspend(struct phy_device *phydev)
{
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD
+ struct aqr107_priv *priv = phydev->priv;
+
+ if (spin_trylock(&priv->lock)) {
+ if (priv->heartbeat_thread) {
+ kthread_stop(priv->heartbeat_thread);
+ priv->heartbeat_thread = NULL;
+ priv->heartbeat = -1;
+ }
+ spin_unlock(&priv->lock);
+ }
+#endif
return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
MDIO_CTRL1_LPOWER);
}
static int aqr107_resume(struct phy_device *phydev)
{
+#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD
+ struct aqr107_priv *priv = phydev->priv;
+
+ if (spin_trylock(&priv->lock)) {
+ if (!priv->heartbeat_thread) {
+ priv->heartbeat_thread = kthread_create(aqr_firmware_heartbeat_thread,
+ phydev,
+ "aqr_firmware_heartbeat_thread");
+ if (IS_ERR(priv->heartbeat_thread)) {
+ phydev_err(phydev,
+ "Failed to create aqr_firmware_heartbeat_thread(%ld)\n",
+ PTR_ERR(priv->heartbeat_thread));
+ priv->heartbeat_thread = NULL;
+ } else
+ wake_up_process(priv->heartbeat_thread);
+ }
+ spin_unlock(&priv->lock);
+ }
+#endif
return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, MDIO_CTRL1,
MDIO_CTRL1_LPOWER);
}
--
2.34.1