Contributors: 10
Author Tokens Token Proportion Commits Commit Proportion
Grzegorz Nitka 1124 83.01% 2 9.52%
Arkadiusz Kubalewski 98 7.24% 2 9.52%
Karol Kolacinski 36 2.66% 3 14.29%
Jacob E Keller 33 2.44% 4 19.05%
Sergey Temerkhanov 24 1.77% 2 9.52%
Anirudh Venkataramanan 17 1.26% 4 19.05%
SF Markus Elfring 10 0.74% 1 4.76%
Kohei Enju 7 0.52% 1 4.76%
Brett Creeley 3 0.22% 1 4.76%
Michal Michalik 2 0.15% 1 4.76%
Total 1354 21


// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2026 Intel Corporation */

#include "ice.h"
#include "ice_cpi.h"
#include "ice_txclk.h"

#define ICE_PHY0	0
#define ICE_PHY1	1

/**
 * ice_txclk_get_pin - map TX reference clock to its DPLL pin
 * @pf: pointer to the PF structure
 * @ref_clk: TX reference clock selection
 *
 * Return the DPLL pin corresponding to a given external TX reference
 * clock. Only external TX reference clocks (SYNCE and EREF0) are
 * represented as DPLL pins. The internal ENET (TXCO) clock has no
 * associated DPLL pin and therefore yields %NULL.
 *
 * This helper is used when emitting DPLL pin change notifications
 * after TX reference clock transitions have been verified.
 *
 * Return: Pointer to the corresponding struct dpll_pin, or %NULL if
 *         the TX reference clock has no DPLL pin representation.
 */
struct dpll_pin *
ice_txclk_get_pin(struct ice_pf *pf, enum ice_e825c_ref_clk ref_clk)
{
	switch (ref_clk) {
	case ICE_REF_CLK_SYNCE:
		return pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX].pin;
	case ICE_REF_CLK_EREF0:
		return pf->dplls.txclks[E825_EXT_EREF_PIN_IDX].pin;
	case ICE_REF_CLK_ENET:
	default:
		return NULL;
	}
}

/**
 * ice_txclk_enable_peer - Enable required TX reference clock on peer PHY
 * @pf: pointer to the PF structure
 * @clk: TX reference clock that must be enabled
 *
 * Some TX reference clocks on E825-class devices (SyncE and EREF0) must
 * be enabled on both PHY complexes to allow proper routing:
 *
 *   - SyncE must be enabled on both PHYs when used by PHY0
 *   - EREF0 must be enabled on both PHYs when used by PHY1
 *
 * If the requested clock is not yet enabled on the peer PHY, enable it.
 * ENET does not require duplication and is ignored.
 *
 * Return: 0 on success or negative error code on failure.
 */
