#define ICE_DPLL_SW_PIN_INPUT_BASE_QSFP 6
#define ICE_DPLL_SW_PIN_OUTPUT_BASE 0
+#define E825_EXT_EREF_PIN_IDX 0
+#define E825_EXT_SYNCE_PIN_IDX 1
+#define E825_RCLK_PARENT_0_PIN_IDX 0
+#define E825_RCLK_PARENT_1_PIN_IDX 1
+
#define ICE_DPLL_PIN_SW_INPUT_ABS(in_idx) \
(ICE_DPLL_SW_PIN_INPUT_BASE_SFP + (in_idx))
* @ICE_DPLL_PIN_TYPE_OUTPUT: output pin
* @ICE_DPLL_PIN_TYPE_RCLK_INPUT: recovery clock input pin
* @ICE_DPLL_PIN_TYPE_SOFTWARE: software controlled SMA/U.FL pins
+ * @ICE_DPLL_PIN_TYPE_TXCLK: transmit clock reference input pin
*/
enum ice_dpll_pin_type {
ICE_DPLL_PIN_INVALID,
ICE_DPLL_PIN_TYPE_OUTPUT,
ICE_DPLL_PIN_TYPE_RCLK_INPUT,
ICE_DPLL_PIN_TYPE_SOFTWARE,
+ ICE_DPLL_PIN_TYPE_TXCLK,
};
static const char * const pin_type_name[] = {
[ICE_DPLL_PIN_TYPE_OUTPUT] = "output",
[ICE_DPLL_PIN_TYPE_RCLK_INPUT] = "rclk-input",
[ICE_DPLL_PIN_TYPE_SOFTWARE] = "software",
+ [ICE_DPLL_PIN_TYPE_TXCLK] = "txclk-input",
};
static const char * const ice_dpll_sw_pin_sma[] = { "SMA1", "SMA2" };
static const char * const ice_dpll_sw_pin_ufl[] = { "U.FL1", "U.FL2" };
+static const char * const ice_dpll_ext_eref_pin = "EXT_EREF0";
+static const char * const ice_dpll_fwnode_ext_synce = "clk_ref_synce";
static const struct dpll_pin_frequency ice_esync_range[] = {
DPLL_PIN_FREQUENCY_RANGE(0, DPLL_PIN_FREQUENCY_1_HZ),
return ret;
}
+/**
+ * ice_dpll_txclk_state_on_dpll_set - set a state on TX clk pin
+ * @pin: pointer to a pin
+ * @pin_priv: private data pointer passed on pin registration
+ * @dpll: registered dpll pointer
+ * @dpll_priv: private data pointer passed on dpll registration
+ * @state: state to be set on pin
+ * @extack: error reporting
+ *
+ * Dpll subsystem callback, set a state of a Tx reference clock pin
+ *
+ * Return:
+ * * negative - failure
+ */
+static int
+ice_dpll_txclk_state_on_dpll_set(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ /*
+ * TODO: set HW accordingly to selected TX reference clock.
+ * To be added in the follow up patches.
+ */
+ return -EOPNOTSUPP;
+}
+
+/**
+ * ice_dpll_txclk_state_on_dpll_get - get a state of Tx clk reference pin
+ * @pin: pointer to a pin
+ * @pin_priv: private data pointer passed on pin registration
+ * @dpll: registered dpll pointer
+ * @dpll_priv: private data pointer passed on dpll registration
+ * @state: on success holds pin state on parent pin
+ * @extack: error reporting
+ *
+ * dpll subsystem callback, get a state of a TX clock reference pin.
+ *
+ * Return:
+ * * 0 - success
+ */
+static int
+ice_dpll_txclk_state_on_dpll_get(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ /*
+ * TODO: query HW status to determine if the TX reference is selected.
+ * To be added in the follow up patches.
+ */
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+
+ return 0;
+}
+
static const struct dpll_pin_ops ice_dpll_rclk_ops = {
.state_on_pin_set = ice_dpll_rclk_state_on_pin_set,
.state_on_pin_get = ice_dpll_rclk_state_on_pin_get,
.direction_get = ice_dpll_input_direction,
};
+static const struct dpll_pin_ops ice_dpll_txclk_ops = {
+ .state_on_dpll_set = ice_dpll_txclk_state_on_dpll_set,
+ .state_on_dpll_get = ice_dpll_txclk_state_on_dpll_get,
+ .direction_get = ice_dpll_input_direction,
+};
+
static const struct dpll_pin_ops ice_dpll_pin_sma_ops = {
.state_on_dpll_set = ice_dpll_sma_pin_state_set,
.state_on_dpll_get = ice_dpll_sw_pin_state_get,
{
int i;
- for (i = 0; i < count; i++)
- if (!pins[i].hidden)
- dpll_pin_unregister(dpll, pins[i].pin, ops, &pins[i]);
+ for (i = 0; i < count; i++) {
+ if (pins[i].hidden)
+ continue;
+ if (IS_ERR_OR_NULL(pins[i].pin))
+ continue;
+ dpll_pin_unregister(dpll, pins[i].pin, ops, &pins[i]);
+ }
}
/**
return !IS_ERR_OR_NULL(pin->fwnode);
}
+static bool ice_dpll_fwnode_eq(const struct fwnode_handle *a,
+ const struct fwnode_handle *b)
+{
+ return a && a == b;
+}
+
static void ice_dpll_pin_notify_work(struct work_struct *work)
{
struct ice_dpll_pin_work *w = container_of(work,
struct ice_dpll_pin_work,
work);
struct ice_dpll_pin *pin, *parent = w->pin;
+ bool is_tx_synce_parent = false;
struct ice_pf *pf = parent->pf;
+ bool is_rclk_parent = false;
int ret;
wait_for_completion(&pf->dplls.dpll_init);
if (!test_bit(ICE_FLAG_DPLL, pf->flags))
goto out; /* DPLL initialization failed */
+ /* Decide which parent we are handling, defensively checking FWNs */
+ for (int i = 0; i < pf->dplls.rclk.num_parents; i++) {
+ if (ice_dpll_fwnode_eq(parent->fwnode,
+ pf->dplls.inputs[i].fwnode)) {
+ is_rclk_parent = true;
+ break;
+ }
+ }
+
+ is_tx_synce_parent =
+ ice_dpll_fwnode_eq(parent->fwnode,
+ pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX].fwnode);
+ if (!is_rclk_parent && !is_tx_synce_parent)
+ goto out;
+
switch (w->action) {
case DPLL_PIN_CREATED:
if (!IS_ERR_OR_NULL(parent->pin)) {
goto out;
}
- /* Register rclk pin */
- pin = &pf->dplls.rclk;
- ret = dpll_pin_on_pin_register(parent->pin, pin->pin,
- &ice_dpll_rclk_ops, pin);
- if (ret) {
- dev_err(ice_pf_to_dev(pf),
- "Failed to register pin: %pe\n", ERR_PTR(ret));
- dpll_pin_put(parent->pin, &parent->tracker);
- parent->pin = NULL;
- goto out;
+ if (is_rclk_parent) {
+ /* Register rclk pin via on-pin relationship */
+ pin = &pf->dplls.rclk;
+ ret = dpll_pin_on_pin_register(parent->pin, pin->pin,
+ &ice_dpll_rclk_ops, pin);
+ if (ret) {
+ dev_err(ice_pf_to_dev(pf),
+ "RCLK pin register failed: %pe\n",
+ ERR_PTR(ret));
+ goto drop_parent_ref;
+ }
+ } else if (is_tx_synce_parent) {
+ /* Register TX-CLK SYNCE pin directly to TXC DPLL */
+ pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX];
+ ret = dpll_pin_register(pf->dplls.txc.dpll, pin->pin,
+ &ice_dpll_txclk_ops, pin);
+ if (ret) {
+ dev_err(ice_pf_to_dev(pf),
+ "TX SYNCE pin register failed: %pe\n",
+ ERR_PTR(ret));
+ goto drop_parent_ref;
+ }
}
break;
case DPLL_PIN_DELETED:
goto out;
}
- /* Unregister rclk pin */
- pin = &pf->dplls.rclk;
- dpll_pin_on_pin_unregister(parent->pin, pin->pin,
- &ice_dpll_rclk_ops, pin);
-
+ if (is_rclk_parent) {
+ /* Unregister rclk pin */
+ pin = &pf->dplls.rclk;
+ dpll_pin_on_pin_unregister(parent->pin, pin->pin,
+ &ice_dpll_rclk_ops, pin);
+ } else if (is_tx_synce_parent) {
+ /* Unregister TX-CLK SYNCE pin from TXC DPLL */
+ pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX];
+ dpll_pin_unregister(pf->dplls.txc.dpll, pin->pin,
+ &ice_dpll_txclk_ops, pin);
+ }
+drop_parent_ref:
/* Drop fwnode pin reference */
dpll_pin_put(parent->pin, &parent->tracker);
parent->pin = NULL;
if (pin->fwnode != info->fwnode)
return NOTIFY_DONE; /* Not this pin */
+ /* Ignore notification which are the outcome of internal pin
+ * registration/unregistration calls - synce pin case.
+ */
+ if (info->src_clock_id == pin->pf->dplls.clock_id)
+ return NOTIFY_DONE;
+
work = kzalloc_obj(*work);
if (!work)
return NOTIFY_DONE;
}
static void
-ice_dpll_deinit_fwnode_pin(struct ice_dpll_pin *pin)
+ice_dpll_stop_fwnode_pin_activity(struct ice_dpll_pin *pin, bool flush)
{
unregister_dpll_notifier(&pin->nb);
- flush_workqueue(pin->pf->dplls.wq);
+ if (flush)
+ flush_workqueue(pin->pf->dplls.wq);
+}
+
+static void
+ice_dpll_release_fwnode_pin(struct ice_dpll_pin *pin)
+{
if (!IS_ERR_OR_NULL(pin->pin)) {
dpll_pin_put(pin->pin, &pin->tracker);
pin->pin = NULL;
pin->fwnode = NULL;
}
+static void
+ice_dpll_deinit_fwnode_pin(struct ice_dpll_pin *pin)
+{
+ ice_dpll_stop_fwnode_pin_activity(pin, true);
+ ice_dpll_release_fwnode_pin(pin);
+}
+
static void
ice_dpll_deinit_fwnode_pins(struct ice_pf *pf, struct ice_dpll_pin *pins,
int start_idx)
destroy_workqueue(pf->dplls.wq);
}
+static int ice_dpll_deinit_txclk_pins(struct ice_pf *pf)
+{
+ struct ice_dpll_pin *synce_pin = &pf->dplls.txclks[E825_EXT_SYNCE_PIN_IDX];
+ struct ice_dpll *dt = &pf->dplls.txc;
+
+ ice_dpll_stop_fwnode_pin_activity(synce_pin, true);
+ ice_dpll_unregister_pins(dt->dpll, pf->dplls.txclks,
+ &ice_dpll_txclk_ops,
+ ARRAY_SIZE(pf->dplls.txclks));
+ ice_dpll_release_pins(&pf->dplls.txclks[E825_EXT_EREF_PIN_IDX], 1);
+ ice_dpll_release_fwnode_pin(synce_pin);
+ return 0;
+}
+
/**
* ice_dpll_deinit_pins - deinitialize direct pins
* @pf: board private structure
struct ice_dpll *dp = &d->pps;
ice_dpll_deinit_rclk_pin(pf);
- if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825)
+ if (pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) {
+ ice_dpll_deinit_txclk_pins(pf);
ice_dpll_deinit_fwnode_pins(pf, pf->dplls.inputs, 0);
+ }
if (cgu) {
ice_dpll_unregister_pins(dp->dpll, inputs, &ice_dpll_input_ops,
num_inputs);
return 0;
error:
+ /*
+ * A notifier worker may already be queued and blocked on dpll_init;
+ * release it so the per-pin flush below does not deadlock.
+ */
+ complete_all(&pf->dplls.dpll_init);
while (i--)
ice_dpll_deinit_fwnode_pin(&pins[start_idx + i]);
return ret;
}
+static int ice_dpll_init_txclk_pins(struct ice_pf *pf, int start_idx)
+{
+ struct ice_dpll_pin *ref_pin = pf->dplls.txclks;
+ struct ice_dpll *txc = &pf->dplls.txc;
+ int ret;
+
+ /* Configure EXT_EREF0 pin */
+ ret = ice_dpll_get_pins(pf, ref_pin, start_idx, 1, pf->dplls.clock_id);
+ if (ret)
+ return ret;
+ ret = dpll_pin_register(txc->dpll, ref_pin->pin, &ice_dpll_txclk_ops,
+ ref_pin);
+ if (ret)
+ goto err_release_ext_eref;
+
+ /*
+ * Configure EXT_SYNCE pin (fwnode-backed).
+ * The pin may not yet be available; in that case registration
+ * will be deferred via the notifier path.
+ */
+ ref_pin++;
+ ret = ice_dpll_init_fwnode_pin(ref_pin, ice_dpll_fwnode_ext_synce);
+ if (ret)
+ goto err_unregister_ext_eref;
+
+ if (IS_ERR_OR_NULL(ref_pin->pin)) {
+ dev_dbg(ice_pf_to_dev(pf),
+ "Tx-clk SYNCE pin not registered yet\n");
+ return 0;
+ }
+
+ ret = dpll_pin_register(txc->dpll, ref_pin->pin, &ice_dpll_txclk_ops,
+ ref_pin);
+ if (ret)
+ goto err_deinit_synce;
+
+ return 0;
+
+err_deinit_synce:
+ /*
+ * Avoid deadlock against notifier workers blocked on dpll_init.
+ * The outer init error path will complete dpll_init and flush the
+ * shared workqueue before destroying it.
+ */
+ ice_dpll_stop_fwnode_pin_activity(ref_pin, false);
+ ice_dpll_release_fwnode_pin(ref_pin);
+err_unregister_ext_eref:
+ dpll_pin_unregister(txc->dpll,
+ pf->dplls.txclks[E825_EXT_EREF_PIN_IDX].pin,
+ &ice_dpll_txclk_ops,
+ &pf->dplls.txclks[E825_EXT_EREF_PIN_IDX]);
+
+err_release_ext_eref:
+ ice_dpll_release_pins(&pf->dplls.txclks[E825_EXT_EREF_PIN_IDX], 1);
+
+ return ret;
+}
+
/**
* ice_dpll_init_pins_e825 - init pins and register pins with a dplls
* @pf: board private structure
ret = ice_dpll_init_rclk_pin(pf, DPLL_PIN_IDX_UNSPEC,
&ice_dpll_rclk_ops);
+
+ if (ret)
+ goto unregister_pins;
+
+ ret = ice_dpll_init_txclk_pins(pf, 0);
+ if (ret)
+ ice_dpll_deinit_rclk_pin(pf);
+
+unregister_pins:
if (ret) {
/* Inform DPLL notifier works that DPLL init was finished
* unsuccessfully (ICE_DPLL_FLAG not set).
static void
ice_dpll_deinit_dpll(struct ice_pf *pf, struct ice_dpll *d, bool cgu)
{
- if (cgu)
+ if (cgu || pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825)
dpll_device_unregister(d->dpll, d->ops, d);
dpll_device_put(d->dpll, &d->tracker);
}
return ret;
}
d->pf = pf;
- if (cgu) {
+ if (cgu || pf->hw.mac_type == ICE_MAC_GENERIC_3K_E825) {
const struct dpll_device_ops *ops = &ice_dpll_ops;
if (type == DPLL_TYPE_PPS && ice_dpll_is_pps_phase_monitor(pf))
ops = &ice_dpll_pom_ops;
- ice_dpll_update_state(pf, d, true);
+ if (cgu)
+ ice_dpll_update_state(pf, d, true);
ret = dpll_device_register(d->dpll, type, ops, d);
if (ret) {
dpll_device_put(d->dpll, &d->tracker);
return 0;
}
+/**
+ * ice_dpll_init_info_txclk_pins_e825c - initializes tx-clk pins information
+ * @pf: board private structure
+ *
+ * Init information for tx-clks pin, cache them in pf->dplls.txclks
+ *
+ * Return:
+ * * 0 - success
+ */
+static int ice_dpll_init_info_txclk_pins_e825c(struct ice_pf *pf)
+{
+ struct ice_dpll_pin *tx_pin;
+
+ for (int i = 0; i < ICE_DPLL_TXCLK_NUM_MAX; i++) {
+ tx_pin = &pf->dplls.txclks[i];
+ tx_pin->prop.type = DPLL_PIN_TYPE_EXT;
+ tx_pin->prop.capabilities |=
+ DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ tx_pin->pf = pf;
+ if (i == E825_EXT_EREF_PIN_IDX) {
+ tx_pin->prop.board_label = ice_dpll_ext_eref_pin;
+ tx_pin->tx_ref_src = ICE_REF_CLK_EREF0;
+ } else if (i == E825_EXT_SYNCE_PIN_IDX) {
+ tx_pin->tx_ref_src = ICE_REF_CLK_SYNCE;
+ }
+ }
+
+ return 0;
+}
+
/**
* ice_dpll_init_pins_info - init pins info wrapper
* @pf: board private structure
return ice_dpll_init_info_rclk_pin(pf);
case ICE_DPLL_PIN_TYPE_SOFTWARE:
return ice_dpll_init_info_sw_pins(pf);
+
+ case ICE_DPLL_PIN_TYPE_TXCLK:
+ return ice_dpll_init_info_txclk_pins_e825c(pf);
default:
return -EINVAL;
}
static int ice_dpll_init_info_e825c(struct ice_pf *pf)
{
struct ice_dplls *d = &pf->dplls;
+ struct ice_dpll *dt = &d->txc;
int ret = 0;
int i;
d->clock_id = ice_generate_clock_id(pf);
d->num_inputs = ICE_SYNCE_CLK_NUM;
+ dt->dpll_state = DPLL_LOCK_STATUS_UNLOCKED;
+ dt->mode = DPLL_MODE_MANUAL;
+ dt->dpll_idx = pf->ptp.port.port_num;
d->inputs = kzalloc_objs(*d->inputs, d->num_inputs);
if (!d->inputs)
ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_RCLK_INPUT);
if (ret)
goto deinit_info;
+
+ ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_TXCLK);
+ if (ret)
+ goto deinit_info;
+
dev_dbg(ice_pf_to_dev(pf),
"%s - success, inputs: %u, outputs: %u, rclk-parents: %u\n",
__func__, d->num_inputs, d->num_outputs, d->rclk.num_parents);
ice_dpll_deinit_dpll(pf, &pf->dplls.pps, cgu);
if (!IS_ERR_OR_NULL(pf->dplls.eec.dpll))
ice_dpll_deinit_dpll(pf, &pf->dplls.eec, cgu);
+ if (!IS_ERR_OR_NULL(pf->dplls.txc.dpll))
+ ice_dpll_deinit_dpll(pf, &pf->dplls.txc, false);
+
ice_dpll_deinit_info(pf);
mutex_destroy(&pf->dplls.lock);
}
err = ice_dpll_init_info_e825c(pf);
if (err)
goto err_exit;
- err = ice_dpll_init_pins_e825(pf);
+ err = ice_dpll_init_dpll(pf, &pf->dplls.txc, false, DPLL_TYPE_GENERIC);
if (err)
goto deinit_info;
+ err = ice_dpll_init_pins_e825(pf);
+ if (err)
+ goto deinit_txclk;
set_bit(ICE_FLAG_DPLL, pf->flags);
complete_all(&d->dpll_init);
return;
+deinit_txclk:
+ ice_dpll_deinit_dpll(pf, &pf->dplls.txc, false);
deinit_info:
ice_dpll_deinit_info(pf);
err_exit: