diff options
Diffstat (limited to 'sys/dev/aq/aq2_fw.c')
| -rw-r--r-- | sys/dev/aq/aq2_fw.c | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/sys/dev/aq/aq2_fw.c b/sys/dev/aq/aq2_fw.c new file mode 100644 index 000000000000..3c2ba7ad944c --- /dev/null +++ b/sys/dev/aq/aq2_fw.c @@ -0,0 +1,624 @@ +/*- + * Atlantic 2 (AQC113/114/115/116) firmware operations for aq(4). + * + * Adapted from the OpenBSD/NetBSD if_aq driver: + * + * Copyright (c) 2021 Jonathan Matthew <jonathan@d14n.org> + * Copyright (c) 2021 Mike Larkin <mlarkin@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/endian.h> +#include <net/ethernet.h> + +#include "aq_common.h" +#include "aq_hw.h" +#include "aq_hw_llh.h" +#include "aq2_hw.h" +#include "aq_fw.h" +#include "aq_dbg.h" + +static int aq2_fw_reset(struct aq_hw *hw); +static int aq2_fw_set_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state mode, + enum aq_fw_link_speed speed); +static int aq2_fw_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 aq2_fw_get_link_info(struct aq_hw *hw, struct aq_hw_link_info *info); +static int aq2_fw_get_mac_addr(struct aq_hw *hw, uint8_t *mac); +static int aq2_fw_get_stats(struct aq_hw *hw, struct aq_hw_stats *stats); +static int aq2_fw_get_link_counters(struct aq_hw *hw, uint32_t *up, + uint32_t *down); +static int aq2_fw_get_temp(struct aq_hw *hw, int *temp_mc); +static int aq2_fw_get_thermal_limit(struct aq_hw *hw, int *limit_mc); +static int aq2_fw_get_phy_hot_warning(struct aq_hw *hw, bool *hot); + +/* Coherent OUT-window read, bracketed by the transaction id. */ +static int +aq2_fw_interface_buffer_read(struct aq_hw *hw, uint32_t reg0, uint32_t *data0, + uint32_t size0) +{ + uint32_t tid0, tid1, reg, *data, size; + int timo; + + for (timo = 10000; timo > 0; timo--) { + tid0 = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_REG); + if (((tid0 & AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_A) >> + AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_A_S) != + ((tid0 & AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_B) >> + AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_B_S)) { + DELAY(10); + continue; + } + + /* size0 is a 4-byte multiple: full register-width reads. */ + for (reg = reg0, data = data0, size = size0; + size >= 4; reg += 4, data++, size -= 4) + *data = AQ_READ_REG(hw, reg); + + tid1 = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_TRANSACTION_ID_REG); + if (tid0 == tid1) + break; + } + if (timo == 0) { + device_printf(hw->dev, "A2 interface buffer read timeout\n"); + return (ETIMEDOUT); + } + return (0); +} + +/* Boot the A2 firmware and select the A2 firmware ops. */ +int +aq2_fw_reboot(struct aq_hw *hw) +{ + uint32_t v, filter_caps[3]; + int timo, err; + const char *iface; + + hw->fw_ops = &aq2_fw_ops; + + AQ_WRITE_REG(hw, AQ2_MCP_HOST_REQ_INT_CLR_REG, 1); + AQ_WRITE_REG(hw, AQ2_MIF_BOOT_REG, 1); /* reboot request */ + for (timo = 20000; timo > 0; timo--) { + v = AQ_READ_REG(hw, AQ2_MIF_BOOT_REG); + if ((v & AQ2_MIF_BOOT_BOOT_STARTED) && v != 0xffffffff) + break; + DELAY(10); + } + if (timo <= 0) { + device_printf(hw->dev, "A2 firmware reboot timeout\n"); + return (ETIMEDOUT); + } + trace(hw, dbg_init, "aq2> F/W boot started, %d ms", (20000 - timo) / 100); + + for (timo = 200000; timo > 0; timo--) { + v = AQ_READ_REG(hw, AQ2_MIF_BOOT_REG); + if (v & AQ2_MIF_BOOT_COMPLETE) + break; + v = AQ_READ_REG(hw, AQ2_MCP_HOST_REQ_INT_REG); + if (v & AQ2_MCP_HOST_REQ_INT_READY) + break; + DELAY(10); + } + if (timo <= 0) { + device_printf(hw->dev, "A2 firmware restart timeout\n"); + return (ETIMEDOUT); + } + trace(hw, dbg_init, "aq2> F/W boot complete, %d ms", + (200000 - timo) / 100); + + v = AQ_READ_REG(hw, AQ2_MIF_BOOT_REG); + if (v & AQ2_MIF_BOOT_FAILED) { + device_printf(hw->dev, "A2 firmware restart failed\n"); + return (EIO); + } + v = AQ_READ_REG(hw, AQ2_MCP_HOST_REQ_INT_REG); + if (v & AQ2_MCP_HOST_REQ_INT_READY) { + device_printf(hw->dev, "A2 firmware required but not present\n"); + return (ENXIO); + } + + /* Repack into fw_version's major.minor.build layout. */ + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_VERSION_BUNDLE_REG, &v, sizeof(v)); + if (err != 0) + return (err); + hw->fw_version.raw = + (((v & AQ2_FW_INTERFACE_OUT_VERSION_MAJOR) >> + AQ2_FW_INTERFACE_OUT_VERSION_MAJOR_S) << 24) | + (((v & AQ2_FW_INTERFACE_OUT_VERSION_MINOR) >> + AQ2_FW_INTERFACE_OUT_VERSION_MINOR_S) << 16) | + ((v & AQ2_FW_INTERFACE_OUT_VERSION_BUILD) >> + AQ2_FW_INTERFACE_OUT_VERSION_BUILD_S); + + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_VERSION_IFACE_REG, &v, sizeof(v)); + if (err != 0) + return (err); + hw->aq2_iface = v & AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER; + switch (hw->aq2_iface) { + case AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_A0: + iface = "A0"; + break; + case AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_B0: + iface = "B0"; + break; + default: + iface = "unknown"; + break; + } + if (!hw->fw_announced || bootverbose) { + device_printf(hw->dev, "Atlantic 2 %s, firmware %u.%u.%u\n", + iface, hw->fw_version.major_version, + hw->fw_version.minor_version, hw->fw_version.build_number); + hw->fw_announced = true; + } + + /* Base row added to every action-resolver-table index. */ + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_FILTER_CAPS_REG, filter_caps, + sizeof(filter_caps)); + if (err != 0) + return (err); + hw->art_filter_base_index = ((filter_caps[2] & + AQ2_FW_INTERFACE_OUT_FILTER_CAPS3_RESOLVER_BASE_INDEX) >> + AQ2_FW_INTERFACE_OUT_FILTER_CAPS3_RESOLVER_BASE_INDEX_SHIFT) * 8; + trace(hw, dbg_init, "aq2> ART filter base index = %u", + hw->art_filter_base_index); + + return (0); +} + +/* Commit an IN-window write; wait for the MCP ack. */ +static int +aq2_fw_wait_shared_ack(struct aq_hw *hw) +{ + int err; + + AQ_WRITE_REG(hw, AQ2_MIF_HOST_FINISHED_STATUS_WRITE_REG, + AQ2_MIF_HOST_FINISHED_STATUS_ACK); + err = AQ_HW_WAIT_FOR((AQ_READ_REG(hw, + AQ2_MIF_HOST_FINISHED_STATUS_READ_REG) & + AQ2_MIF_HOST_FINISHED_STATUS_ACK) == 0, 100, 1000); + if (err != 0) + trace_error(hw, dbg_fw, "aq2> shared buffer ack timed out"); + + return (err); +} + +static int +aq2_fw_reset(struct aq_hw *hw) +{ + uint32_t v; + int err; + + AQ_WRITE_REG_BIT(hw, AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG, + AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE, 0, + AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_ACTIVE); + + AQ_WRITE_REG(hw, AQ2_FW_INTERFACE_IN_MTU_REG, HW_ATL_B0_MTU_JUMBO); + + v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_REQUEST_POLICY_REG); + v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_QUEUE_OR_TC; + v &= ~AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_RX_QUEUE_TC_INDEX; + v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_ACCEPT; + v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_QUEUE_OR_TC; + v &= ~AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_RX_QUEUE_TC_INDEX; + v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_BCAST_ACCEPT; + v |= AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_QUEUE_OR_TC; + v &= ~AQ2_FW_INTERFACE_IN_REQUEST_POLICY_PROMISC_RX_QUEUE_TX_INDEX; + AQ_WRITE_REG(hw, AQ2_FW_INTERFACE_IN_REQUEST_POLICY_REG, v); + + trace(hw, dbg_init, "aq2> reset: mtu %u, request policy %#x", + HW_ATL_B0_MTU_JUMBO, v); + + err = aq2_fw_wait_shared_ack(hw); + if (err != 0) + device_printf(hw->dev, "A2 firmware reset timed out\n"); + return (err); +} + +static int +aq2_fw_get_mac_addr(struct aq_hw *hw, uint8_t *mac) +{ + uint32_t mac_addr[2]; + + mac_addr[0] = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_MAC_ADDRESS_REG); + mac_addr[1] = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_MAC_ADDRESS_REG + 4); + + if (mac_addr[0] == 0 && mac_addr[1] == 0) { + device_printf(hw->dev, "A2 mac address not found\n"); + return (ENXIO); + } + + mac_addr[0] = htole32(mac_addr[0]); + mac_addr[1] = htole32(mac_addr[1]); + memcpy(mac, (uint8_t *)mac_addr, ETHER_ADDR_LEN); + trace(hw, dbg_init, "aq2> MAC addr %6D", mac, ":"); + return (0); +} + +/* No fw_mtx: the IN window is written only from iflib-serialised paths. */ +static int +aq2_fw_set_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state mode, + enum aq_fw_link_speed speed) +{ + uint32_t v; + int err; + + v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_LINK_OPTIONS_REG); + v &= ~(AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10G | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N5G | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_5G | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N2G5 | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_2G5 | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G_HD | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M_HD | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M_HD); + v &= ~AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_UP; + + if (mode == MPI_INIT) { + if (speed & aq_fw_10G) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10G; + if (speed & aq_fw_5G) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N5G | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_5G; + if (speed & aq_fw_2G5) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_N2G5 | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_2G5; + if (speed & aq_fw_1G) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_1G; + if (speed & aq_fw_100M) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_100M; + if (speed & aq_fw_10M) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_RATE_10M; + + v &= ~(AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_TX | + AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_RX); + if (hw->fc.fc_tx) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_TX; + if (hw->fc.fc_rx) + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_PAUSE_RX; + + v |= AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_UP; + } else { + AQ_WRITE_REG_BIT(hw, AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG, + AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE, 0, + AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_SHUTDOWN); + } + + trace(hw, dbg_init, "aq2> set mode %d, speed mask %#x, link options %#x", + mode, speed, v); + + /* Options acked before ACTIVE so bring-up negotiates the new mask. */ + AQ_WRITE_REG(hw, AQ2_FW_INTERFACE_IN_LINK_OPTIONS_REG, v); + if (mode == MPI_INIT) { + err = aq2_fw_wait_shared_ack(hw); + if (err != 0) + return (err); + AQ_WRITE_REG_BIT(hw, AQ2_FW_INTERFACE_IN_LINK_CONTROL_REG, + AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE, 0, + AQ2_FW_INTERFACE_IN_LINK_CONTROL_MODE_ACTIVE); + } + return (aq2_fw_wait_shared_ack(hw)); +} + +static int +aq2_fw_get_mode(struct aq_hw *hw, enum aq_hw_fw_mpi_state *modep, + enum aq_fw_link_speed *speedp, enum aq_fw_link_fc *fcp) +{ + uint32_t v; + enum aq_fw_link_speed speed; + enum aq_fw_link_fc fc = aq_fw_fc_none; + + if (modep != NULL) + *modep = MPI_INIT; + + v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_LINK_STATUS_REG); + switch ((v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE) >> + AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_S) { + case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_10G: + speed = aq_fw_10G; + break; + case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_5G: + speed = aq_fw_5G; + break; + case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_2G5: + speed = aq_fw_2G5; + break; + case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_1G: + speed = aq_fw_1G; + break; + case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_100M: + speed = aq_fw_100M; + break; + case AQ2_FW_INTERFACE_OUT_LINK_STATUS_RATE_10M: + speed = aq_fw_10M; + break; + default: + speed = aq_fw_none; + break; + } + if (speedp != NULL) + *speedp = speed; + + if (v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_PAUSE_TX) + fc |= aq_fw_fc_ENABLE_TX; + if (v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_PAUSE_RX) + fc |= aq_fw_fc_ENABLE_RX; + if (fcp != NULL) + *fcp = fc; + + trace_detail(hw, dbg_init, + "aq2> get mode: link status %#x, speed %d, fc %d", v, speed, fc); + + return (0); +} + +static int +aq2_fw_get_link_info(struct aq_hw *hw, struct aq_hw_link_info *info) +{ + uint32_t v; + + v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_OUT_LINK_STATUS_REG); + + info->full_duplex = + (v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_DUPLEX) != 0; + info->eee = (v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_EEE) != 0; + info->state = v & AQ2_FW_INTERFACE_OUT_LINK_STATUS_STATE; + + return (0); +} + +/* A2 firmware stats; layout depends on interface version. */ +struct aq2_fw_statistics_a0 { + uint32_t link_up; + uint32_t link_down; + uint64_t tx_unicast_octets; + uint64_t tx_multicast_octets; + uint64_t tx_broadcast_octets; + uint64_t rx_unicast_octets; + uint64_t rx_multicast_octets; + uint64_t rx_broadcast_octets; + uint32_t tx_unicast_frames; + uint32_t tx_multicast_frames; + uint32_t tx_broadcast_frames; + uint32_t tx_errors; + uint32_t rx_unicast_frames; + uint32_t rx_multicast_frames; + uint32_t rx_broadcast_frames; + uint32_t rx_dropped_frames; + uint32_t rx_errors; + uint32_t tx_good_frames; + uint32_t rx_good_frames; + uint32_t reserved1; + uint32_t main_loop_cycles; + uint32_t reserved2; +}; + +struct aq2_fw_statistics_b0 { + uint64_t rx_good_octets; + uint64_t rx_pause_frames; + uint64_t rx_good_frames; + uint64_t rx_errors; + uint64_t rx_unicast_frames; + uint64_t rx_multicast_frames; + uint64_t rx_broadcast_frames; + uint64_t tx_good_octets; + uint64_t tx_pause_frames; + uint64_t tx_good_frames; + uint64_t tx_errors; + uint64_t tx_unicast_frames; + uint64_t tx_multicast_frames; + uint64_t tx_broadcast_frames; + uint32_t main_loop_cycles; +} __packed; + +union aq2_fw_statistics { + struct aq2_fw_statistics_a0 a0; + struct aq2_fw_statistics_b0 b0; +}; + +static int +aq2_fw_get_stats(struct aq_hw *hw, struct aq_hw_stats *stats) +{ + union aq2_fw_statistics u; + int err; + + memset(stats, 0, sizeof(*stats)); + + err = aq2_fw_interface_buffer_read(hw, AQ2_FW_INTERFACE_OUT_STATS_REG, + (uint32_t *)&u, sizeof(u)); + if (err != 0) { + trace_error(hw, dbg_fw, + "aq2> statistics read FAILED, error %d", err); + return (err); + } + + if (hw->aq2_iface == AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_A0) { + stats->uprc = u.a0.rx_unicast_frames; + stats->mprc = u.a0.rx_multicast_frames; + stats->bprc = u.a0.rx_broadcast_frames; + stats->erpr = u.a0.rx_errors; + stats->ubrc = u.a0.rx_unicast_octets; + stats->mbrc = u.a0.rx_multicast_octets; + stats->bbrc = u.a0.rx_broadcast_octets; + stats->prc = u.a0.rx_good_frames; + stats->uptc = u.a0.tx_unicast_frames; + stats->mptc = u.a0.tx_multicast_frames; + stats->bptc = u.a0.tx_broadcast_frames; + stats->erpt = u.a0.tx_errors; + stats->ubtc = u.a0.tx_unicast_octets; + stats->mbtc = u.a0.tx_multicast_octets; + stats->bbtc = u.a0.tx_broadcast_octets; + stats->ptc = u.a0.tx_good_frames; + } else if (hw->aq2_iface == AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_B0) { + /* B0 reports aggregate octets only; per-cast stays zero. */ + stats->brc = u.b0.rx_good_octets; + stats->btc = u.b0.tx_good_octets; + stats->uprc = u.b0.rx_unicast_frames; + stats->mprc = u.b0.rx_multicast_frames; + stats->bprc = u.b0.rx_broadcast_frames; + stats->erpr = u.b0.rx_errors; + stats->prc = u.b0.rx_good_frames; + stats->uptc = u.b0.tx_unicast_frames; + stats->mptc = u.b0.tx_multicast_frames; + stats->bptc = u.b0.tx_broadcast_frames; + stats->erpt = u.b0.tx_errors; + stats->ptc = u.b0.tx_good_frames; + } else { + trace_warn(hw, dbg_fw, + "aq2> unknown F/W interface version %u, no statistics", + hw->aq2_iface); + } + + return (0); +} + +/* Only the A0 interface layout carries the link transition counters. */ +static int +aq2_fw_get_link_counters(struct aq_hw *hw, uint32_t *up, uint32_t *down) +{ + union aq2_fw_statistics u; + int err; + + if (hw->aq2_iface != AQ2_FW_INTERFACE_OUT_VERSION_IFACE_VER_A0) + return (ENOTSUP); + + err = aq2_fw_interface_buffer_read(hw, AQ2_FW_INTERFACE_OUT_STATS_REG, + (uint32_t *)&u, sizeof(u)); + if (err != 0) { + trace_error(hw, dbg_fw, + "aq2> link counter read FAILED, error %d", err); + return (err); + } + + *up = u.a0.link_up; + *down = u.a0.link_down; + + return (0); +} + +static int +aq2_fw_get_temp(struct aq_hw *hw, int *temp_mc) +{ + uint32_t raw; + int err; + + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_REG, &raw, sizeof(raw)); + if (err != 0) + return (err); + + /* The F/W zeroes the block until the PHY reports in. */ + if ((raw & AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_READY) == 0) { + trace_detail(hw, dbg_fw, "aq2> PHY health monitor not ready"); + return (ENXIO); + } + + /* F/W reports whole degrees Celsius, signed. */ + *temp_mc = (int)(int8_t)((raw & + AQ2_FW_INTERFACE_OUT_PHY_TEMPERATURE) >> + AQ2_FW_INTERFACE_OUT_PHY_TEMPERATURE_S) * 1000; + + return (0); +} + +/* interface-in thermal_shutdown.shutdown_temperature, whole degC. */ +static int +aq2_fw_get_thermal_limit(struct aq_hw *hw, int *limit_mc) +{ + uint32_t v; + int temp_c; + + v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_REG); + temp_c = (v & AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_TEMP) >> + AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_TEMP_S; + if (temp_c == 0 || temp_c == 0xff) + return (ENXIO); + *limit_mc = temp_c * 1000; + + return (0); +} + +static int +aq2_fw_get_phy_fault(struct aq_hw *hw, uint16_t *fault) +{ + uint32_t health, code; + int err; + + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_REG, &health, + sizeof(health)); + if (err != 0) + return (err); + + if ((health & AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_FAULT) == 0) { + *fault = 0; + return (0); + } + + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_PHY_FAULT_CODE_REG, &code, sizeof(code)); + if (err != 0) + return (err); + + *fault = (uint16_t)(code & AQ2_FW_INTERFACE_OUT_PHY_FAULT_CODE); + + return (0); +} + +static int +aq2_fw_get_phy_hot_warning(struct aq_hw *hw, bool *hot) +{ + uint32_t health; + int err; + + err = aq2_fw_interface_buffer_read(hw, + AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_REG, &health, + sizeof(health)); + if (err != 0) + return (err); + + if ((health & AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_READY) == 0) + return (ENXIO); + + *hot = (health & AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_HOT) != 0; + + return (0); +} + +const struct aq_firmware_ops aq2_fw_ops = { + .reset = aq2_fw_reset, + .set_mode = aq2_fw_set_mode, + .get_mode = aq2_fw_get_mode, + .get_link_info = aq2_fw_get_link_info, + .get_mac_addr = aq2_fw_get_mac_addr, + .get_stats = aq2_fw_get_stats, + .get_link_counters = aq2_fw_get_link_counters, + .get_temp = aq2_fw_get_temp, + .get_phy_fault = aq2_fw_get_phy_fault, + .get_phy_hot_warning = aq2_fw_get_phy_hot_warning, + .phy_reset = NULL, /* A2 clears thermal shutdown on its own reset */ + .thermal_arm = NULL, /* A2 firmware ships thermal shutdown armed */ + .get_thermal_limit = aq2_fw_get_thermal_limit, + .led_control = NULL, +}; |
