/** * aQuantia Corporation Network Driver * Copyright (C) 2014-2017 aQuantia Corporation. All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * (1) Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * (2) Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * (3) The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @file aq_fw2x.c * Firmware v2.x specific functions. * @date 2017.12.11 @author roman.agafonov@aquantia.com */ #include __FBSDID("$FreeBSD$"); #include #include "aq_common.h" #include "aq_hw.h" #include "aq_hw_llh.h" #include "aq_hw_llh_internal.h" #include "aq_fw.h" #include "aq_dbg.h" typedef enum { CAPS_LO_10BASET_HD = 0x00, CAPS_LO_10BASET_FD, CAPS_LO_100BASETX_HD, CAPS_LO_100BASET4_HD, CAPS_LO_100BASET2_HD, CAPS_LO_100BASETX_FD, CAPS_LO_100BASET2_FD, CAPS_LO_1000BASET_HD, CAPS_LO_1000BASET_FD, CAPS_LO_2P5GBASET_FD, CAPS_LO_5GBASET_FD, CAPS_LO_10GBASET_FD, } fw2x_caps_lo; typedef enum { CAPS_HI_RESERVED1 = 0x00, CAPS_HI_10BASET_EEE, CAPS_HI_RESERVED2, CAPS_HI_PAUSE, CAPS_HI_ASYMMETRIC_PAUSE, CAPS_HI_100BASETX_EEE, CAPS_HI_RESERVED3, CAPS_HI_RESERVED4, CAPS_HI_1000BASET_FD_EEE, CAPS_HI_2P5GBASET_FD_EEE, CAPS_HI_5GBASET_FD_EEE, CAPS_HI_10GBASET_FD_EEE, CAPS_HI_RESERVED5, CAPS_HI_RESERVED6, CAPS_HI_RESERVED7, CAPS_HI_RESERVED8, CAPS_HI_RESERVED9, CAPS_HI_CABLE_DIAG, CAPS_HI_TEMPERATURE, CAPS_HI_DOWNSHIFT, CAPS_HI_PTP_AVB_EN, CAPS_HI_MEDIA_DETECT, CAPS_HI_LINK_DROP, CAPS_HI_SLEEP_PROXY, CAPS_HI_WOL, CAPS_HI_MAC_STOP, CAPS_HI_EXT_LOOPBACK, CAPS_HI_INT_LOOPBACK, CAPS_HI_EFUSE_AGENT, CAPS_HI_WOL_TIMER, CAPS_HI_STATISTICS, CAPS_HI_TRANSACTION_ID, } fw2x_caps_hi; typedef enum aq_fw2x_rate { FW2X_RATE_100M = 0x20, FW2X_RATE_1G = 0x100, FW2X_RATE_2G5 = 0x200, FW2X_RATE_5G = 0x400, FW2X_RATE_10G = 0x800, } aq_fw2x_rate; typedef struct fw2x_msm_statistics { uint32_t uprc; uint32_t mprc; uint32_t bprc; uint32_t erpt; uint32_t uptc; uint32_t mptc; uint32_t bptc; uint32_t erpr; uint32_t mbtc; uint32_t bbtc; uint32_t mbrc; uint32_t bbrc; uint32_t ubrc; uint32_t ubtc; uint32_t ptc; uint32_t prc; } fw2x_msm_statistics; typedef struct fw2x_phy_cable_diag_data { uint32_t lane_data[4]; } fw2x_phy_cable_diag_data; typedef struct fw2x_capabilities { uint32_t caps_lo; uint32_t caps_hi; } fw2x_capabilities; typedef struct fw2x_mailbox // struct fwHostInterface { uint32_t version; uint32_t transaction_id; int32_t error; fw2x_msm_statistics msm; // msmStatistics_t msm; uint16_t phy_h_bit; uint16_t phy_fault_code; int16_t phy_temperature; uint8_t cable_len; uint8_t reserved1; fw2x_phy_cable_diag_data diag_data; uint32_t reserved[8]; fw2x_capabilities caps; /* ... */ } fw2x_mailbox; // EEE caps #define FW2X_FW_CAP_EEE_100M (1ULL << (32 + CAPS_HI_100BASETX_EEE)) #define FW2X_FW_CAP_EEE_1G (1ULL << (32 + CAPS_HI_1000BASET_FD_EEE)) #define FW2X_FW_CAP_EEE_2G5 (1ULL << (32 + CAPS_HI_2P5GBASET_FD_EEE)) #define FW2X_FW_CAP_EEE_5G (1ULL << (32 + CAPS_HI_5GBASET_FD_EEE)) #define FW2X_FW_CAP_EEE_10G (1ULL << (32 + CAPS_HI_10GBASET_FD_EEE)) // Flow Control #define FW2X_FW_CAP_PAUSE (1ULL << (32 + CAPS_HI_PAUSE)) #define FW2X_FW_CAP_ASYM_PAUSE (1ULL << (32 + CAPS_HI_ASYMMETRIC_PAUSE)) // Link Drop #define FW2X_CAP_LINK_DROP (1ull << (32 + CAPS_HI_LINK_DROP)) // MSM Statistics #define FW2X_CAP_STATISTICS (1ull << (32 + CAPS_HI_STATISTICS)) #define FW2X_RATE_MASK (FW2X_RATE_100M | FW2X_RATE_1G | FW2X_RATE_2G5 | FW2X_RATE_5G | FW2X_RATE_10G) #define FW2X_EEE_MASK (FW2X_FW_CAP_EEE_100M | FW2X_FW_CAP_EEE_1G | FW2X_FW_CAP_EEE_2G5 | FW2X_FW_CAP_EEE_5G | FW2X_FW_CAP_EEE_10G) #define FW2X_MPI_LED_ADDR 0x31c #define FW2X_MPI_CONTROL_ADDR 0x368 #define FW2X_MPI_STATE_ADDR 0x370 #define FW2X_FW_MIN_VER_LED 0x03010026U #define FW2X_LED_BLINK 0x2U #define FW2X_LED_DEFAULT 0x0U // Firmware v2-3.x specific functions. int fw2x_reset(struct aq_hw* hw); int fw2x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state_e mode, aq_fw_link_speed_t speed); int fw2x_get_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state_e* mode, aq_fw_link_speed_t* speed, aq_fw_link_fc_t* fc); int fw2x_get_mac_addr(struct aq_hw* hw, uint8_t* mac); int fw2x_get_stats(struct aq_hw* hw, struct aq_hw_stats_s* stats); static uint64_t read64_(struct aq_hw* hw, uint32_t addr) { uint64_t lo = AQ_READ_REG(hw, addr); uint64_t hi = AQ_READ_REG(hw, addr + 4); return (lo | (hi << 32)); } static uint64_t get_mpi_ctrl_(struct aq_hw* hw) { return read64_(hw, FW2X_MPI_CONTROL_ADDR); } static uint64_t get_mpi_state_(struct aq_hw* hw) { return read64_(hw, FW2X_MPI_STATE_ADDR); } static void set_mpi_ctrl_(struct aq_hw* hw, uint64_t value) { AQ_WRITE_REG(hw, FW2X_MPI_CONTROL_ADDR, (uint32_t)value); AQ_WRITE_REG(hw, FW2X_MPI_CONTROL_ADDR + 4, (uint32_t)(value >> 32)); } int fw2x_reset(struct aq_hw* hw) { fw2x_capabilities caps = {0}; AQ_DBG_ENTER(); int err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr + offsetof(fw2x_mailbox, caps), (uint32_t*)&caps, sizeof caps/sizeof(uint32_t)); if (err == EOK) { hw->fw_caps = caps.caps_lo | ((uint64_t)caps.caps_hi << 32); trace(dbg_init, "fw2x> F/W capabilities mask = %llx", (unsigned long long)hw->fw_caps); } else { trace_error(dbg_init, "fw2x> can't get F/W capabilities mask, error %d", err); } AQ_DBG_EXIT(EOK); return (EOK); } static aq_fw2x_rate link_speed_mask_to_fw2x_(uint32_t speed) { uint32_t rate = 0; AQ_DBG_ENTER(); if (speed & aq_fw_10G) rate |= FW2X_RATE_10G; if (speed & aq_fw_5G) rate |= FW2X_RATE_5G; if (speed & aq_fw_2G5) rate |= FW2X_RATE_2G5; if (speed & aq_fw_1G) rate |= FW2X_RATE_1G; if (speed & aq_fw_100M) rate |= FW2X_RATE_100M; AQ_DBG_EXIT(rate); return ((aq_fw2x_rate)rate); } int fw2x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state_e mode, aq_fw_link_speed_t speed) { uint64_t mpi_ctrl = get_mpi_ctrl_(hw); AQ_DBG_ENTERA("speed=%d", speed); switch (mode) { case MPI_INIT: mpi_ctrl &= ~FW2X_RATE_MASK; mpi_ctrl |= link_speed_mask_to_fw2x_(speed); mpi_ctrl &= ~FW2X_CAP_LINK_DROP; #if 0 // #todo #flowcontrol #pause #eee if (pHal->pCfg->eee) mpi_ctrl |= FW2X_EEE_MASK; #endif if (hw->fc.fc_rx) mpi_ctrl |= FW2X_FW_CAP_PAUSE; if (hw->fc.fc_tx) mpi_ctrl |= FW2X_FW_CAP_ASYM_PAUSE; break; case MPI_DEINIT: mpi_ctrl &= ~(FW2X_RATE_MASK | FW2X_EEE_MASK); mpi_ctrl &= ~(FW2X_FW_CAP_PAUSE | FW2X_FW_CAP_ASYM_PAUSE); break; default: trace_error(dbg_init, "fw2x> unknown MPI state %d", mode); return (-EINVAL); } set_mpi_ctrl_(hw, mpi_ctrl); AQ_DBG_EXIT(EOK); return (EOK); } int fw2x_get_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state_e* mode, aq_fw_link_speed_t* link_speed, aq_fw_link_fc_t* fc) { uint64_t mpi_state = get_mpi_state_(hw); uint32_t rates = mpi_state & FW2X_RATE_MASK; // AQ_DBG_ENTER(); if (mode) { uint64_t mpi_ctrl = get_mpi_ctrl_(hw); if (mpi_ctrl & FW2X_RATE_MASK) *mode = MPI_INIT; else *mode = MPI_DEINIT; } aq_fw_link_speed_t speed = aq_fw_none; if (rates & FW2X_RATE_10G) speed = aq_fw_10G; else if (rates & FW2X_RATE_5G) speed = aq_fw_5G; else if (rates & FW2X_RATE_2G5) speed = aq_fw_2G5; else if (rates & FW2X_RATE_1G) speed = aq_fw_1G; else if (rates & FW2X_RATE_100M) speed = aq_fw_100M; if (link_speed) *link_speed = speed; *fc = (mpi_state & (FW2X_FW_CAP_PAUSE | FW2X_FW_CAP_ASYM_PAUSE)) >> (32 + CAPS_HI_PAUSE); // AQ_DBG_EXIT(0); return (EOK); } int fw2x_get_mac_addr(struct aq_hw* hw, uint8_t* mac) { int err = -EFAULT; uint32_t mac_addr[2]; AQ_DBG_ENTER(); uint32_t efuse_shadow_addr = AQ_READ_REG(hw, 0x364); if (efuse_shadow_addr == 0) { trace_error(dbg_init, "couldn't read eFUSE Shadow Address"); AQ_DBG_EXIT(-EFAULT); return (-EFAULT); } err = aq_hw_fw_downld_dwords(hw, efuse_shadow_addr + (40 * 4), mac_addr, ARRAY_SIZE(mac_addr)); if (err < 0) { mac_addr[0] = 0; mac_addr[1] = 0; AQ_DBG_EXIT(err); return (err); } mac_addr[0] = bswap32(mac_addr[0]); mac_addr[1] = bswap32(mac_addr[1]); memcpy(mac, (uint8_t*)mac_addr, ETHER_ADDR_LEN); AQ_DBG_EXIT(EOK); return (EOK); } static inline void fw2x_stats_to_fw_stats_(struct aq_hw_stats_s* dst, const fw2x_msm_statistics* src) { dst->uprc = src->uprc; dst->mprc = src->mprc; dst->bprc = src->bprc; dst->erpt = src->erpt; dst->uptc = src->uptc; dst->mptc = src->mptc; dst->bptc = src->bptc; dst->erpr = src->erpr; dst->mbtc = src->mbtc; dst->bbtc = src->bbtc; dst->mbrc = src->mbrc; dst->bbrc = src->bbrc; dst->ubrc = src->ubrc; dst->ubtc = src->ubtc; dst->ptc = src->ptc; dst->prc = src->prc; } static bool toggle_mpi_ctrl_and_wait_(struct aq_hw* hw, uint64_t mask, uint32_t timeout_ms, uint32_t try_count) { uint64_t ctrl = get_mpi_ctrl_(hw); uint64_t state = get_mpi_state_(hw); // AQ_DBG_ENTER(); // First, check that control and state values are consistent if ((ctrl & mask) != (state & mask)) { trace_warn(dbg_fw, "fw2x> MPI control (%#llx) and state (%#llx) are not consistent for mask %#llx!", (unsigned long long)ctrl, (unsigned long long)state, (unsigned long long)mask); AQ_DBG_EXIT(false); return (false); } // Invert bits (toggle) in control register ctrl ^= mask; set_mpi_ctrl_(hw, ctrl); // Clear all bits except masked ctrl &= mask; // Wait for FW reflecting change in state register while (try_count-- != 0) { if ((get_mpi_state_(hw) & mask) == ctrl) { // AQ_DBG_EXIT(true); return (true); } msec_delay(timeout_ms); } trace_detail(dbg_fw, "f/w2x> timeout while waiting for response in state register for bit %#llx!", (unsigned long long)mask); // AQ_DBG_EXIT(false); return (false); } int fw2x_get_stats(struct aq_hw* hw, struct aq_hw_stats_s* stats) { int err = 0; fw2x_msm_statistics fw2x_stats = {0}; // AQ_DBG_ENTER(); if ((hw->fw_caps & FW2X_CAP_STATISTICS) == 0) { trace_warn(dbg_fw, "fw2x> statistics not supported by F/W"); return (-ENOTSUP); } // Tell F/W to update the statistics. if (!toggle_mpi_ctrl_and_wait_(hw, FW2X_CAP_STATISTICS, 1, 25)) { trace_error(dbg_fw, "fw2x> statistics update timeout"); AQ_DBG_EXIT(-ETIME); return (-ETIME); } err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr + offsetof(fw2x_mailbox, msm), (uint32_t*)&fw2x_stats, sizeof fw2x_stats/sizeof(uint32_t)); fw2x_stats_to_fw_stats_(stats, &fw2x_stats); if (err != EOK) trace_error(dbg_fw, "fw2x> download statistics data FAILED, error %d", err); // AQ_DBG_EXIT(err); return (err); } static int fw2x_led_control(struct aq_hw* hw, uint32_t onoff) { int err = 0; AQ_DBG_ENTER(); aq_hw_fw_version ver_expected = { .raw = FW2X_FW_MIN_VER_LED}; if (aq_hw_ver_match(&ver_expected, &hw->fw_version)) AQ_WRITE_REG(hw, FW2X_MPI_LED_ADDR, (onoff) ? ((FW2X_LED_BLINK) | (FW2X_LED_BLINK << 2) | (FW2X_LED_BLINK << 4)): (FW2X_LED_DEFAULT)); AQ_DBG_EXIT(err); return (err); } struct aq_firmware_ops aq_fw2x_ops = { .reset = fw2x_reset, .set_mode = fw2x_set_mode, .get_mode = fw2x_get_mode, .get_mac_addr = fw2x_get_mac_addr, .get_stats = fw2x_get_stats, .led_control = fw2x_led_control, };