root/sys/dev/aq/aq_fw1x.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/cdefs.h>
__FBSDID("$FreeBSD$");

#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;

        for (int i = 0; i < retryCount; ++i) {
                // Read the beginning of Statistics structure to capture the
                // Transaction ID.
                aq_hw_fw_downld_dwords(hw, hw->mbox_addr, (uint32_t*)&mbox,
                    (uint32_t)((char*)&mbox.stats - (char*)&mbox) / sizeof(uint32_t));

                // 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(dbg_init, "F/W 1.x reset finalize timeout");
        return (EBUSY);
}

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(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(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(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(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,
};