// SPDX-License-Identifier: GPL-2.0-only
/* Atlantic Network Driver
 *
 * Copyright (C) 2014-2019 aQuantia Corporation
 * Copyright (C) 2019-2020 Marvell International Ltd.
 */

/* File hw_atl_utils_fw2x.c: Definition of firmware 2.x functions for
 * Atlantic hardware abstraction layer.
 */

#include "../aq_hw.h"
#include "../aq_hw_utils.h"
#include "../aq_pci_func.h"
#include "../aq_ring.h"
#include "../aq_vec.h"
#include "../aq_nic.h"
#include "hw_atl_utils.h"
#include "hw_atl_llh.h"

#define HW_ATL_FW2X_MPI_LED_ADDR         0x31c
#define HW_ATL_FW2X_MPI_RPC_ADDR         0x334

#define HW_ATL_FW2X_MPI_MBOX_ADDR        0x360
#define HW_ATL_FW2X_MPI_EFUSE_ADDR       0x364
#define HW_ATL_FW2X_MPI_CONTROL_ADDR     0x368
#define HW_ATL_FW2X_MPI_CONTROL2_ADDR    0x36C
#define HW_ATL_FW2X_MPI_STATE_ADDR       0x370
#define HW_ATL_FW2X_MPI_STATE2_ADDR      0x374

#define HW_ATL_FW3X_EXT_CONTROL_ADDR     0x378
#define HW_ATL_FW3X_EXT_STATE_ADDR       0x37c

#define HW_ATL_FW3X_PTP_ADJ_LSW_ADDR	 0x50a0
#define HW_ATL_FW3X_PTP_ADJ_MSW_ADDR	 0x50a4

#define HW_ATL_FW2X_CAP_PAUSE            BIT(CAPS_HI_PAUSE)
#define HW_ATL_FW2X_CAP_ASYM_PAUSE       BIT(CAPS_HI_ASYMMETRIC_PAUSE)
#define HW_ATL_FW2X_CAP_SLEEP_PROXY      BIT(CAPS_HI_SLEEP_PROXY)
#define HW_ATL_FW2X_CAP_WOL              BIT(CAPS_HI_WOL)

#define HW_ATL_FW2X_CTRL_WAKE_ON_LINK     BIT(CTRL_WAKE_ON_LINK)
#define HW_ATL_FW2X_CTRL_SLEEP_PROXY      BIT(CTRL_SLEEP_PROXY)
#define HW_ATL_FW2X_CTRL_WOL              BIT(CTRL_WOL)
#define HW_ATL_FW2X_CTRL_LINK_DROP        BIT(CTRL_LINK_DROP)
#define HW_ATL_FW2X_CTRL_PAUSE            BIT(CTRL_PAUSE)
#define HW_ATL_FW2X_CTRL_TEMPERATURE      BIT(CTRL_TEMPERATURE)
#define HW_ATL_FW2X_CTRL_ASYMMETRIC_PAUSE BIT(CTRL_ASYMMETRIC_PAUSE)
#define HW_ATL_FW2X_CTRL_INT_LOOPBACK     BIT(CTRL_INT_LOOPBACK)
#define HW_ATL_FW2X_CTRL_EXT_LOOPBACK     BIT(CTRL_EXT_LOOPBACK)
#define HW_ATL_FW2X_CTRL_DOWNSHIFT        BIT(CTRL_DOWNSHIFT)
#define HW_ATL_FW2X_CTRL_FORCE_RECONNECT  BIT(CTRL_FORCE_RECONNECT)

#define HW_ATL_FW2X_CAP_EEE_1G_MASK      BIT(CAPS_HI_1000BASET_FD_EEE)
#define HW_ATL_FW2X_CAP_EEE_2G5_MASK     BIT(CAPS_HI_2P5GBASET_FD_EEE)
#define HW_ATL_FW2X_CAP_EEE_5G_MASK      BIT(CAPS_HI_5GBASET_FD_EEE)
#define HW_ATL_FW2X_CAP_EEE_10G_MASK     BIT(CAPS_HI_10GBASET_FD_EEE)

