aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/aq/aq_fw1x.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/aq/aq_fw1x.c')
-rw-r--r--sys/dev/aq/aq_fw1x.c313
1 files changed, 313 insertions, 0 deletions
diff --git a/sys/dev/aq/aq_fw1x.c b/sys/dev/aq/aq_fw1x.c
new file mode 100644
index 000000000000..97bbbfc5e335
--- /dev/null
+++ b/sys/dev/aq/aq_fw1x.c
@@ -0,0 +1,313 @@
+/**
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+
+#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"
+
+
+#define FW1X_MPI_CONTROL_ADR 0x368
+#define FW1X_MPI_STATE_ADR 0x36C
+
+
+typedef enum fw1x_mode {
+ FW1X_MPI_DEINIT = 0,
+ FW1X_MPI_RESERVED = 1,
+ FW1X_MPI_INIT = 2,
+ FW1X_MPI_POWER = 4,
+} fw1x_mode;
+
+typedef 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,
+} aq_fw1x_rate;
+
+typedef union fw1x_state_reg {
+ u32 val;
+ struct {
+ u8 mode;
+ u8 reserved1;
+ u8 speed;
+ u8 reserved2 : 1;
+ u8 disableDirtyWake : 1;
+ u8 reserved3 : 2;
+ u8 downshift : 4;
+ };
+} fw1x_state_reg;
+
+int fw1x_reset(struct aq_hw* hw);
+
+int fw1x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state_e mode, aq_fw_link_speed_t speed);
+int fw1x_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 fw1x_get_mac_addr(struct aq_hw* hw, u8* mac_addr);
+int fw1x_get_stats(struct aq_hw* hw, struct aq_hw_stats_s* stats);
+
+
+static
+fw1x_mode mpi_mode_to_fw1x_(enum aq_hw_fw_mpi_state_e 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
+aq_fw1x_rate link_speed_mask_to_fw1x_(u32 /*aq_fw_link_speed*/ speed)
+{
+ u32 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 ((aq_fw1x_rate)rate);
+}
+
+static
+aq_fw_link_speed_t fw1x_rate_to_link_speed_(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);
+}
+
+int fw1x_reset(struct aq_hw* hal)
+{
+ u32 tid0 = ~0u; /*< Initial value of MBOX transactionId. */
+ struct aq_hw_fw_mbox mbox;
+ const int retryCount = 1000;
+
+ for (int i = 0; i < retryCount; ++i) {
+ // Read the beginning of Statistics structure to capture the Transaction ID.
+ aq_hw_fw_downld_dwords(hal, hal->mbox_addr, (u32*)&mbox,
+ (u32)((char*)&mbox.stats - (char*)&mbox) / sizeof(u32));
+
+ // 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 (EOK);
+ }
+
+ /*
+ * Transaction ID value haven't changed since last time.
+ * Try reading the stats again.
+ */
+ usec_delay(10);
+ }
+
+ trace_error(dbg_init, "F/W 1.x reset finalize timeout");
+ return (-EBUSY);
+}
+
+int fw1x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state_e mode, aq_fw_link_speed_t speed)
+{
+ union fw1x_state_reg state = {0};
+ state.mode = mpi_mode_to_fw1x_(mode);
+ state.speed = link_speed_mask_to_fw1x_(speed);
+
+ trace(dbg_init, "fw1x> set mode %d, rate mask = %#x; raw = %#x", state.mode, state.speed, state.val);
+
+ AQ_WRITE_REG(hw, FW1X_MPI_CONTROL_ADR, state.val);
+
+ return (EOK);
+}
+
+int fw1x_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)
+{
+ union fw1x_state_reg state = { .val = AQ_READ_REG(hw, AQ_HW_MPI_STATE_ADR) };
+
+ trace(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_e 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 = fw1x_rate_to_link_speed_(state.speed);
+
+ *fc = aq_fw_fc_none;
+
+ AQ_DBG_EXIT(EOK);
+ return (EOK);
+}
+
+
+int fw1x_get_mac_addr(struct aq_hw* hw, u8* mac)
+{
+ int err = -EFAULT;
+ u32 mac_addr[2];
+
+ AQ_DBG_ENTER();
+
+ u32 efuse_shadow_addr = AQ_READ_REG(hw, 0x374);
+ 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, (u8*)mac_addr, ETH_MAC_LEN);
+
+ trace(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(EOK);
+ return (EOK);
+}
+
+int fw1x_get_stats(struct aq_hw* hw, struct aq_hw_stats_s* stats)
+{
+ int err = 0;
+
+ AQ_DBG_ENTER();
+ err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr, (u32*)(void*)&hw->mbox,
+ sizeof hw->mbox / sizeof(u32));
+
+ if (err >= 0) {
+ if (stats != &hw->mbox.stats)
+ memcpy(stats, &hw->mbox.stats, sizeof *stats);
+
+ stats->dpc = reg_rx_dma_stat_counter7get(hw);
+ }
+
+ AQ_DBG_EXIT(err);
+ return (err);
+}
+
+struct aq_firmware_ops aq_fw1x_ops =
+{
+ .reset = fw1x_reset,
+
+ .set_mode = fw1x_set_mode,
+ .get_mode = fw1x_get_mode,
+
+ .get_mac_addr = fw1x_get_mac_addr,
+ .get_stats = fw1x_get_stats,
+};
+