diff options
-rw-r--r-- | drivers/net/phy/Kconfig | 24 | ||||
-rw-r--r-- | drivers/net/phy/aquantia.c | 234 |
2 files changed, 256 insertions, 2 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 28f3ee35f13..3dc0822d9c2 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -64,11 +64,33 @@ endif # MV88E61XX_SWITCH config PHYLIB_10G bool "Generic 10G PHY support" -config PHY_AQUANTIA +menuconfig PHY_AQUANTIA bool "Aquantia Ethernet PHYs support" select PHY_GIGE select PHYLIB_10G +config PHY_AQUANTIA_UPLOAD_FW + bool "Aquantia firmware loading support" + default n + depends on PHY_AQUANTIA + help + Aquantia PHYs use firmware which can be either loaded automatically + from storage directly attached to the phy or loaded by the boot loader + via MDIO commands. The firmware is loaded from a file, specified by + the PHY_AQUANTIA_FW_PART and PHY_AQUANTIA_FW_NAME options. + +config PHY_AQUANTIA_FW_PART + string "Aquantia firmware partition" + depends on PHY_AQUANTIA_UPLOAD_FW + help + Partition containing the firmware file. + +config PHY_AQUANTIA_FW_NAME + string "Aquantia firmware filename" + depends on PHY_AQUANTIA_UPLOAD_FW + help + Firmware filename. + config PHY_ATHEROS bool "Atheros Ethernet PHYs support" diff --git a/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c index a44fba0f1a2..cbd70120d19 100644 --- a/drivers/net/phy/aquantia.c +++ b/drivers/net/phy/aquantia.c @@ -8,6 +8,10 @@ #include <common.h> #include <dm.h> #include <phy.h> +#include <crc.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <fs.h> #define AQUNTIA_10G_CTL 0x20 #define AQUNTIA_VENDOR_P1 0xc400 @@ -15,9 +19,237 @@ #define AQUNTIA_SPEED_LSB_MASK 0x2000 #define AQUNTIA_SPEED_MSB_MASK 0x40 +/* registers in MDIO_MMD_VEND1 region */ +#define GLOBAL_FIRMWARE_ID 0x20 +#define GLOBAL_FAULT 0xc850 +#define GLOBAL_RSTATUS_1 0xc885 + +#define GLOBAL_STANDARD_CONTROL 0x0 +#define SOFT_RESET BIT(15) +#define LOW_POWER BIT(11) + +#define MAILBOX_CONTROL 0x0200 +#define MAILBOX_EXECUTE BIT(15) +#define MAILBOX_WRITE BIT(14) +#define MAILBOX_RESET_CRC BIT(12) +#define MAILBOX_BUSY BIT(8) + +#define MAILBOX_CRC 0x0201 + +#define MAILBOX_ADDR_MSW 0x0202 +#define MAILBOX_ADDR_LSW 0x0203 + +#define MAILBOX_DATA_MSW 0x0204 +#define MAILBOX_DATA_LSW 0x0205 + +#define UP_CONTROL 0xc001 +#define UP_RESET BIT(15) +#define UP_RUN_STALL_OVERRIDE BIT(6) +#define UP_RUN_STALL BIT(0) + +/* addresses of memory segments in the phy */ +#define DRAM_BASE_ADDR 0x3FFE0000 +#define IRAM_BASE_ADDR 0x40000000 + +/* firmware image format constants */ +#define VERSION_STRING_SIZE 0x40 +#define VERSION_STRING_OFFSET 0x0200 +#define HEADER_OFFSET 0x300 + +#pragma pack(1) +struct fw_header { + u8 padding[4]; + u8 iram_offset[3]; + u8 iram_size[3]; + u8 dram_offset[3]; + u8 dram_size[3]; +}; + +#pragma pack() + +#if defined(CONFIG_PHY_AQUANTIA_UPLOAD_FW) +static int aquantia_read_fw(u8 **fw_addr, size_t *fw_length) +{ + loff_t length, read; + int ret; + void *addr = NULL; + + *fw_addr = NULL; + *fw_length = 0; + debug("Loading Acquantia microcode from %s %s\n", + CONFIG_PHY_AQUANTIA_FW_PART, CONFIG_PHY_AQUANTIA_FW_NAME); + ret = fs_set_blk_dev("mmc", CONFIG_PHY_AQUANTIA_FW_PART, FS_TYPE_ANY); + if (ret < 0) + goto cleanup; + + ret = fs_size(CONFIG_PHY_AQUANTIA_FW_NAME, &length); + if (ret < 0) + goto cleanup; + + addr = malloc(length); + if (!addr) { + ret = -ENOMEM; + goto cleanup; + } + + ret = fs_set_blk_dev("mmc", CONFIG_PHY_AQUANTIA_FW_PART, FS_TYPE_ANY); + if (ret < 0) + goto cleanup; + + ret = fs_read(CONFIG_PHY_AQUANTIA_FW_NAME, (ulong)addr, 0, length, + &read); + if (ret < 0) + goto cleanup; + + *fw_addr = addr; + *fw_length = length; + debug("Found Acquantia microcode.\n"); + +cleanup: + if (ret < 0) { + printf("loading firmware file %s %s failed with error %d\n", + CONFIG_PHY_AQUANTIA_FW_PART, + CONFIG_PHY_AQUANTIA_FW_NAME, ret); + free(addr); + } + return ret; +} + +/* load data into the phy's memory */ +static int aquantia_load_memory(struct phy_device *phydev, u32 addr, + const u8 *data, size_t len) +{ + size_t pos; + u16 crc = 0, up_crc; + + phy_write(phydev, MDIO_MMD_VEND1, MAILBOX_CONTROL, MAILBOX_RESET_CRC); + phy_write(phydev, MDIO_MMD_VEND1, MAILBOX_ADDR_MSW, addr >> 16); + phy_write(phydev, MDIO_MMD_VEND1, MAILBOX_ADDR_LSW, addr & 0xfffc); + + for (pos = 0; pos < len; pos += min(sizeof(u32), len - pos)) { + u32 word = 0; + + memcpy(&word, &data[pos], min(sizeof(u32), len - pos)); + + phy_write(phydev, MDIO_MMD_VEND1, MAILBOX_DATA_MSW, + (word >> 16)); + phy_write(phydev, MDIO_MMD_VEND1, MAILBOX_DATA_LSW, + word & 0xffff); + + phy_write(phydev, MDIO_MMD_VEND1, MAILBOX_CONTROL, + MAILBOX_EXECUTE | MAILBOX_WRITE); + + /* keep a big endian CRC to match the phy processor */ + word = cpu_to_be32(word); + crc = crc16_ccitt(crc, (u8 *)&word, sizeof(word)); + } + + up_crc = phy_read(phydev, MDIO_MMD_VEND1, MAILBOX_CRC); + if (crc != up_crc) { + printf("%s crc mismatch: calculated 0x%04hx phy 0x%04hx\n", + phydev->dev->name, crc, up_crc); + return -EINVAL; + } + return 0; +} + +static u32 unpack_u24(const u8 *data) +{ + return (data[2] << 16) + (data[1] << 8) + data[0]; +} + +static int aquantia_upload_firmware(struct phy_device *phydev) +{ + int ret; + u8 *addr = NULL; + size_t fw_length = 0; + u16 calculated_crc, read_crc; + char version[VERSION_STRING_SIZE]; + u32 primary_offset, iram_offset, iram_size, dram_offset, dram_size; + const struct fw_header *header; + + ret = aquantia_read_fw(&addr, &fw_length); + if (ret != 0) + return ret; + + read_crc = (addr[fw_length - 2] << 8) | addr[fw_length - 1]; + calculated_crc = crc16_ccitt(0, addr, fw_length - 2); + if (read_crc != calculated_crc) { + printf("%s bad firmware crc: file 0x%04x calculated 0x%04x\n", + phydev->dev->name, read_crc, calculated_crc); + ret = -EINVAL; + goto done; + } + + /* Find the DRAM and IRAM sections within the firmware file. */ + primary_offset = ((addr[9] & 0xf) << 8 | addr[8]) << 12; + + header = (struct fw_header *)&addr[primary_offset + HEADER_OFFSET]; + + iram_offset = primary_offset + unpack_u24(header->iram_offset); + iram_size = unpack_u24(header->iram_size); + + dram_offset = primary_offset + unpack_u24(header->dram_offset); + dram_size = unpack_u24(header->dram_size); + + debug("primary %d iram offset=%d size=%d dram offset=%d size=%d\n", + primary_offset, iram_offset, iram_size, dram_offset, dram_size); + + strlcpy(version, (char *)&addr[dram_offset + VERSION_STRING_OFFSET], + VERSION_STRING_SIZE); + printf("%s loading firmare version '%s'\n", phydev->dev->name, version); + + /* stall the microcprocessor */ + phy_write(phydev, MDIO_MMD_VEND1, UP_CONTROL, + UP_RUN_STALL | UP_RUN_STALL_OVERRIDE); + + debug("loading dram 0x%08x from offset=%d size=%d\n", + DRAM_BASE_ADDR, dram_offset, dram_size); + ret = aquantia_load_memory(phydev, DRAM_BASE_ADDR, &addr[dram_offset], + dram_size); + if (ret != 0) + goto done; + + debug("loading iram 0x%08x from offset=%d size=%d\n", + IRAM_BASE_ADDR, iram_offset, iram_size); + ret = aquantia_load_memory(phydev, IRAM_BASE_ADDR, &addr[iram_offset], + iram_size); + if (ret != 0) + goto done; + + /* make sure soft reset and low power mode are clear */ + phy_write(phydev, MDIO_MMD_VEND1, GLOBAL_STANDARD_CONTROL, 0); + + /* Release the microprocessor. UP_RESET must be held for 100 usec. */ + phy_write(phydev, MDIO_MMD_VEND1, UP_CONTROL, + UP_RUN_STALL | UP_RUN_STALL_OVERRIDE | UP_RESET); + + udelay(100); + + phy_write(phydev, MDIO_MMD_VEND1, UP_CONTROL, UP_RUN_STALL_OVERRIDE); + + printf("%s firmare loading done.\n", phydev->dev->name); +done: + free(addr); + return ret; +} +#else +static int aquantia_upload_firmware(struct phy_device *phydev) +{ + return 0; +} +#endif + int aquantia_config(struct phy_device *phydev) { - u32 val = phy_read(phydev, MDIO_MMD_PMAPMD, MII_BMCR); + u32 val; + int ret; + + ret = aquantia_upload_firmware(phydev); + if (ret != 0) + return ret; + + val = phy_read(phydev, MDIO_MMD_PMAPMD, MII_BMCR); if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { /* 1000BASE-T mode */ |