#define HW_ATL_FW2X_CAP_MACSEC           BIT(CAPS_LO_MACSEC)

#define HAL_ATLANTIC_WOL_FILTERS_COUNT   8
#define HAL_ATLANTIC_UTILS_FW2X_MSG_WOL  0x0E

#define HW_ATL_FW_VER_LED                0x03010026U
#define HW_ATL_FW_VER_MEDIA_CONTROL      0x0301005aU

struct __packed fw2x_msg_wol_pattern {
	u8 mask[16];
	u32 crc;
};

struct __packed fw2x_msg_wol {
	u32 msg_id;
	u8 hw_addr[ETH_ALEN];
	u8 magic_packet_enabled;
	u8 filter_count;
	struct fw2x_msg_wol_pattern filter[HAL_ATLANTIC_WOL_FILTERS_COUNT];
	u8 link_up_enabled;
	u8 link_down_enabled;
	u16 reserved;
	u32 link_up_timeout;
	u32 link_down_timeout;
};

static int aq_fw2x_set_link_speed(struct aq_hw_s *self, u32 speed);
static int aq_fw2x_set_state(struct aq_hw_s *self,
			     enum hal_atl_utils_fw_state_e state);

static u32 aq_fw2x_mbox_get(struct aq_hw_s *self);
static u32 aq_fw2x_rpc_get(struct aq_hw_s *self);
static int aq_fw2x_settings_get(struct aq_hw_s *self, u32 *addr);
static u32 aq_fw2x_state_get(struct aq_hw_s *self);
static u32 aq_fw2x_state2_get(struct aq_hw_s *self);

static int aq_fw2x_init(struct aq_hw_s *self)
{
	int err = 0;

	/* check 10 times by 1ms */
	err = readx_poll_timeout_atomic(aq_fw2x_mbox_get,
					self, self->mbox_addr,
					self->mbox_addr != 0U,
					1000U, 10000U);

	err = readx_poll_timeout_atomic(aq_fw2x_rpc_get,
					self, self->rpc_addr,
					self->rpc_addr != 0U,
					1000U, 100000U);

	err = aq_fw2x_settings_get(self, &self->settings_addr);

	return err;
}

static int aq_fw2x_deinit(struct aq_hw_s *self)
{
	int err = aq_fw2x_set_link_speed(self, 0);

	if (!err)
		err = aq_fw2x_set_state(self, MPI_DEINIT);

	return err;
}

static enum hw_atl_fw2x_rate link_speed_mask_2fw2x_ratemask(u32 speed)
{
	enum hw_atl_fw2x_rate rate = 0;

	if (speed & AQ_NIC_RATE_10G)
		rate |= FW2X_RATE_10G;

	if (speed & AQ_NIC_RATE_5G)
		rate |= FW2X_RATE_5G;

	if (speed & AQ_NIC_RATE_2G5)
		rate |= FW2X_RATE_2G5;

	if (speed & AQ_NIC_RATE_1G)
		rate |= FW2X_RATE_1G;

	if (speed & AQ_NIC_RATE_100M)
		rate |= FW2X_RATE_100M;

	return rate;
}

static u32 fw2x_to_eee_mask(u32 speed)
{
	u32 rate = 0;

	if (speed & HW_ATL_FW2X_CAP_EEE_10G_MASK)
		rate |= AQ_NIC_RATE_EEE_10G;
	if (speed & HW_ATL_FW2X_CAP_EEE_5G_MASK)
		rate |= AQ_NIC_RATE_EEE_5G;
	if (speed & HW_ATL_FW2X_CAP_EEE_2G5_MASK)
		rate |= AQ_NIC_RATE_EEE_2G5;
	if (speed & HW_ATL_FW2X_CAP_EEE_1G_MASK)
		rate |= AQ_NIC_RATE_EEE_1G;

	return rate;
}