static int ice_txclk_enable_peer(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
{
	struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
	bool peer_clk_in_use;
	u8 port_num, phy;
	int err;

	if (clk == ICE_REF_CLK_ENET)
		return 0;

	if (IS_ERR_OR_NULL(ctrl_pf)) {
		dev_err(ice_pf_to_dev(pf),
			"Can't enable tx-clk on peer: no controlling PF\n");
		return -EINVAL;
	}

	port_num = pf->ptp.port.port_num;
	phy = port_num / pf->hw.ptp.ports_per_phy;
	peer_clk_in_use = true;

	/* Hold ctrl_pf->dplls.lock across both the peer-usage check and
	 * the enable AQ command so that two PFs racing to enable the same
	 * peer-PHY clock cannot both observe peer_clk_in_use == false and
	 * issue duplicate enables.
	 */
	mutex_lock(&ctrl_pf->dplls.lock);
	if (clk == ICE_REF_CLK_SYNCE && phy == ICE_PHY0)
		peer_clk_in_use = ice_txclk_any_port_uses(ctrl_pf,
							  ICE_PHY1,
							  clk);
	else if (clk == ICE_REF_CLK_EREF0 && phy == ICE_PHY1)
		peer_clk_in_use = ice_txclk_any_port_uses(ctrl_pf,
							  ICE_PHY0,
							  clk);

	if ((clk == ICE_REF_CLK_SYNCE && phy == ICE_PHY0 && !peer_clk_in_use) ||
	    (clk == ICE_REF_CLK_EREF0 && phy == ICE_PHY1 && !peer_clk_in_use)) {
		u8 peer_phy = phy ? ICE_PHY0 : ICE_PHY1;

		err = ice_cpi_ena_dis_clk_ref(&pf->hw, peer_phy, clk, true);
		if (err) {
			mutex_unlock(&ctrl_pf->dplls.lock);
			dev_err(ice_pf_to_dev(pf),
				"Failed to enable the %u TX clock for the %u PHY\n",
				clk, peer_phy);
			return err;
		}
	}
	mutex_unlock(&ctrl_pf->dplls.lock);

	return 0;
}

#define ICE_REFCLK_USER_TO_AQ_IDX(x) ((x) + 1)

/**
 * ice_txclk_set_clk - Set Tx reference clock
 * @pf: pointer to pf structure
 * @clk: new Tx clock
 *
 * Return: 0 on success, negative value otherwise.
 */
int ice_txclk_set_clk(struct ice_pf *pf, enum ice_e825c_ref_clk clk)
{
	struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
	struct ice_port_info *port_info;
	bool clk_in_use;
	u8 port_num, phy;
	int err;

	if (pf->ptp.port.tx_clk == clk)
		return 0;

	if (IS_ERR_OR_NULL(ctrl_pf)) {
		dev_err(ice_pf_to_dev(pf),
			"Can't set tx-clk: no controlling PF\n");
		return -EINVAL;
	}

	if (!test_bit(ICE_FLAG_DPLL, ctrl_pf->flags)) {
		dev_err(ice_pf_to_dev(pf),
			"Can't set tx-clk: ctrl PF DPLL not available\n");
		return -EOPNOTSUPP;
	}

	port_num = pf->ptp.port.port_num;
	phy = port_num / pf->hw.ptp.ports_per_phy;
	port_info = pf->hw.port_info;

	/* Hold ctrl_pf->dplls.lock across both the usage check and the
	 * enable AQ command so that two PFs racing to switch to the same
	 * (phy, clk) cannot both observe clk_in_use == false and issue
	 * duplicate enables. The tx_refclks bitmap is updated only later
	 * by ice_txclk_update_and_notify() after link-up, so without this
	 * the check-then-act window is wide open.
	 */
	mutex_lock(&ctrl_pf->dplls.lock);
	clk_in_use = ice_txclk_any_port_uses(ctrl_pf, phy, clk);
	if (!clk_in_use) {
		err = ice_cpi_ena_dis_clk_ref(&pf->hw, phy, clk, true);
		if (err) {
			mutex_unlock(&ctrl_pf->dplls.lock);
			dev_err(ice_pf_to_dev(pf), "Failed to enable the %u TX clock for the %u PHY\n",
				clk, phy);
			return err;
		}
	}
	mutex_unlock(&ctrl_pf->dplls.lock);

	if (!clk_in_use) {
		err = ice_txclk_enable_peer(pf, clk);
		if (err)
			return err;
	}

	/* We are ready to switch to the new TX clk. */
	err = ice_aq_set_link_restart_an(port_info, true, NULL,
					 ICE_REFCLK_USER_TO_AQ_IDX(clk));
	if (err) {
		dev_err(ice_pf_to_dev(pf),
			"AN restart AQ command failed with err %d\n",
			err);
		return err;
	}

	/* Clear txclk_switch_requested only after the AN restart AQ has been
	 * accepted by FW. Clearing earlier would race with any asynchronous
	 * link-up event: ice_txclk_update_and_notify() would observe the
	 * cleared flag, read the stale SERDES selector, and misinterpret the
	 * not-yet-applied switch as a HW rejection. Only clear if no newer
	 * request has overwritten tx_clk_req while we were dropping locks.
	 */
	mutex_lock(&pf->dplls.lock);
	if (pf->ptp.port.tx_clk_req == clk)
		pf->dplls.txclk_switch_requested = false;
	mutex_unlock(&pf->dplls.lock);

	return 0;
}

/**
 * ice_txclk_update_and_notify - Validate TX reference clock switching
 * @pf: pointer to PF structure
 *
 * After a link-up event, verify whether the previously requested TX reference
 * clock transition actually succeeded. The SERDES reference selector reflects
 * the effective hardware choice, which may differ from the requested clock
 * when Auto-Negotiation or firmware applies additional policy.
 *
 * If the hardware-selected clock differs from the requested one, update the
 * software state accordingly and stop further processing.
 *
 * When the switch is successful, update the per‑PHY usage bitmaps so that the
 * driver knows which reference clock is currently in use by this port.
 *
 * This function does not initiate a clock switch; it only validates the result
 * of a previously triggered transition and performs cleanup of unused clocks.
 */
void ice_txclk_update_and_notify(struct ice_pf *pf)
{
	struct ice_ptp_port *ptp_port = &pf->ptp.port;
	struct ice_pf *ctrl_pf = ice_get_ctrl_pf(pf);
	struct dpll_pin *old_pin = NULL;
	struct dpll_pin *new_pin = NULL;
	struct ice_hw *hw = &pf->hw;
	enum ice_e825c_ref_clk clk;
	bool notify_dpll = false;
	int err;
	u8 phy;

	phy = ptp_port->port_num / hw->ptp.ports_per_phy;

	/* Hold txclk_notify_rwsem for read across the entire critical
	 * region, including the out-of-lock dpll_*_change_ntf() calls
	 * below. ice_dpll_deinit() takes the write side to wait for all
	 * in-flight notifications to complete before freeing pins and the
	 * TXC DPLL device, preventing a use-after-free on rmmod.
	 */
	down_read(&pf->dplls.txclk_notify_rwsem);
	mutex_lock(&pf->dplls.lock);
	/* Bail out if DPLL subsystem is being torn down. ice_dpll_deinit()
	 * clears ICE_FLAG_DPLL before freeing pins and the dpll device, so a
	 * cleared flag under the lock means those objects can no longer be
	 * safely dereferenced.
	 */
	if (!test_bit(ICE_FLAG_DPLL, pf->flags)) {
		mutex_unlock(&pf->dplls.lock);
		goto out;
	}
	/* If a switch is still pending, the link-up event preceded the
	 * worker's AN restart. Hardware hasn't applied the new clock yet,
	 * so reading the SERDES selector now would produce a false failure.
	 * Let the worker run first; the link-up that follows the AN restart
	 * will trigger the verification.
	 */
	if (pf->dplls.txclk_switch_requested) {
		mutex_unlock(&pf->dplls.lock);
		goto out;
	}
	/* no TX clock change requested */
	if (pf->ptp.port.tx_clk == pf->ptp.port.tx_clk_req) {
		mutex_unlock(&pf->dplls.lock);
		goto out;
	}
	/* verify current Tx reference settings */
	err = ice_get_serdes_ref_sel_e825c(hw,
					   ptp_port->port_num,
					   &clk);
	if (err) {
		mutex_unlock(&pf->dplls.lock);
		goto out;
	}

	if (clk != pf->ptp.port.tx_clk_req) {
		dev_warn(ice_pf_to_dev(pf),
			 "Failed to switch tx-clk for phy %d and clk %u (current: %u)\n",
			 phy, pf->ptp.port.tx_clk_req, clk);
		old_pin = ice_txclk_get_pin(pf, pf->ptp.port.tx_clk_req);
		new_pin = ice_txclk_get_pin(pf, clk);
		pf->ptp.port.tx_clk = clk;
		pf->ptp.port.tx_clk_req = clk;
		/* Update the reference clock bitmap to match the hardware
		 * clock that was actually accepted, so that
		 * ice_txclk_any_port_uses() reflects reality even on failure.
		 * The map is owned by ctrl_pf; take its lock per documented
		 * order (pf->dplls.lock first, then ctrl_pf->dplls.lock) so
		 * readers on other PFs observe a consistent snapshot.
		 */
		if (!IS_ERR_OR_NULL(ctrl_pf)) {
			if (ctrl_pf != pf)
				mutex_lock(&ctrl_pf->dplls.lock);
			for (int i = 0; i < ICE_REF_CLK_MAX; i++) {
				if (clk == i)
					set_bit(ptp_port->port_num,
						&ctrl_pf->ptp.tx_refclks[phy][i]);
				else
					clear_bit(ptp_port->port_num,
						  &ctrl_pf->ptp.tx_refclks[phy][i]);
			}
			if (ctrl_pf != pf)
				mutex_unlock(&ctrl_pf->dplls.lock);
		}
		goto err_notify;
	}

	old_pin = ice_txclk_get_pin(pf, pf->ptp.port.tx_clk);
	pf->ptp.port.tx_clk = clk;
	pf->ptp.port.tx_clk_req = clk;

	if (IS_ERR_OR_NULL(ctrl_pf)) {
		dev_err(ice_pf_to_dev(pf),
			"Can't set tx-clk: no controlling PF\n");
		goto err_notify;
	}

	/* update Tx reference clock usage map; map is owned by ctrl_pf,
	 * take its lock per documented order so readers on other PFs see
	 * a consistent view.
	 */
	if (ctrl_pf != pf)
		mutex_lock(&ctrl_pf->dplls.lock);
	for (int i = 0; i < ICE_REF_CLK_MAX; i++)
		if (clk == i)
			set_bit(ptp_port->port_num,
				&ctrl_pf->ptp.tx_refclks[phy][i]);
		else
			clear_bit(ptp_port->port_num,
				  &ctrl_pf->ptp.tx_refclks[phy][i]);
	if (ctrl_pf != pf)
		mutex_unlock(&ctrl_pf->dplls.lock);

err_notify:
	/* Update TXC DPLL lock status based on effective TX clk, while still
	 * holding the lock to prevent concurrent link-up events from racing
	 * on dpll_state.
	 */
	if (!IS_ERR_OR_NULL(pf->dplls.txc.dpll)) {
		enum dpll_lock_status new_lock = ice_txclk_lock_status(clk);

		if (pf->dplls.txc.dpll_state != new_lock) {
			pf->dplls.txc.dpll_state = new_lock;
			notify_dpll = true;
		}
	}
	mutex_unlock(&pf->dplls.lock);

	/* Notify TX clk pins state transition */
	if (old_pin)
		dpll_pin_change_ntf(old_pin);
	if (new_pin)
		dpll_pin_change_ntf(new_pin);

	if (notify_dpll && !IS_ERR_OR_NULL(pf->dplls.txc.dpll))
		dpll_device_change_ntf(pf->dplls.txc.dpll);

out:
	up_read(&pf->dplls.txclk_notify_rwsem);
}