/** * 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 #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_fw2x_caps_lo { 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, }; enum aq_fw2x_caps_hi { 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_THERMAL_SHUTDOWN, 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, }; enum aq_fw2x_rate { FW2X_RATE_100M = 0x20, FW2X_RATE_1G = 0x100, FW2X_RATE_2G5 = 0x200, FW2X_RATE_5G = 0x400, FW2X_RATE_10G = 0x800, }; struct aq_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; }; struct aq_fw2x_phy_cable_diag_data { uint32_t lane_data[4]; }; struct aq_fw2x_capabilities { uint32_t caps_lo; uint32_t caps_hi; }; struct aq_fw2x_mailbox // struct fwHostInterface { uint32_t version; uint32_t transaction_id; int32_t error; struct aq_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; struct aq_fw2x_phy_cable_diag_data diag_data; uint32_t reserved[8]; struct aq_fw2x_capabilities caps; /* ... */ }; // 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_CAP_TEMPERATURE (1ull << (32 + CAPS_HI_TEMPERATURE)) #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. static int aq_fw2x_reset(struct aq_hw* hw); static int aq_fw2x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state mode, enum aq_fw_link_speed speed); static int aq_fw2x_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_fw2x_get_mac_addr(struct aq_hw* hw, uint8_t* mac); static int aq_fw2x_get_stats(struct aq_hw* hw, struct aq_hw_stats* stats); static uint64_t read64(struct aq_hw* hw, uint32_t addr) { uint64_t lo, hi, hi2; hi = AQ_READ_REG(hw, addr + 4); do { hi2 = hi; lo = AQ_READ_REG(hw, addr); hi = AQ_READ_REG(hw, addr + 4); } while (hi != hi2); 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)); } static int aq_fw2x_reset(struct aq_hw* hw) { struct aq_fw2x_capabilities caps = {0}; AQ_DBG_ENTER(); mtx_lock(&hw->fw_mtx); int err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr + offsetof(struct aq_fw2x_mailbox, caps), (uint32_t*)&caps, sizeof caps/sizeof(uint32_t)); mtx_unlock(&hw->fw_mtx); if (err == 0) { hw->fw_caps = caps.caps_lo | ((uint64_t)caps.caps_hi << 32); trace(hw, dbg_init, "fw2x> F/W capabilities mask = %llx", (unsigned long long)hw->fw_caps); } else { trace_error(hw, dbg_init, "fw2x> can't get F/W capabilities mask, error %d", err); } AQ_DBG_EXIT(err); return (err); } static enum 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 ((enum aq_fw2x_rate)rate); } static int aq_fw2x_set_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state mode, enum aq_fw_link_speed speed) { uint64_t mpi_ctrl; AQ_DBG_ENTERA("speed=%d", speed); mtx_lock(&hw->fw_mtx); mpi_ctrl = get_mpi_ctrl(hw); switch (mode) { case MPI_INIT: mpi_ctrl &= ~FW2X_RATE_MASK; mpi_ctrl |= link_speed_mask_to_fw2x(speed); mpi_ctrl &= ~FW2X_CAP_LINK_DROP; mpi_ctrl &= ~(FW2X_FW_CAP_PAUSE | FW2X_FW_CAP_ASYM_PAUSE); #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 | FW2X_FW_CAP_ASYM_PAUSE; else 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: mtx_unlock(&hw->fw_mtx); trace_error(hw, dbg_init, "fw2x> unknown MPI state %d", mode); return (EINVAL); } set_mpi_ctrl(hw, mpi_ctrl); mtx_unlock(&hw->fw_mtx); AQ_DBG_EXIT(0); return (0); } static int aq_fw2x_get_mode(struct aq_hw* hw, enum aq_hw_fw_mpi_state* mode, enum aq_fw_link_speed* link_speed, enum aq_fw_link_fc* fc) { uint64_t mpi_state; uint32_t rates; mtx_lock(&hw->fw_mtx); mpi_state = get_mpi_state(hw); if (mode) { uint64_t mpi_ctrl = get_mpi_ctrl(hw); if (mpi_ctrl & FW2X_RATE_MASK) *mode = MPI_INIT; else *mode = MPI_DEINIT; } mtx_unlock(&hw->fw_mtx); rates = mpi_state & FW2X_RATE_MASK; enum aq_fw_link_speed 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); return (0); } static int aq_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(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); AQ_DBG_EXIT(0); return (0); } static inline void aq_fw2x_stats_to_fw_stats(struct aq_hw_stats* dst, const struct aq_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 int aq_fw2x_get_stats(struct aq_hw* hw, struct aq_hw_stats* stats) { struct aq_fw2x_msm_statistics aq_fw2x_stats = {0}; uint64_t mpi_ctrl; int err; if ((hw->fw_caps & FW2X_CAP_STATISTICS) == 0) { trace_warn(hw, dbg_fw, "fw2x> statistics not supported by F/W"); return (ENOTSUP); } /* Kick-and-read: take the F/W's previous snapshot, request the next. */ mtx_lock(&hw->fw_mtx); err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr + offsetof(struct aq_fw2x_mailbox, msm), (uint32_t*)&aq_fw2x_stats, sizeof aq_fw2x_stats/sizeof(uint32_t)); mpi_ctrl = get_mpi_ctrl(hw); mpi_ctrl ^= FW2X_CAP_STATISTICS; set_mpi_ctrl(hw, mpi_ctrl); mtx_unlock(&hw->fw_mtx); aq_fw2x_stats_to_fw_stats(stats, &aq_fw2x_stats); if (err != 0) trace_error(hw, dbg_fw, "fw2x> download statistics data FAILED, error %d", err); return (err); } static int aq_fw2x_get_temp(struct aq_hw* hw, int* temp_mc) { uint64_t mpi_ctrl, req_bit; uint32_t raw; int err; if ((hw->fw_caps & FW2X_CAP_TEMPERATURE) == 0) return (ENOTSUP); /* Toggle the request bit and wait for the F/W to echo it back. */ mtx_lock(&hw->fw_mtx); mpi_ctrl = get_mpi_ctrl(hw); req_bit = mpi_ctrl & FW2X_CAP_TEMPERATURE; set_mpi_ctrl(hw, mpi_ctrl ^ FW2X_CAP_TEMPERATURE); err = AQ_HW_WAIT_FOR((get_mpi_state(hw) & FW2X_CAP_TEMPERATURE) != req_bit, 1, 10000); if (err == 0) err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr + offsetof(struct aq_fw2x_mailbox, phy_temperature), &raw, 1); mtx_unlock(&hw->fw_mtx); if (err != 0) { trace_error(hw, dbg_fw, "fw2x> temperature read FAILED, error %d", err); return (err); } /* F/W reports 1/256 degree Celsius. */ *temp_mc = (int)(int16_t)(raw & 0xffff) * 1000 / 256; return (0); } static int aq_fw2x_get_phy_fault(struct aq_hw* hw, uint16_t* fault) { uint32_t raw; int err; mtx_lock(&hw->fw_mtx); err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr + offsetof(struct aq_fw2x_mailbox, phy_h_bit), &raw, 1); mtx_unlock(&hw->fw_mtx); if (err != 0) return (err); *fault = (uint16_t)(raw >> 16); return (0); } /* PHY MDIO access: MMD register read/write via the MAC's MDIO controller. */ #define AQ_MDIO_IFACE(n) (0x280 + (((n) - 1) * 4)) #define AQ_MDIO_BUSY 0x80000000u /* iface2 bit 31 */ #define AQ_MDIO_EXECUTE 0x00008000u /* iface2 bit 15 */ #define AQ_MDIO_OP_S 12 /* iface2 bits 13:12 */ #define AQ_MDIO_OP_ADDR 3 #define AQ_MDIO_OP_READ 1 #define AQ_MDIO_OP_WRITE 2 #define AQ_MDIO_PHYADDR_MSK 0x3ffu #define AQ_FW_SM_MDIO 0 /* cpu semaphore index */ #define AQ_PHY_ID_MAX 32 /* MDIO port addresses to scan */ #define AQ_MDIO_MMD_PMAPMD 0x01 /* PMA/PMD MMD */ #define AQ_PHY_ID2_REG 0x0003 /* PMA/PMD Device Identifier 2 */ #define AQ_PHY_MMD_GLOBAL 0x1e #define AQ_PHY_RESET_REG 0x2681 /* 1E.2681.0 = PHY hard reset */ #define AQ_PHY_RESET 0x0001 #define AQ_PHY_THERMAL_CTRL_REG 0xc478 /* 1E.C478 thermal control */ #define AQ_PHY_THERMAL_SD_EN 0x0400 /* .A thermalShutdownEnable */ static int aq_fw2x_mdio_op(struct aq_hw* hw, uint16_t mmd, uint16_t addr, int write, uint16_t data, uint16_t* val) { uint32_t pa = (((uint32_t)hw->phy_id & 0x1f) << 5) | (mmd & 0x1f); int err; AQ_WRITE_REG(hw, AQ_MDIO_IFACE(4), addr); AQ_WRITE_REG(hw, AQ_MDIO_IFACE(2), AQ_MDIO_EXECUTE | (AQ_MDIO_OP_ADDR << AQ_MDIO_OP_S) | pa); err = AQ_HW_WAIT_FOR((AQ_READ_REG(hw, AQ_MDIO_IFACE(2)) & AQ_MDIO_BUSY) == 0, 10, 10000); if (err != 0) return (err); if (write) { AQ_WRITE_REG(hw, AQ_MDIO_IFACE(3), data); AQ_WRITE_REG(hw, AQ_MDIO_IFACE(2), AQ_MDIO_EXECUTE | (AQ_MDIO_OP_WRITE << AQ_MDIO_OP_S) | pa); } else { AQ_WRITE_REG(hw, AQ_MDIO_IFACE(2), AQ_MDIO_EXECUTE | (AQ_MDIO_OP_READ << AQ_MDIO_OP_S) | pa); } err = AQ_HW_WAIT_FOR((AQ_READ_REG(hw, AQ_MDIO_IFACE(2)) & AQ_MDIO_BUSY) == 0, 10, 10000); if (err != 0) return (err); if (val != NULL) *val = (uint16_t)AQ_READ_REG(hw, AQ_MDIO_IFACE(5)); return (0); } /* MDIO is serialized against the F/W by cpu semaphore 0. */ static int aq_fw2x_phy_write(struct aq_hw* hw, uint16_t mmd, uint16_t addr, uint16_t data) { int err; err = AQ_HW_WAIT_FOR(reg_glb_cpu_sem_get(hw, AQ_FW_SM_MDIO) == 1U, 10, 10000); if (err != 0) return (err); err = aq_fw2x_mdio_op(hw, mmd, addr, 1, data, NULL); reg_glb_cpu_sem_set(hw, 1U, AQ_FW_SM_MDIO); return (err); } static int aq_fw2x_phy_read(struct aq_hw* hw, uint16_t mmd, uint16_t addr, uint16_t* val) { int err; err = AQ_HW_WAIT_FOR(reg_glb_cpu_sem_get(hw, AQ_FW_SM_MDIO) == 1U, 10, 10000); if (err != 0) return (err); err = aq_fw2x_mdio_op(hw, mmd, addr, 0, 0, val); reg_glb_cpu_sem_set(hw, 1U, AQ_FW_SM_MDIO); return (err); } /* Discover the PHY's MDIO port address; it is strap-selectable, not fixed at 0. */ static bool aq_fw2x_init_phy_id(struct aq_hw* hw) { uint16_t val; uint8_t id; int err; for (id = 0; id < AQ_PHY_ID_MAX; id++) { hw->phy_id = id; err = aq_fw2x_phy_read(hw, AQ_MDIO_MMD_PMAPMD, AQ_PHY_ID2_REG, &val); if (err == 0 && val != 0xffff) return (true); /* A timeout means the controller is wedged, not this port. */ if (err == ETIMEDOUT) break; } hw->phy_id = 0; return (false); } /* Called with fw_mtx held; the port address is needed by every MDIO caller. */ static void aq_fw2x_phy_id_probe(struct aq_hw* hw) { if (!hw->phy_id_valid && aq_fw2x_init_phy_id(hw)) hw->phy_id_valid = true; } /* PHY hard reset (1E.2681.0): clears a latched thermal shutdown a MAC reset cannot. */ static int aq_fw2x_phy_reset(struct aq_hw* hw) { int err; mtx_lock(&hw->fw_mtx); aq_fw2x_phy_id_probe(hw); err = aq_fw2x_phy_write(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_RESET_REG, AQ_PHY_RESET); mtx_unlock(&hw->fw_mtx); return (err); } /* Arm/disarm thermal shutdown, cleared by any PHY reset. */ static int aq_fw2x_thermal_arm(struct aq_hw* hw, bool enable) { uint16_t ctrl, want; int err; if ((hw->fw_caps & FW2X_CAP_TEMPERATURE) == 0) return (ENOTSUP); mtx_lock(&hw->fw_mtx); aq_fw2x_phy_id_probe(hw); err = aq_fw2x_phy_read(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_THERMAL_CTRL_REG, &ctrl); if (err != 0) goto out; /* All ones is no PHY answering, not a control word. */ if (ctrl == 0xffff) { err = ENXIO; goto out; } if (enable) want = ctrl | AQ_PHY_THERMAL_SD_EN; else want = ctrl & ~AQ_PHY_THERMAL_SD_EN; if (want != ctrl) err = aq_fw2x_phy_write(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_THERMAL_CTRL_REG, want); out: mtx_unlock(&hw->fw_mtx); return (err); } /* 1E.C421 high-temp shutdown threshold, degrees C in Q8.8 fixed point. */ #define AQ_PHY_THERMAL_HIGH_REG 0xc421 static int aq_fw2x_get_thermal_limit(struct aq_hw* hw, int* limit_mc) { uint16_t raw; int err; mtx_lock(&hw->fw_mtx); aq_fw2x_phy_id_probe(hw); err = aq_fw2x_phy_read(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_THERMAL_HIGH_REG, &raw); mtx_unlock(&hw->fw_mtx); if (err != 0 || raw == 0 || raw == 0xffff) return (ENXIO); *limit_mc = (int)(int16_t)raw * 1000 / 256; return (0); } static int aq_fw2x_led_control(struct aq_hw* hw, uint32_t onoff) { int err = 0; AQ_DBG_ENTER(); struct 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); } const struct aq_firmware_ops aq_fw2x_ops = { .reset = aq_fw2x_reset, .set_mode = aq_fw2x_set_mode, .get_mode = aq_fw2x_get_mode, .get_mac_addr = aq_fw2x_get_mac_addr, .get_stats = aq_fw2x_get_stats, .get_temp = aq_fw2x_get_temp, .get_phy_fault = aq_fw2x_get_phy_fault, .phy_reset = aq_fw2x_phy_reset, .thermal_arm = aq_fw2x_thermal_arm, .get_thermal_limit = aq_fw2x_get_thermal_limit, .led_control = aq_fw2x_led_control, };