mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
327 lines
7.7 KiB
C
327 lines
7.7 KiB
C
/**
|
|
* 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/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"
|
|
|
|
|
|
|
|
|
|
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,
|
|
};
|