static u32 eee_mask_to_fw2x(u32 speed)
{
	u32 rate = 0;

	if (speed & AQ_NIC_RATE_EEE_10G)
		rate |= HW_ATL_FW2X_CAP_EEE_10G_MASK;
	if (speed & AQ_NIC_RATE_EEE_5G)
		rate |= HW_ATL_FW2X_CAP_EEE_5G_MASK;
	if (speed & AQ_NIC_RATE_EEE_2G5)
		rate |= HW_ATL_FW2X_CAP_EEE_2G5_MASK;
	if (speed & AQ_NIC_RATE_EEE_1G)
		rate |= HW_ATL_FW2X_CAP_EEE_1G_MASK;

	return rate;
}

static int aq_fw2x_set_link_speed(struct aq_hw_s *self, u32 speed)
{
	u32 val = link_speed_mask_2fw2x_ratemask(speed);

	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL_ADDR, val);

	return 0;
}

static void aq_fw2x_upd_flow_control_bits(struct aq_hw_s *self,
					  u32 *mpi_state, u32 fc)
{
	*mpi_state &= ~(HW_ATL_FW2X_CTRL_PAUSE |
			HW_ATL_FW2X_CTRL_ASYMMETRIC_PAUSE);

	switch (fc) {
	/* There is not explicit mode of RX only pause frames,
	 * thus, we join this mode with FC full.
	 * FC full is either Rx, either Tx, or both.
	 */
	case AQ_NIC_FC_FULL:
	case AQ_NIC_FC_RX:
		*mpi_state |= HW_ATL_FW2X_CTRL_PAUSE |
			      HW_ATL_FW2X_CTRL_ASYMMETRIC_PAUSE;
		break;
	case AQ_NIC_FC_TX:
		*mpi_state |= HW_ATL_FW2X_CTRL_ASYMMETRIC_PAUSE;
		break;
	}
}

static void aq_fw2x_upd_eee_rate_bits(struct aq_hw_s *self, u32 *mpi_opts,
				      u32 eee_speeds)
{
	*mpi_opts &= ~(HW_ATL_FW2X_CAP_EEE_1G_MASK |
		       HW_ATL_FW2X_CAP_EEE_2G5_MASK |
		       HW_ATL_FW2X_CAP_EEE_5G_MASK |
		       HW_ATL_FW2X_CAP_EEE_10G_MASK);

	*mpi_opts |= eee_mask_to_fw2x(eee_speeds);
}

static int aq_fw2x_set_state(struct aq_hw_s *self,
			     enum hal_atl_utils_fw_state_e state)
{
	u32 mpi_state = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
	struct aq_nic_cfg_s *cfg = self->aq_nic_cfg;

	switch (state) {
	case MPI_INIT:
		mpi_state &= ~BIT(CAPS_HI_LINK_DROP);
		aq_fw2x_upd_eee_rate_bits(self, &mpi_state, cfg->eee_speeds);
		aq_fw2x_upd_flow_control_bits(self, &mpi_state,
					      self->aq_nic_cfg->fc.req);
		break;
	case MPI_DEINIT:
		mpi_state |= BIT(CAPS_HI_LINK_DROP);
		break;
	case MPI_RESET:
	case MPI_POWER:
		/* No actions */
		break;
	}
	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_state);

	return 0;
}

static int aq_fw2x_update_link_status(struct aq_hw_s *self)
{
	struct aq_hw_link_status_s *link_status = &self->aq_link_status;
	u32 mpi_state;
	u32 speed;

	mpi_state = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE_ADDR);
	speed = mpi_state & (FW2X_RATE_100M | FW2X_RATE_1G |
			     FW2X_RATE_2G5 | FW2X_RATE_5G |
			     FW2X_RATE_10G);

	if (speed) {
		if (speed & FW2X_RATE_10G)
			link_status->mbps = 10000;
		else if (speed & FW2X_RATE_5G)
			link_status->mbps = 5000;
		else if (speed & FW2X_RATE_2G5)
			link_status->mbps = 2500;
		else if (speed & FW2X_RATE_1G)
			link_status->mbps = 1000;
		else if (speed & FW2X_RATE_100M)
			link_status->mbps = 100;
		else
			link_status->mbps = 10000;
	} else {
		link_status->mbps = 0;
	}
	link_status->full_duplex = true;

	return 0;
}

