| diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig |
| index 7b49c94..5a79af2 100644 |
| --- a/drivers/net/phy/Kconfig |
| +++ b/drivers/net/phy/Kconfig |
| @@ -372,7 +372,56 @@ 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_MDI_SWAP |
| + tristate "MDI Swap Enable" |
| + depends on AQUANTIA_PHY |
| + ---help--- |
| + Currently supports the Aquantia AQR113C |
| + |
| +choice |
| + prompt "Swap mode" |
| + default AQUANTIA_PHY_MDI_REVERSED |
| + depends on AQUANTIA_PHY_MDI_SWAP |
| + |
| + config AQUANTIA_PHY_MDI_NORMAL |
| + bool "Normal" |
| + |
| + config AQUANTIA_PHY_MDI_REVERSED |
| + bool "Reversed" |
| +endchoice |
| + |
| +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 043a697..4f67110 100644 |
| --- a/drivers/net/phy/Makefile |
| +++ b/drivers/net/phy/Makefile |
| @@ -67,5 +67,8 @@ 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_AQUANTIA_PHY) += aquantia.o |
| diff --git a/drivers/net/phy/aquantia.h b/drivers/net/phy/aquantia.h |
| index 5a16caa..912bbe6 100644 |
| --- a/drivers/net/phy/aquantia.h |
| +++ b/drivers/net/phy/aquantia.h |
| @@ -9,8 +9,66 @@ |
| #include <linux/device.h> |
| #include <linux/phy.h> |
| |
| +#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; |
| + bool fw_initialized; |
| + int fw_dl_mode; |
| + u16 heartbeat; |
| +#endif |
| +}; |
| + |
| +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 { |
| + FW_DL_SINGLE = 0, |
| + FW_DL_GNAGLOAD, |
| +}; |
| + |
| +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 0000000..7aeec86 |
| --- /dev/null |
| +++ b/drivers/net/phy/aquantia_firmware.c |
| @@ -0,0 +1,1096 @@ |
| +// 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 int gangload = 0; |
| + |
| +static int aqr_firmware_download_single(struct phy_device *phydev); |
| + |
| +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; |
| + |
| + if (priv->heartbeat == ret) |
| + stopped = 1; |
| + |
| + priv->heartbeat = ret; |
| + |
| + return stopped; |
| +} |
| + |
| +static 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 (phydev->state != PHY_HALTED && |
| + aqr_firmware_check_heartbeat(phydev) == 1) { |
| + dev_err(dev, "Detect heartbeat stopped, start to realod firmware...\n"); |
| + priv->fw_initialized = false; |
| + aqr_firmware_download_single(phydev); |
| + } |
| + |
| + 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; |
| + struct aqr107_priv *priv = phydevs[0]->priv; |
| + int result[MAX_GANGLOAD_DEVICES]; |
| + int i, num_phydevs = 0, ret = 0; |
| + |
| + if (!fw) |
| + return; |
| + |
| + num_phydevs = priv->fw_dl_mode == FW_DL_GNAGLOAD ? |
| + MAX_GANGLOAD_DEVICES : 1; |
| + |
| +retry: |
| + if (gandload_phydev->state == PHY_HALTED) { |
| + dev = &phydevs[0]->mdio.dev; |
| + dev_info(dev, "Detect PHY power down, stop to reload 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) { |
| + priv = phydevs[i]->priv; |
| + priv->fw_initialized = true; |
| + |
| +#ifdef CONFIG_AQUANTIA_PHY_MDI_SWAP |
| + aqr107_config_mdi(phydevs[i]); |
| +#endif |
| + |
| + if (priv->fw_dl_mode == FW_DL_GNAGLOAD) { |
| + phydevs[i]->state = PHY_UP; |
| + phy_queue_state_machine(phydevs[i], 0); |
| + } |
| + |
| + /* create a thread for monitor heartbeat status */ |
| + if (!priv->heartbeat_thread) { |
| + priv->heartbeat_thread = kthread_create(aqr_firmware_heartbeat_thread, |
| + phydevs[i], |
| + "aqr_firmware_heartbeat_thread"); |
| + if (IS_ERR(priv->heartbeat_thread)) { |
| + dev_err(dev, |
| + "%s Failed to create thread for aqr_firmware_heartbeat_thread\n", |
| + __func__); |
| + } |
| + wake_up_process(priv->heartbeat_thread); |
| + } |
| + } |
| + } |
| +out: |
| + release_firmware(fw); |
| +} |
| + |
| +static int aqr_firmware_download_single(struct phy_device *phydev) |
| +{ |
| + struct aqr107_priv *priv = phydev->priv; |
| + struct device *dev = &phydev->mdio.dev; |
| + const struct firmware *fw; |
| + int ret = 0; |
| + |
| + if (priv->fw_initialized == true) |
| + return 0; |
| + |
| + priv->fw_dl_mode = FW_DL_SINGLE; |
| + priv->phydevs[0] = phydev; |
| + priv->heartbeat = -1; |
| + |
| + ret = request_firmware(&fw, AQR_FIRMWARE, dev); |
| + if (ret) { |
| + dev_err(dev, "failed to load 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; |
| + |
| + /* reach maximum gangload phy devices */ |
| + if (gangload == MAX_GANGLOAD_DEVICES) { |
| + ret = request_firmware_nowait(THIS_MODULE, true, AQR_FIRMWARE, dev, |
| + GFP_KERNEL, phydevs, aqr_firmware_download_cb); |
| + if (ret) { |
| + dev_err(dev, "failed to load 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) { |
| + /* 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, |
| + "%s Failed to create thread for aqr_firmware_gandload_thread\n", |
| + __func__); |
| + return PTR_ERR(gangload_kthread); |
| + } |
| + wake_up_process(gangload_kthread); |
| + } |
| + |
| + for (i = 0; i < gangload; i++) { |
| + if (gangload_phydevs[i] == phydev) { |
| + dev_err(dev, "Detect duplicate gangload phydev\n"); |
| + return -EINVAL; |
| + } |
| + } |
| + |
| + priv->fw_dl_mode = FW_DL_GNAGLOAD; |
| + priv->heartbeat = -1; |
| + gangload_phydevs[gangload] = phydev; |
| + gangload++; |
| + |
| + 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); |
| +#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 ac8dd8e..421cdd3 100644 |
| --- a/drivers/net/phy/aquantia_main.c |
| +++ b/drivers/net/phy/aquantia_main.c |
| @@ -8,6 +8,7 @@ |
| */ |
| |
| #include <linux/kernel.h> |
| +#include <linux/kthread.h> |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/bitfield.h> |
| @@ -73,18 +73,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 +112,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; |
| @@ -498,6 +461,17 @@ 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) |
| +{ |
| +#ifdef CONFIG_AQUANTIA_PHY_MDI_REVERSED |
| + return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV, |
| + PMAPMD_RSVD_VEND_PROV_MDI_CONF, 1); |
| +#else |
| + return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, PMAPMD_RSVD_VEND_PROV, |
| + PMAPMD_RSVD_VEND_PROV_MDI_CONF, 0); |
| +#endif |
| +} |
| + |
| static int aqr107_config_init(struct phy_device *phydev) |
| { |
| int ret; |
| @@ -517,6 +491,14 @@ static int aqr107_config_init(struct phy_device *phydev) |
| if (!ret) |
| aqr107_chip_info(phydev); |
| |
| +#if !defined(CONFIG_AQUANTIA_PHY_FW_DOWNLOAD) && defined(CONFIG_AQUANTIA_PHY_MDI_SWAP) |
| + aqr107_config_mdi(phydev); |
| +#endif |
| + |
| +#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD |
| + aqr_firmware_download(phydev); |
| +#endif |
| + |
| return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT); |
| } |
| |
| @@ -605,6 +609,15 @@ static int aqr107_resume(struct phy_device *phydev) |
| MDIO_CTRL1_LPOWER); |
| } |
| |
| +static void aqr107_remove(struct phy_device *phydev) |
| +{ |
| +#ifdef CONFIG_AQUANTIA_PHY_FW_DOWNLOAD |
| + struct aqr107_priv *priv = phydev->priv; |
| + |
| + kthread_stop(priv->heartbeat_thread); |
| +#endif |
| +} |
| + |
| static int aqr107_probe(struct phy_device *phydev) |
| { |
| phydev->priv = devm_kzalloc(&phydev->mdio.dev, |
| @@ -711,6 +737,7 @@ static struct phy_driver aqr_driver[] = { |
| .get_strings = aqr107_get_strings, |
| .get_stats = aqr107_get_stats, |
| .link_change_notify = aqr107_link_change_notify, |
| + .remove = aqr107_remove, |
| }, |
| }; |
| |