/** * 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. */ #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" enum aq_fw1x_mode { FW1X_MPI_DEINIT = 0, FW1X_MPI_RESERVED = 1, FW1X_MPI_INIT = 2, FW1X_MPI_POWER = 4, }; enum aq_fw1x_rate { FW1X_RATE_10G = 1 << 0, FW1X_RATE_5G = 1 << 1, FW1X_RATE_5GSR = 1 << 2, FW1X_RATE_2G5 = 1 << 3, FW1X_RATE_1G = 1 << 4, FW1X_RATE_100M = 1 << 5, FW1X_RATE_INVALID = 1 << 6, }; union aq_fw1x_state_reg { uint32_t val; struct { uint8_t mode; uint8_t reserved1; uint8_t speed; uint8_t reserved2 : 1; uint8_t disableDirtyWake : 1; uint8_t reserved3 : 2; uint8_t downshift : 4; }; }; static int aq_fw1x_reset(struct aq_hw* hw); static int aq_fw1x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state mode, enum aq_fw_link_speed speed); static int aq_fw1x_get_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state* mode, enum aq_fw_link_speed* speed, enum aq_fw_link_fc* fc); static int aq_fw1x_get_mac_addr(struct aq_hw* hw, uint8_t* mac_addr); static int aq_fw1x_get_stats(struct aq_hw* hw, struct aq_hw_stats* stats); static enum aq_fw1x_mode mpi_mode_to_fw1x(enum aq_hw_fw_mpi_state mode) { switch (mode) { case MPI_DEINIT: return (FW1X_MPI_DEINIT); case MPI_INIT: return (FW1X_MPI_INIT); case MPI_POWER: return (FW1X_MPI_POWER); case MPI_RESET: return (FW1X_MPI_RESERVED); } /* * We shouldn't get here. */ return (FW1X_MPI_RESERVED); } static enum aq_fw1x_rate link_speed_mask_to_fw1x(uint32_t /*aq_fw_link_speed*/ speed) { uint32_t rate = 0; if (speed & aq_fw_10G) rate |= FW1X_RATE_10G; if (speed & aq_fw_5G) { rate |= FW1X_RATE_5G; rate |= FW1X_RATE_5GSR; } if (speed & aq_fw_2G5) rate |= FW1X_RATE_2G5; if (speed & aq_fw_1G) rate |= FW1X_RATE_1G; if (speed & aq_fw_100M) rate |= FW1X_RATE_100M; return ((enum aq_fw1x_rate)rate); } static enum aq_fw_link_speed aq_fw1x_rate_to_link_speed(enum aq_fw1x_rate rate) { switch (rate) { case FW1X_RATE_10G: return (aq_fw_10G); case FW1X_RATE_5G: case FW1X_RATE_5GSR: return (aq_fw_5G); case FW1X_RATE_2G5: return (aq_fw_2G5); case FW1X_RATE_1G: return (aq_fw_1G); case FW1X_RATE_100M: return (aq_fw_100M); case FW1X_RATE_INVALID: return (aq_fw_none); } /* * We should never get here. */ return (aq_fw_none); } static int aq_fw1x_reset(struct aq_hw* hw) { uint32_t tid0 = ~0u; /*< Initial value of MBOX transactionId. */ struct aq_hw_fw_mbox mbox; const int retryCount = 1000; int err; for (int i = 0; i < retryCount; ++i) { // Read the beginning of Statistics structure to capture the // Transaction ID. err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr, (uint32_t*)&mbox, (uint32_t)((char*)&mbox.stats - (char*)&mbox) / sizeof(uint32_t)); /* The MCP is still cold-starting; that is what we wait for. */ if (err != 0) { DELAY(10); continue; } // Successfully read the stats. if (tid0 == ~0U) { // We have read the initial value. tid0 = mbox.transaction_id; continue; } else if (mbox.transaction_id != tid0) { /* * Compare transaction ID to initial value. * If it's different means f/w is alive. We're done. */ return (0); } /* * Transaction ID value haven't changed since last time. * Try reading the stats again. */ DELAY(10); } trace_error(hw, dbg_init, "F/W 1.x reset finalize timeout"); return (EBUSY); } /* No fw_mtx here: the control register is written whole, never modified. */ static int aq_fw1x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state mode, enum aq_fw_link_speed speed) { union aq_fw1x_state_reg state = {0}; state.mode = mpi_mode_to_fw1x(mode); state.speed = link_speed_mask_to_fw1x(speed); trace(hw, dbg_init, "fw1x> set mode %d, rate mask = %#x; raw = %#x", state.mode, state.speed, state.val); AQ_WRITE_REG(hw, AQ_HW_MPI_CONTROL_ADR, state.val); return (0); } static int aq_fw1x_get_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state* mode, enum aq_fw_link_speed* speed, enum aq_fw_link_fc* fc) { union aq_fw1x_state_reg state = { .val = AQ_READ_REG(hw, AQ_HW_MPI_STATE_ADR) }; trace(hw, dbg_init, "fw1x> get_mode(): 0x36c -> %x, 0x368 -> %x", state.val, AQ_READ_REG(hw, AQ_HW_MPI_CONTROL_ADR)); enum aq_hw_fw_mpi_state md = MPI_DEINIT; switch (state.mode) { case FW1X_MPI_DEINIT: md = MPI_DEINIT; break; case FW1X_MPI_RESERVED: md = MPI_RESET; break; case FW1X_MPI_INIT: md = MPI_INIT; break; case FW1X_MPI_POWER: md = MPI_POWER; break; } if (mode) *mode = md; if (speed) *speed = aq_fw1x_rate_to_link_speed(state.speed); *fc = aq_fw_fc_none; AQ_DBG_EXIT(0); return (0); } static int aq_fw1x_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, 0x374); if (efuse_shadow_addr == 0) { trace_error(hw, 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, nitems(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); trace(hw, dbg_init, "fw1x> eFUSE MAC addr -> %02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); AQ_DBG_EXIT(0); return (0); } /* aq_fw1x_get_stats() memcpy's this raw block onto aq_hw_stats' prefix. */ _Static_assert(sizeof(struct aq_fw1x_mbox_stats) == __offsetof(struct aq_hw_stats, brc), "fw1x mailbox stats must match the aq_hw_stats prefix"); static int aq_fw1x_get_stats(struct aq_hw* hw, struct aq_hw_stats* stats) { int err = 0; AQ_DBG_ENTER(); err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr, (uint32_t*)(void*)&hw->mbox, sizeof hw->mbox / sizeof(uint32_t)); if (err == 0) memcpy(stats, &hw->mbox.stats, sizeof hw->mbox.stats); AQ_DBG_EXIT(err); return (err); } const struct aq_firmware_ops aq_fw1x_ops = { .reset = aq_fw1x_reset, .set_mode = aq_fw1x_set_mode, .get_mode = aq_fw1x_get_mode, .get_mac_addr = aq_fw1x_get_mac_addr, .get_stats = aq_fw1x_get_stats, };