static int aq_fw2x_get_mac_permanent(struct aq_hw_s *self, u8 *mac)
{
	u32 efuse_addr = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_EFUSE_ADDR);
	u32 mac_addr[2] = { 0 };
	int err = 0;

	if (efuse_addr != 0) {
		err = hw_atl_utils_fw_downld_dwords(self,
						    efuse_addr + (40U * 4U),
						    mac_addr,
						    ARRAY_SIZE(mac_addr));
		if (err)
			return err;
		mac_addr[0] = __swab32(mac_addr[0]);
		mac_addr[1] = __swab32(mac_addr[1]);
	}

	ether_addr_copy(mac, (u8 *)mac_addr);

	return err;
}

static int aq_fw2x_update_stats(struct aq_hw_s *self)
{
	u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
	u32 orig_stats_val = mpi_opts & BIT(CAPS_HI_STATISTICS);
	u32 stats_val;
	int err = 0;

	/* Toggle statistics bit for FW to update */
	mpi_opts = mpi_opts ^ BIT(CAPS_HI_STATISTICS);
	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);

	/* Wait FW to report back */
	err = readx_poll_timeout_atomic(aq_fw2x_state2_get,
					self, stats_val,
					orig_stats_val != (stats_val &
					BIT(CAPS_HI_STATISTICS)),
					1U, 10000U);
	if (err)
		return err;

	return hw_atl_utils_update_stats(self);
}

static int aq_fw2x_get_phy_temp(struct aq_hw_s *self, int *temp)
{
	u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
	u32 temp_val = mpi_opts & HW_ATL_FW2X_CTRL_TEMPERATURE;
	u32 phy_temp_offset;
	u32 temp_res;
	int err = 0;
	u32 val;

	phy_temp_offset = self->mbox_addr + offsetof(struct hw_atl_utils_mbox,
						     info.phy_temperature);

	/* Toggle statistics bit for FW to 0x36C.18 (CTRL_TEMPERATURE) */
	mpi_opts = mpi_opts ^ HW_ATL_FW2X_CTRL_TEMPERATURE;
	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
	/* Wait FW to report back */
	err = readx_poll_timeout_atomic(aq_fw2x_state2_get, self, val,
					temp_val !=
					(val & HW_ATL_FW2X_CTRL_TEMPERATURE),
					1U, 10000U);
	err = hw_atl_utils_fw_downld_dwords(self, phy_temp_offset,
					    &temp_res, 1);

	if (err)
		return err;

	/* Convert PHY temperature from 1/256 degree Celsius
	 * to 1/1000 degree Celsius.
	 */
	*temp = (int16_t)(temp_res & 0xFFFF) * 1000 / 256;

	return 0;
}

static int aq_fw2x_set_wol(struct aq_hw_s *self, const u8 *mac)
{
	struct hw_atl_utils_fw_rpc *rpc = NULL;
	struct offload_info *info = NULL;
	u32 wol_bits = 0;
	u32 rpc_size;
	int err = 0;
	u32 val;

	if (self->aq_nic_cfg->wol & WAKE_PHY) {
		aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR,
				HW_ATL_FW2X_CTRL_LINK_DROP);
		readx_poll_timeout_atomic(aq_fw2x_state2_get, self, val,
					  (val &
					   HW_ATL_FW2X_CTRL_LINK_DROP) != 0,
					  1000, 100000);
		wol_bits |= HW_ATL_FW2X_CTRL_WAKE_ON_LINK;
	}

	if (self->aq_nic_cfg->wol & WAKE_MAGIC) {
		wol_bits |= HW_ATL_FW2X_CTRL_SLEEP_PROXY |
			    HW_ATL_FW2X_CTRL_WOL;

		err = hw_atl_utils_fw_rpc_wait(self, &rpc);
		if (err < 0)
			goto err_exit;

		rpc_size = sizeof(*info) +
			   offsetof(struct hw_atl_utils_fw_rpc, fw2x_offloads);
		memset(rpc, 0, rpc_size);
		info = &rpc->fw2x_offloads;
		memcpy(info->mac_addr, mac, ETH_ALEN);
		info->len = sizeof(*info);

		err = hw_atl_utils_fw_rpc_call(self, rpc_size);
		if (err < 0)
			goto err_exit;
	}

	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, wol_bits);

err_exit:
	return err;
}

static int aq_fw2x_set_power(struct aq_hw_s *self, unsigned int power_state,
			     const u8 *mac)
{
	int err = 0;

	if (self->aq_nic_cfg->wol)
		err = aq_fw2x_set_wol(self, mac);

	return err;
}

static int aq_fw2x_send_fw_request(struct aq_hw_s *self,
				   const struct hw_fw_request_iface *fw_req,
				   size_t size)
{
	u32 ctrl2, orig_ctrl2;
	u32 dword_cnt;
	int err = 0;
	u32 val;

	/* Write data to drvIface Mailbox */
	dword_cnt = size / sizeof(u32);
	if (size % sizeof(u32))
		dword_cnt++;
	err = hw_atl_write_fwcfg_dwords(self, (void *)fw_req, dword_cnt);
	if (err < 0)
		goto err_exit;

	/* Toggle statistics bit for FW to update */
	ctrl2 = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
	orig_ctrl2 = ctrl2 & BIT(CAPS_HI_FW_REQUEST);
	ctrl2 = ctrl2 ^ BIT(CAPS_HI_FW_REQUEST);
	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, ctrl2);

	/* Wait FW to report back */
	err = readx_poll_timeout_atomic(aq_fw2x_state2_get, self, val,
					orig_ctrl2 != (val &
						       BIT(CAPS_HI_FW_REQUEST)),
					1U, 10000U);

err_exit:
	return err;
}

static void aq_fw3x_enable_ptp(struct aq_hw_s *self, int enable)
{
	u32 ptp_opts = aq_hw_read_reg(self, HW_ATL_FW3X_EXT_STATE_ADDR);
	u32 all_ptp_features = BIT(CAPS_EX_PHY_PTP_EN) |
						   BIT(CAPS_EX_PTP_GPIO_EN);

	if (enable)
		ptp_opts |= all_ptp_features;
	else
		ptp_opts &= ~all_ptp_features;

	aq_hw_write_reg(self, HW_ATL_FW3X_EXT_CONTROL_ADDR, ptp_opts);
}

static void aq_fw3x_adjust_ptp(struct aq_hw_s *self, uint64_t adj)
{
	aq_hw_write_reg(self, HW_ATL_FW3X_PTP_ADJ_LSW_ADDR,
			(adj >>  0) & 0xffffffff);
	aq_hw_write_reg(self, HW_ATL_FW3X_PTP_ADJ_MSW_ADDR,
			(adj >> 32) & 0xffffffff);
}

static int aq_fw2x_led_control(struct aq_hw_s *self, u32 mode)
{
	if (self->fw_ver_actual < HW_ATL_FW_VER_LED)
		return -EOPNOTSUPP;

	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_LED_ADDR, mode);

	return 0;
}

static int aq_fw2x_set_eee_rate(struct aq_hw_s *self, u32 speed)
{
	u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);

	aq_fw2x_upd_eee_rate_bits(self, &mpi_opts, speed);

	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);

	return 0;
}

static int aq_fw2x_get_eee_rate(struct aq_hw_s *self, u32 *rate,
				u32 *supported_rates)
{
	u32 mpi_state;
	u32 caps_hi;
	int err = 0;
	u32 offset;

	offset = self->mbox_addr + offsetof(struct hw_atl_utils_mbox,
					    info.caps_hi);

	err = hw_atl_utils_fw_downld_dwords(self, offset, &caps_hi, 1);

	if (err)
		return err;

	*supported_rates = fw2x_to_eee_mask(caps_hi);

	mpi_state = aq_fw2x_state2_get(self);
	*rate = fw2x_to_eee_mask(mpi_state);

	return err;
}

static int aq_fw2x_renegotiate(struct aq_hw_s *self)
{
	u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);

	mpi_opts |= BIT(CTRL_FORCE_RECONNECT);

	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);

	return 0;
}

static int aq_fw2x_set_flow_control(struct aq_hw_s *self)
{
	u32 mpi_state = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);

	aq_fw2x_upd_flow_control_bits(self, &mpi_state,
				      self->aq_nic_cfg->fc.req);

	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_state);

	return 0;
}

static u32 aq_fw2x_get_flow_control(struct aq_hw_s *self, u32 *fcmode)
{
	u32 mpi_state = aq_fw2x_state2_get(self);
	*fcmode = 0;

	if (mpi_state & HW_ATL_FW2X_CAP_PAUSE)
		*fcmode |= AQ_NIC_FC_RX;

	if (mpi_state & HW_ATL_FW2X_CAP_ASYM_PAUSE)
		*fcmode |= AQ_NIC_FC_TX;

	return 0;
}

static int aq_fw2x_set_phyloopback(struct aq_hw_s *self, u32 mode, bool enable)
{
	u32 mpi_opts;

	switch (mode) {
	case AQ_HW_LOOPBACK_PHYINT_SYS:
		mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
		if (enable)
			mpi_opts |= HW_ATL_FW2X_CTRL_INT_LOOPBACK;
		else
			mpi_opts &= ~HW_ATL_FW2X_CTRL_INT_LOOPBACK;
		aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
		break;
	case AQ_HW_LOOPBACK_PHYEXT_SYS:
		mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
		if (enable)
			mpi_opts |= HW_ATL_FW2X_CTRL_EXT_LOOPBACK;
		else
			mpi_opts &= ~HW_ATL_FW2X_CTRL_EXT_LOOPBACK;
		aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static u32 aq_fw2x_mbox_get(struct aq_hw_s *self)
{
	return aq_hw_read_reg(self, HW_ATL_FW2X_MPI_MBOX_ADDR);
}

static u32 aq_fw2x_rpc_get(struct aq_hw_s *self)
{
	return aq_hw_read_reg(self, HW_ATL_FW2X_MPI_RPC_ADDR);
}

static int aq_fw2x_settings_get(struct aq_hw_s *self, u32 *addr)
{
	int err = 0;
	u32 offset;

	offset = self->mbox_addr + offsetof(struct hw_atl_utils_mbox,
					    info.setting_address);

	err = hw_atl_utils_fw_downld_dwords(self, offset, addr, 1);

	return err;
}

static u32 aq_fw2x_state_get(struct aq_hw_s *self)
{
	return aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE_ADDR);
}

static u32 aq_fw2x_state2_get(struct aq_hw_s *self)
{
	return aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE2_ADDR);
}

static int aq_fw2x_set_downshift(struct aq_hw_s *self, u32 counter)
{
	int err = 0;
	u32 mpi_opts;
	u32 offset;

	offset = offsetof(struct hw_atl_utils_settings, downshift_retry_count);
	err = hw_atl_write_fwsettings_dwords(self, offset, &counter, 1);
	if (err)
		return err;

	mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
	if (counter)
		mpi_opts |= HW_ATL_FW2X_CTRL_DOWNSHIFT;
	else
		mpi_opts &= ~HW_ATL_FW2X_CTRL_DOWNSHIFT;
	aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);

	return err;
}

static int aq_fw2x_set_media_detect(struct aq_hw_s *self, bool on)
{
	u32 enable;
	u32 offset;

	if (self->fw_ver_actual < HW_ATL_FW_VER_MEDIA_CONTROL)
		return -EOPNOTSUPP;

	offset = offsetof(struct hw_atl_utils_settings, media_detect);
	enable = on;

	return hw_atl_write_fwsettings_dwords(self, offset, &enable, 1);
}

static u32 aq_fw2x_get_link_capabilities(struct aq_hw_s *self)
{
	int err = 0;
	u32 offset;
	u32 val;

	offset = self->mbox_addr +
		 offsetof(struct hw_atl_utils_mbox, info.caps_lo);

	err = hw_atl_utils_fw_downld_dwords(self, offset, &val, 1);

	if (err)
		return 0;

	return val;
}

static int aq_fw2x_send_macsec_req(struct aq_hw_s *hw,
				   struct macsec_msg_fw_request *req,
				   struct macsec_msg_fw_response *response)
{
	u32 low_status, low_req = 0;
	u32 dword_cnt;
	u32 caps_lo;
	u32 offset;
	int err;

	if (!req || !response)
		return -EINVAL;

	caps_lo = aq_fw2x_get_link_capabilities(hw);
	if (!(caps_lo & BIT(CAPS_LO_MACSEC)))
		return -EOPNOTSUPP;

	/* Write macsec request to cfg memory */
	dword_cnt = (sizeof(*req) + sizeof(u32) - 1) / sizeof(u32);
	err = hw_atl_write_fwcfg_dwords(hw, (void *)req, dword_cnt);
	if (err < 0)
		return err;

	/* Toggle 0x368.CAPS_LO_MACSEC bit */
	low_req = aq_hw_read_reg(hw, HW_ATL_FW2X_MPI_CONTROL_ADDR);
	low_req ^= HW_ATL_FW2X_CAP_MACSEC;
	aq_hw_write_reg(hw, HW_ATL_FW2X_MPI_CONTROL_ADDR, low_req);

	/* Wait FW to report back */
	err = readx_poll_timeout_atomic(aq_fw2x_state_get, hw, low_status,
		low_req != (low_status & BIT(CAPS_LO_MACSEC)), 1U, 10000U);
	if (err)
		return -EIO;

	/* Read status of write operation */
	offset = hw->rpc_addr + sizeof(u32);
	err = hw_atl_utils_fw_downld_dwords(hw, offset, (u32 *)(void *)response,
					    sizeof(*response) / sizeof(u32));

	return err;
}

const struct aq_fw_ops aq_fw_2x_ops = {
	.init               = aq_fw2x_init,
	.deinit             = aq_fw2x_deinit,
	.reset              = NULL,
	.renegotiate        = aq_fw2x_renegotiate,
	.get_mac_permanent  = aq_fw2x_get_mac_permanent,
	.set_link_speed     = aq_fw2x_set_link_speed,
	.set_state          = aq_fw2x_set_state,
	.update_link_status = aq_fw2x_update_link_status,
	.update_stats       = aq_fw2x_update_stats,
	.get_mac_temp       = NULL,
	.get_phy_temp       = aq_fw2x_get_phy_temp,
	.set_power          = aq_fw2x_set_power,
	.set_eee_rate       = aq_fw2x_set_eee_rate,
	.get_eee_rate       = aq_fw2x_get_eee_rate,
	.set_flow_control   = aq_fw2x_set_flow_control,
	.get_flow_control   = aq_fw2x_get_flow_control,
	.send_fw_request    = aq_fw2x_send_fw_request,
	.enable_ptp         = aq_fw3x_enable_ptp,
	.led_control        = aq_fw2x_led_control,
	.set_phyloopback    = aq_fw2x_set_phyloopback,
	.set_downshift      = aq_fw2x_set_downshift,
	.set_media_detect   = aq_fw2x_set_media_detect,
	.adjust_ptp         = aq_fw3x_adjust_ptp,
	.get_link_capabilities = aq_fw2x_get_link_capabilities,
	.send_macsec_req    = aq_fw2x_send_macsec_req,
}