// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2022, Intel Corporation. */ #include "ice.h" #include "ice_lib.h" #include "ice_trace.h" #include #define ICE_CGU_STATE_ACQ_ERR_THRESHOLD 50 #define ICE_DPLL_PIN_IDX_INVALID 0xff #define ICE_DPLL_RCLK_NUM_PER_PF 1 /** * enum ice_dpll_pin_type - enumerate ice pin types: * @ICE_DPLL_PIN_INVALID: invalid pin type * @ICE_DPLL_PIN_TYPE_INPUT: input pin * @ICE_DPLL_PIN_TYPE_OUTPUT: output pin * @ICE_DPLL_PIN_TYPE_RCLK_INPUT: recovery clock input pin */ enum ice_dpll_pin_type { ICE_DPLL_PIN_INVALID, ICE_DPLL_PIN_TYPE_INPUT, ICE_DPLL_PIN_TYPE_OUTPUT, ICE_DPLL_PIN_TYPE_RCLK_INPUT, }; static const char * const pin_type_name[] = { [ICE_DPLL_PIN_TYPE_INPUT] = "input", [ICE_DPLL_PIN_TYPE_OUTPUT] = "output", [ICE_DPLL_PIN_TYPE_RCLK_INPUT] = "rclk-input", }; /** * ice_dpll_is_reset - check if reset is in progress * @pf: private board structure * @extack: error reporting * * If reset is in progress, fill extack with error. * * Return: * * false - no reset in progress * * true - reset in progress */ static bool ice_dpll_is_reset(struct ice_pf *pf, struct netlink_ext_ack *extack) { if (ice_is_reset_in_progress(pf->state)) { NL_SET_ERR_MSG(extack, "PF reset in progress"); return true; } return false; } /** * ice_dpll_pin_freq_set - set pin's frequency * @pf: private board structure * @pin: pointer to a pin * @pin_type: type of pin being configured * @freq: frequency to be set * @extack: error reporting * * Set requested frequency on a pin. * * Context: Called under pf->dplls.lock * Return: * * 0 - success * * negative - error on AQ or wrong pin type given */ static int ice_dpll_pin_freq_set(struct ice_pf *pf, struct ice_dpll_pin *pin, enum ice_dpll_pin_type pin_type, const u32 freq, struct netlink_ext_ack *extack) { u8 flags; int ret; switch (pin_type) { case ICE_DPLL_PIN_TYPE_INPUT: flags = ICE_AQC_SET_CGU_IN_CFG_FLG1_UPDATE_FREQ; ret = ice_aq_set_input_pin_cfg(&pf->hw, pin->idx, flags, pin->flags[0], freq, 0); break; case ICE_DPLL_PIN_TYPE_OUTPUT: flags = ICE_AQC_SET_CGU_OUT_CFG_UPDATE_FREQ; ret = ice_aq_set_output_pin_cfg(&pf->hw, pin->idx, flags, 0, freq, 0); break; default: return -EINVAL; } if (ret) { NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to set pin freq:%u on pin:%u\n", ret, ice_aq_str(pf->hw.adminq.sq_last_status), freq, pin->idx); return ret; } pin->freq = freq; return 0; } /** * ice_dpll_frequency_set - wrapper for pin callback for set frequency * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: pointer to dpll * @dpll_priv: private data pointer passed on dpll registration * @frequency: frequency to be set * @extack: error reporting * @pin_type: type of pin being configured * * Wraps internal set frequency command on a pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - error pin not found or couldn't set in hw */ static int ice_dpll_frequency_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, const u32 frequency, struct netlink_ext_ack *extack, enum ice_dpll_pin_type pin_type) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; int ret; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); ret = ice_dpll_pin_freq_set(pf, p, pin_type, frequency, extack); mutex_unlock(&pf->dplls.lock); return ret; } /** * ice_dpll_input_frequency_set - input pin callback for set frequency * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: pointer to dpll * @dpll_priv: private data pointer passed on dpll registration * @frequency: frequency to be set * @extack: error reporting * * Wraps internal set frequency command on a pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - error pin not found or couldn't set in hw */ static int ice_dpll_input_frequency_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u64 frequency, struct netlink_ext_ack *extack) { return ice_dpll_frequency_set(pin, pin_priv, dpll, dpll_priv, frequency, extack, ICE_DPLL_PIN_TYPE_INPUT); } /** * ice_dpll_output_frequency_set - output pin callback for set frequency * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: pointer to dpll * @dpll_priv: private data pointer passed on dpll registration * @frequency: frequency to be set * @extack: error reporting * * Wraps internal set frequency command on a pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - error pin not found or couldn't set in hw */ static int ice_dpll_output_frequency_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u64 frequency, struct netlink_ext_ack *extack) { return ice_dpll_frequency_set(pin, pin_priv, dpll, dpll_priv, frequency, extack, ICE_DPLL_PIN_TYPE_OUTPUT); } /** * ice_dpll_frequency_get - wrapper for pin callback for get frequency * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: pointer to dpll * @dpll_priv: private data pointer passed on dpll registration * @frequency: on success holds pin's frequency * @extack: error reporting * @pin_type: type of pin being configured * * Wraps internal get frequency command of a pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - error pin not found or couldn't get from hw */ static int ice_dpll_frequency_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u64 *frequency, struct netlink_ext_ack *extack, enum ice_dpll_pin_type pin_type) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; mutex_lock(&pf->dplls.lock); *frequency = p->freq; mutex_unlock(&pf->dplls.lock); return 0; } /** * ice_dpll_input_frequency_get - input pin callback for get frequency * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: pointer to dpll * @dpll_priv: private data pointer passed on dpll registration * @frequency: on success holds pin's frequency * @extack: error reporting * * Wraps internal get frequency command of a input pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - error pin not found or couldn't get from hw */ static int ice_dpll_input_frequency_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u64 *frequency, struct netlink_ext_ack *extack) { return ice_dpll_frequency_get(pin, pin_priv, dpll, dpll_priv, frequency, extack, ICE_DPLL_PIN_TYPE_INPUT); } /** * ice_dpll_output_frequency_get - output pin callback for get frequency * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: pointer to dpll * @dpll_priv: private data pointer passed on dpll registration * @frequency: on success holds pin's frequency * @extack: error reporting * * Wraps internal get frequency command of a pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - error pin not found or couldn't get from hw */ static int ice_dpll_output_frequency_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u64 *frequency, struct netlink_ext_ack *extack) { return ice_dpll_frequency_get(pin, pin_priv, dpll, dpll_priv, frequency, extack, ICE_DPLL_PIN_TYPE_OUTPUT); } /** * ice_dpll_pin_enable - enable a pin on dplls * @hw: board private hw structure * @pin: pointer to a pin * @dpll_idx: dpll index to connect to output pin * @pin_type: type of pin being enabled * @extack: error reporting * * Enable a pin on both dplls. Store current state in pin->flags. * * Context: Called under pf->dplls.lock * Return: * * 0 - OK * * negative - error */ static int ice_dpll_pin_enable(struct ice_hw *hw, struct ice_dpll_pin *pin, u8 dpll_idx, enum ice_dpll_pin_type pin_type, struct netlink_ext_ack *extack) { u8 flags = 0; int ret; switch (pin_type) { case ICE_DPLL_PIN_TYPE_INPUT: if (pin->flags[0] & ICE_AQC_GET_CGU_IN_CFG_FLG2_ESYNC_EN) flags |= ICE_AQC_SET_CGU_IN_CFG_FLG2_ESYNC_EN; flags |= ICE_AQC_SET_CGU_IN_CFG_FLG2_INPUT_EN; ret = ice_aq_set_input_pin_cfg(hw, pin->idx, 0, flags, 0, 0); break; case ICE_DPLL_PIN_TYPE_OUTPUT: flags = ICE_AQC_SET_CGU_OUT_CFG_UPDATE_SRC_SEL; if (pin->flags[0] & ICE_AQC_GET_CGU_OUT_CFG_ESYNC_EN) flags |= ICE_AQC_SET_CGU_OUT_CFG_ESYNC_EN; flags |= ICE_AQC_SET_CGU_OUT_CFG_OUT_EN; ret = ice_aq_set_output_pin_cfg(hw, pin->idx, flags, dpll_idx, 0, 0); break; default: return -EINVAL; } if (ret) NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to enable %s pin:%u\n", ret, ice_aq_str(hw->adminq.sq_last_status), pin_type_name[pin_type], pin->idx); return ret; } /** * ice_dpll_pin_disable - disable a pin on dplls * @hw: board private hw structure * @pin: pointer to a pin * @pin_type: type of pin being disabled * @extack: error reporting * * Disable a pin on both dplls. Store current state in pin->flags. * * Context: Called under pf->dplls.lock * Return: * * 0 - OK * * negative - error */ static int ice_dpll_pin_disable(struct ice_hw *hw, struct ice_dpll_pin *pin, enum ice_dpll_pin_type pin_type, struct netlink_ext_ack *extack) { u8 flags = 0; int ret; switch (pin_type) { case ICE_DPLL_PIN_TYPE_INPUT: if (pin->flags[0] & ICE_AQC_GET_CGU_IN_CFG_FLG2_ESYNC_EN) flags |= ICE_AQC_SET_CGU_IN_CFG_FLG2_ESYNC_EN; ret = ice_aq_set_input_pin_cfg(hw, pin->idx, 0, flags, 0, 0); break; case ICE_DPLL_PIN_TYPE_OUTPUT: if (pin->flags[0] & ICE_AQC_GET_CGU_OUT_CFG_ESYNC_EN) flags |= ICE_AQC_SET_CGU_OUT_CFG_ESYNC_EN; ret = ice_aq_set_output_pin_cfg(hw, pin->idx, flags, 0, 0, 0); break; default: return -EINVAL; } if (ret) NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to disable %s pin:%u\n", ret, ice_aq_str(hw->adminq.sq_last_status), pin_type_name[pin_type], pin->idx); return ret; } /** * ice_dpll_pin_state_update - update pin's state * @pf: private board struct * @pin: structure with pin attributes to be updated * @pin_type: type of pin being updated * @extack: error reporting * * Determine pin current state and frequency, then update struct * holding the pin info. For input pin states are separated for each * dpll, for rclk pins states are separated for each parent. * * Context: Called under pf->dplls.lock * Return: * * 0 - OK * * negative - error */ static int ice_dpll_pin_state_update(struct ice_pf *pf, struct ice_dpll_pin *pin, enum ice_dpll_pin_type pin_type, struct netlink_ext_ack *extack) { u8 parent, port_num = ICE_AQC_SET_PHY_REC_CLK_OUT_CURR_PORT; int ret; switch (pin_type) { case ICE_DPLL_PIN_TYPE_INPUT: ret = ice_aq_get_input_pin_cfg(&pf->hw, pin->idx, NULL, NULL, NULL, &pin->flags[0], &pin->freq, &pin->phase_adjust); if (ret) goto err; if (ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN & pin->flags[0]) { if (pin->pin) { pin->state[pf->dplls.eec.dpll_idx] = pin->pin == pf->dplls.eec.active_input ? DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_SELECTABLE; pin->state[pf->dplls.pps.dpll_idx] = pin->pin == pf->dplls.pps.active_input ? DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_SELECTABLE; } else { pin->state[pf->dplls.eec.dpll_idx] = DPLL_PIN_STATE_SELECTABLE; pin->state[pf->dplls.pps.dpll_idx] = DPLL_PIN_STATE_SELECTABLE; } } else { pin->state[pf->dplls.eec.dpll_idx] = DPLL_PIN_STATE_DISCONNECTED; pin->state[pf->dplls.pps.dpll_idx] = DPLL_PIN_STATE_DISCONNECTED; } break; case ICE_DPLL_PIN_TYPE_OUTPUT: ret = ice_aq_get_output_pin_cfg(&pf->hw, pin->idx, &pin->flags[0], &parent, &pin->freq, NULL); if (ret) goto err; parent &= ICE_AQC_GET_CGU_OUT_CFG_DPLL_SRC_SEL; if (ICE_AQC_SET_CGU_OUT_CFG_OUT_EN & pin->flags[0]) { pin->state[pf->dplls.eec.dpll_idx] = parent == pf->dplls.eec.dpll_idx ? DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_DISCONNECTED; pin->state[pf->dplls.pps.dpll_idx] = parent == pf->dplls.pps.dpll_idx ? DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_DISCONNECTED; } else { pin->state[pf->dplls.eec.dpll_idx] = DPLL_PIN_STATE_DISCONNECTED; pin->state[pf->dplls.pps.dpll_idx] = DPLL_PIN_STATE_DISCONNECTED; } break; case ICE_DPLL_PIN_TYPE_RCLK_INPUT: for (parent = 0; parent < pf->dplls.rclk.num_parents; parent++) { u8 p = parent; ret = ice_aq_get_phy_rec_clk_out(&pf->hw, &p, &port_num, &pin->flags[parent], NULL); if (ret) goto err; if (ICE_AQC_GET_PHY_REC_CLK_OUT_OUT_EN & pin->flags[parent]) pin->state[parent] = DPLL_PIN_STATE_CONNECTED; else pin->state[parent] = DPLL_PIN_STATE_DISCONNECTED; } break; default: return -EINVAL; } return 0; err: if (extack) NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to update %s pin:%u\n", ret, ice_aq_str(pf->hw.adminq.sq_last_status), pin_type_name[pin_type], pin->idx); else dev_err_ratelimited(ice_pf_to_dev(pf), "err:%d %s failed to update %s pin:%u\n", ret, ice_aq_str(pf->hw.adminq.sq_last_status), pin_type_name[pin_type], pin->idx); return ret; } /** * ice_dpll_hw_input_prio_set - set input priority value in hardware * @pf: board private structure * @dpll: ice dpll pointer * @pin: ice pin pointer * @prio: priority value being set on a dpll * @extack: error reporting * * Internal wrapper for setting the priority in the hardware. * * Context: Called under pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_hw_input_prio_set(struct ice_pf *pf, struct ice_dpll *dpll, struct ice_dpll_pin *pin, const u32 prio, struct netlink_ext_ack *extack) { int ret; ret = ice_aq_set_cgu_ref_prio(&pf->hw, dpll->dpll_idx, pin->idx, (u8)prio); if (ret) NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to set pin prio:%u on pin:%u\n", ret, ice_aq_str(pf->hw.adminq.sq_last_status), prio, pin->idx); else dpll->input_prio[pin->idx] = prio; return ret; } /** * ice_dpll_lock_status_get - get dpll lock status callback * @dpll: registered dpll pointer * @dpll_priv: private data pointer passed on dpll registration * @status: on success holds dpll's lock status * @extack: error reporting * * Dpll subsystem callback, provides dpll's lock status. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv, enum dpll_lock_status *status, struct netlink_ext_ack *extack) { struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; mutex_lock(&pf->dplls.lock); *status = d->dpll_state; mutex_unlock(&pf->dplls.lock); return 0; } /** * ice_dpll_mode_get - get dpll's working mode * @dpll: registered dpll pointer * @dpll_priv: private data pointer passed on dpll registration * @mode: on success holds current working mode of dpll * @extack: error reporting * * Dpll subsystem callback. Provides working mode of dpll. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, enum dpll_mode *mode, struct netlink_ext_ack *extack) { struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; mutex_lock(&pf->dplls.lock); *mode = d->mode; mutex_unlock(&pf->dplls.lock); return 0; } /** * ice_dpll_pin_state_set - set pin's state on dpll * @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 * @enable: if pin shalll be enabled * @extack: error reporting * @pin_type: type of a pin * * Set pin state on a pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - OK or no change required * * negative - error */ static int ice_dpll_pin_state_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, bool enable, struct netlink_ext_ack *extack, enum ice_dpll_pin_type pin_type) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; int ret; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); if (enable) ret = ice_dpll_pin_enable(&pf->hw, p, d->dpll_idx, pin_type, extack); else ret = ice_dpll_pin_disable(&pf->hw, p, pin_type, extack); if (!ret) ret = ice_dpll_pin_state_update(pf, p, pin_type, extack); mutex_unlock(&pf->dplls.lock); return ret; } /** * ice_dpll_output_state_set - enable/disable output pin on dpll device * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: dpll being configured * @dpll_priv: private data pointer passed on dpll registration * @state: state of pin to be set * @extack: error reporting * * Dpll subsystem callback. Set given state on output type pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - successfully enabled mode * * negative - failed to enable mode */ static int ice_dpll_output_state_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) { bool enable = state == DPLL_PIN_STATE_CONNECTED; struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; if (!enable && p->state[d->dpll_idx] == DPLL_PIN_STATE_DISCONNECTED) return 0; return ice_dpll_pin_state_set(pin, pin_priv, dpll, dpll_priv, enable, extack, ICE_DPLL_PIN_TYPE_OUTPUT); } /** * ice_dpll_input_state_set - enable/disable input pin on dpll levice * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @dpll: dpll being configured * @dpll_priv: private data pointer passed on dpll registration * @state: state of pin to be set * @extack: error reporting * * Dpll subsystem callback. Enables given mode on input type pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - successfully enabled mode * * negative - failed to enable mode */ static int ice_dpll_input_state_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) { bool enable = state == DPLL_PIN_STATE_SELECTABLE; return ice_dpll_pin_state_set(pin, pin_priv, dpll, dpll_priv, enable, extack, ICE_DPLL_PIN_TYPE_INPUT); } /** * ice_dpll_pin_state_get - set pin's state on dpll * @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 state of the pin * @extack: error reporting * @pin_type: type of questioned pin * * Determine pin state set it on a pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failed to get state */ static int ice_dpll_pin_state_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, enum ice_dpll_pin_type pin_type) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; int ret; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); ret = ice_dpll_pin_state_update(pf, p, pin_type, extack); if (ret) goto unlock; if (pin_type == ICE_DPLL_PIN_TYPE_INPUT || pin_type == ICE_DPLL_PIN_TYPE_OUTPUT) *state = p->state[d->dpll_idx]; ret = 0; unlock: mutex_unlock(&pf->dplls.lock); return ret; } /** * ice_dpll_output_state_get - get output pin state on dpll device * @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 state of the pin * @extack: error reporting * * Dpll subsystem callback. Check state of a pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - failed to get state */ static int ice_dpll_output_state_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) { return ice_dpll_pin_state_get(pin, pin_priv, dpll, dpll_priv, state, extack, ICE_DPLL_PIN_TYPE_OUTPUT); } /** * ice_dpll_input_state_get - get input pin state on dpll device * @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 state of the pin * @extack: error reporting * * Dpll subsystem callback. Check state of a input pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - failed to get state */ static int ice_dpll_input_state_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) { return ice_dpll_pin_state_get(pin, pin_priv, dpll, dpll_priv, state, extack, ICE_DPLL_PIN_TYPE_INPUT); } /** * ice_dpll_input_prio_get - get dpll's input prio * @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 * @prio: on success - returns input priority on dpll * @extack: error reporting * * Dpll subsystem callback. Handler for getting priority of a input pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_input_prio_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 *prio, struct netlink_ext_ack *extack) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; mutex_lock(&pf->dplls.lock); *prio = d->input_prio[p->idx]; mutex_unlock(&pf->dplls.lock); return 0; } /** * ice_dpll_input_prio_set - set dpll input prio * @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 * @prio: input priority to be set on dpll * @extack: error reporting * * Dpll subsystem callback. Handler for setting priority of a input pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_input_prio_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u32 prio, struct netlink_ext_ack *extack) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; int ret; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); ret = ice_dpll_hw_input_prio_set(pf, d, p, prio, extack); mutex_unlock(&pf->dplls.lock); return ret; } /** * ice_dpll_input_direction - callback for get input pin direction * @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 * @direction: holds input pin direction * @extack: error reporting * * Dpll subsystem callback. Handler for getting direction of a input pin. * * Return: * * 0 - success */ static int ice_dpll_input_direction(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, enum dpll_pin_direction *direction, struct netlink_ext_ack *extack) { *direction = DPLL_PIN_DIRECTION_INPUT; return 0; } /** * ice_dpll_output_direction - callback for get output pin direction * @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 * @direction: holds output pin direction * @extack: error reporting * * Dpll subsystem callback. Handler for getting direction of an output pin. * * Return: * * 0 - success */ static int ice_dpll_output_direction(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, enum dpll_pin_direction *direction, struct netlink_ext_ack *extack) { *direction = DPLL_PIN_DIRECTION_OUTPUT; return 0; } /** * ice_dpll_pin_phase_adjust_get - callback for get pin phase adjust value * @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 * @phase_adjust: on success holds pin phase_adjust value * @extack: error reporting * * Dpll subsystem callback. Handler for getting phase adjust value of a pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - error */ static int ice_dpll_pin_phase_adjust_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, s32 *phase_adjust, struct netlink_ext_ack *extack) { struct ice_dpll_pin *p = pin_priv; struct ice_pf *pf = p->pf; mutex_lock(&pf->dplls.lock); *phase_adjust = p->phase_adjust; mutex_unlock(&pf->dplls.lock); return 0; } /** * ice_dpll_pin_phase_adjust_set - helper for setting a pin phase adjust value * @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 * @phase_adjust: phase_adjust to be set * @extack: error reporting * @type: type of a pin * * Helper for dpll subsystem callback. Handler for setting phase adjust value * of a pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - error */ static int ice_dpll_pin_phase_adjust_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, s32 phase_adjust, struct netlink_ext_ack *extack, enum ice_dpll_pin_type type) { struct ice_dpll_pin *p = pin_priv; struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; u8 flag, flags_en = 0; int ret; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); switch (type) { case ICE_DPLL_PIN_TYPE_INPUT: flag = ICE_AQC_SET_CGU_IN_CFG_FLG1_UPDATE_DELAY; if (p->flags[0] & ICE_AQC_GET_CGU_IN_CFG_FLG2_ESYNC_EN) flags_en |= ICE_AQC_SET_CGU_IN_CFG_FLG2_ESYNC_EN; if (p->flags[0] & ICE_AQC_GET_CGU_IN_CFG_FLG2_INPUT_EN) flags_en |= ICE_AQC_SET_CGU_IN_CFG_FLG2_INPUT_EN; ret = ice_aq_set_input_pin_cfg(&pf->hw, p->idx, flag, flags_en, 0, phase_adjust); break; case ICE_DPLL_PIN_TYPE_OUTPUT: flag = ICE_AQC_SET_CGU_OUT_CFG_UPDATE_PHASE; if (p->flags[0] & ICE_AQC_GET_CGU_OUT_CFG_OUT_EN) flag |= ICE_AQC_SET_CGU_OUT_CFG_OUT_EN; if (p->flags[0] & ICE_AQC_GET_CGU_OUT_CFG_ESYNC_EN) flag |= ICE_AQC_SET_CGU_OUT_CFG_ESYNC_EN; ret = ice_aq_set_output_pin_cfg(&pf->hw, p->idx, flag, 0, 0, phase_adjust); break; default: ret = -EINVAL; } if (!ret) p->phase_adjust = phase_adjust; mutex_unlock(&pf->dplls.lock); if (ret) NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to set pin phase_adjust:%d for pin:%u on dpll:%u\n", ret, ice_aq_str(pf->hw.adminq.sq_last_status), phase_adjust, p->idx, d->dpll_idx); return ret; } /** * ice_dpll_input_phase_adjust_set - callback for set input pin phase adjust * @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 * @phase_adjust: phase_adjust to be set * @extack: error reporting * * Dpll subsystem callback. Wraps a handler for setting phase adjust on input * pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - error */ static int ice_dpll_input_phase_adjust_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, s32 phase_adjust, struct netlink_ext_ack *extack) { return ice_dpll_pin_phase_adjust_set(pin, pin_priv, dpll, dpll_priv, phase_adjust, extack, ICE_DPLL_PIN_TYPE_INPUT); } /** * ice_dpll_output_phase_adjust_set - callback for set output pin phase adjust * @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 * @phase_adjust: phase_adjust to be set * @extack: error reporting * * Dpll subsystem callback. Wraps a handler for setting phase adjust on output * pin. * * Context: Calls a function which acquires pf->dplls.lock * Return: * * 0 - success * * negative - error */ static int ice_dpll_output_phase_adjust_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, s32 phase_adjust, struct netlink_ext_ack *extack) { return ice_dpll_pin_phase_adjust_set(pin, pin_priv, dpll, dpll_priv, phase_adjust, extack, ICE_DPLL_PIN_TYPE_OUTPUT); } #define ICE_DPLL_PHASE_OFFSET_DIVIDER 100 #define ICE_DPLL_PHASE_OFFSET_FACTOR \ (DPLL_PHASE_OFFSET_DIVIDER / ICE_DPLL_PHASE_OFFSET_DIVIDER) /** * ice_dpll_phase_offset_get - callback for get dpll phase shift value * @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 * @phase_offset: on success holds pin phase_offset value * @extack: error reporting * * Dpll subsystem callback. Handler for getting phase shift value between * dpll's input and output. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - error */ static int ice_dpll_phase_offset_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, s64 *phase_offset, struct netlink_ext_ack *extack) { struct ice_dpll *d = dpll_priv; struct ice_pf *pf = d->pf; mutex_lock(&pf->dplls.lock); if (d->active_input == pin) *phase_offset = d->phase_offset * ICE_DPLL_PHASE_OFFSET_FACTOR; else *phase_offset = 0; mutex_unlock(&pf->dplls.lock); return 0; } /** * ice_dpll_rclk_state_on_pin_set - set a state on rclk pin * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @parent_pin: pin parent pointer * @parent_pin_priv: parent private data pointer passed on pin registration * @state: state to be set on pin * @extack: error reporting * * Dpll subsystem callback, set a state of a rclk pin on a parent pin * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_rclk_state_on_pin_set(const struct dpll_pin *pin, void *pin_priv, const struct dpll_pin *parent_pin, void *parent_pin_priv, enum dpll_pin_state state, struct netlink_ext_ack *extack) { struct ice_dpll_pin *p = pin_priv, *parent = parent_pin_priv; bool enable = state == DPLL_PIN_STATE_CONNECTED; struct ice_pf *pf = p->pf; int ret = -EINVAL; u32 hw_idx; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); hw_idx = parent->idx - pf->dplls.base_rclk_idx; if (hw_idx >= pf->dplls.num_inputs) goto unlock; if ((enable && p->state[hw_idx] == DPLL_PIN_STATE_CONNECTED) || (!enable && p->state[hw_idx] == DPLL_PIN_STATE_DISCONNECTED)) { NL_SET_ERR_MSG_FMT(extack, "pin:%u state:%u on parent:%u already set", p->idx, state, parent->idx); goto unlock; } ret = ice_aq_set_phy_rec_clk_out(&pf->hw, hw_idx, enable, &p->freq); if (ret) NL_SET_ERR_MSG_FMT(extack, "err:%d %s failed to set pin state:%u for pin:%u on parent:%u\n", ret, ice_aq_str(pf->hw.adminq.sq_last_status), state, p->idx, parent->idx); unlock: mutex_unlock(&pf->dplls.lock); return ret; } /** * ice_dpll_rclk_state_on_pin_get - get a state of rclk pin * @pin: pointer to a pin * @pin_priv: private data pointer passed on pin registration * @parent_pin: pin parent pointer * @parent_pin_priv: pin parent priv data pointer passed on pin registration * @state: on success holds pin state on parent pin * @extack: error reporting * * dpll subsystem callback, get a state of a recovered clock pin. * * Context: Acquires pf->dplls.lock * Return: * * 0 - success * * negative - failure */ static int ice_dpll_rclk_state_on_pin_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_pin *parent_pin, void *parent_pin_priv, enum dpll_pin_state *state, struct netlink_ext_ack *extack) { struct ice_dpll_pin *p = pin_priv, *parent = parent_pin_priv; struct ice_pf *pf = p->pf; int ret = -EINVAL; u32 hw_idx; if (ice_dpll_is_reset(pf, extack)) return -EBUSY; mutex_lock(&pf->dplls.lock); hw_idx = parent->idx - pf->dplls.base_rclk_idx; if (hw_idx >= pf->dplls.num_inputs) goto unlock; ret = ice_dpll_pin_state_update(pf, p, ICE_DPLL_PIN_TYPE_RCLK_INPUT, extack); if (ret) goto unlock; *state = p->state[hw_idx]; ret = 0; unlock: mutex_unlock(&pf->dplls.lock); return ret; } 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_input_ops = { .frequency_get = ice_dpll_input_frequency_get, .frequency_set = ice_dpll_input_frequency_set, .state_on_dpll_get = ice_dpll_input_state_get, .state_on_dpll_set = ice_dpll_input_state_set, .prio_get = ice_dpll_input_prio_get, .prio_set = ice_dpll_input_prio_set, .direction_get = ice_dpll_input_direction, .phase_adjust_get = ice_dpll_pin_phase_adjust_get, .phase_adjust_set = ice_dpll_input_phase_adjust_set, .phase_offset_get = ice_dpll_phase_offset_get, }; static const struct dpll_pin_ops ice_dpll_output_ops = { .frequency_get = ice_dpll_output_frequency_get, .frequency_set = ice_dpll_output_frequency_set, .state_on_dpll_get = ice_dpll_output_state_get, .state_on_dpll_set = ice_dpll_output_state_set, .direction_get = ice_dpll_output_direction, .phase_adjust_get = ice_dpll_pin_phase_adjust_get, .phase_adjust_set = ice_dpll_output_phase_adjust_set, }; static const struct dpll_device_ops ice_dpll_ops = { .lock_status_get = ice_dpll_lock_status_get, .mode_get = ice_dpll_mode_get, }; /** * ice_generate_clock_id - generates unique clock_id for registering dpll. * @pf: board private structure * * Generates unique (per board) clock_id for allocation and search of dpll * devices in Linux dpll subsystem. * * Return: generated clock id for the board */ static u64 ice_generate_clock_id(struct ice_pf *pf) { return pci_get_dsn(pf->pdev); } /** * ice_dpll_notify_changes - notify dpll subsystem about changes * @d: pointer do dpll * * Once change detected appropriate event is submitted to the dpll subsystem. */ static void ice_dpll_notify_changes(struct ice_dpll *d) { bool pin_notified = false; if (d->prev_dpll_state != d->dpll_state) { d->prev_dpll_state = d->dpll_state; dpll_device_change_ntf(d->dpll); } if (d->prev_input != d->active_input) { if (d->prev_input) dpll_pin_change_ntf(d->prev_input); d->prev_input = d->active_input; if (d->active_input) { dpll_pin_change_ntf(d->active_input); pin_notified = true; } } if (d->prev_phase_offset != d->phase_offset) { d->prev_phase_offset = d->phase_offset; if (!pin_notified && d->active_input) dpll_pin_change_ntf(d->active_input); } } /** * ice_dpll_update_state - update dpll state * @pf: pf private structure * @d: pointer to queried dpll device * @init: if function called on initialization of ice dpll * * Poll current state of dpll from hw and update ice_dpll struct. * * Context: Called by kworker under pf->dplls.lock * Return: * * 0 - success * * negative - AQ failure */ static int ice_dpll_update_state(struct ice_pf *pf, struct ice_dpll *d, bool init) { struct ice_dpll_pin *p = NULL; int ret; ret = ice_get_cgu_state(&pf->hw, d->dpll_idx, d->prev_dpll_state, &d->input_idx, &d->ref_state, &d->eec_mode, &d->phase_offset, &d->dpll_state); dev_dbg(ice_pf_to_dev(pf), "update dpll=%d, prev_src_idx:%u, src_idx:%u, state:%d, prev:%d mode:%d\n", d->dpll_idx, d->prev_input_idx, d->input_idx, d->dpll_state, d->prev_dpll_state, d->mode); if (ret) { dev_err(ice_pf_to_dev(pf), "update dpll=%d state failed, ret=%d %s\n", d->dpll_idx, ret, ice_aq_str(pf->hw.adminq.sq_last_status)); return ret; } if (init) { if (d->dpll_state == DPLL_LOCK_STATUS_LOCKED || d->dpll_state == DPLL_LOCK_STATUS_LOCKED_HO_ACQ) d->active_input = pf->dplls.inputs[d->input_idx].pin; p = &pf->dplls.inputs[d->input_idx]; return ice_dpll_pin_state_update(pf, p, ICE_DPLL_PIN_TYPE_INPUT, NULL); } if (d->dpll_state == DPLL_LOCK_STATUS_HOLDOVER || d->dpll_state == DPLL_LOCK_STATUS_UNLOCKED) { d->active_input = NULL; if (d->input_idx != ICE_DPLL_PIN_IDX_INVALID) p = &pf->dplls.inputs[d->input_idx]; d->prev_input_idx = ICE_DPLL_PIN_IDX_INVALID; d->input_idx = ICE_DPLL_PIN_IDX_INVALID; if (!p) return 0; ret = ice_dpll_pin_state_update(pf, p, ICE_DPLL_PIN_TYPE_INPUT, NULL); } else if (d->input_idx != d->prev_input_idx) { if (d->prev_input_idx != ICE_DPLL_PIN_IDX_INVALID) { p = &pf->dplls.inputs[d->prev_input_idx]; ice_dpll_pin_state_update(pf, p, ICE_DPLL_PIN_TYPE_INPUT, NULL); } if (d->input_idx != ICE_DPLL_PIN_IDX_INVALID) { p = &pf->dplls.inputs[d->input_idx]; d->active_input = p->pin; ice_dpll_pin_state_update(pf, p, ICE_DPLL_PIN_TYPE_INPUT, NULL); } d->prev_input_idx = d->input_idx; } return ret; } /** * ice_dpll_periodic_work - DPLLs periodic worker * @work: pointer to kthread_work structure * * DPLLs periodic worker is responsible for polling state of dpll. * Context: Holds pf->dplls.lock */ static void ice_dpll_periodic_work(struct kthread_work *work) { struct ice_dplls *d = container_of(work, struct ice_dplls, work.work); struct ice_pf *pf = container_of(d, struct ice_pf, dplls); struct ice_dpll *de = &pf->dplls.eec; struct ice_dpll *dp = &pf->dplls.pps; int ret = 0; if (ice_is_reset_in_progress(pf->state)) goto resched; mutex_lock(&pf->dplls.lock); ret = ice_dpll_update_state(pf, de, false); if (!ret) ret = ice_dpll_update_state(pf, dp, false); if (ret) { d->cgu_state_acq_err_num++; /* stop rescheduling this worker */ if (d->cgu_state_acq_err_num > ICE_CGU_STATE_ACQ_ERR_THRESHOLD) { dev_err(ice_pf_to_dev(pf), "EEC/PPS DPLLs periodic work disabled\n"); mutex_unlock(&pf->dplls.lock); return; } } mutex_unlock(&pf->dplls.lock); ice_dpll_notify_changes(de); ice_dpll_notify_changes(dp); resched: /* Run twice a second or reschedule if update failed */ kthread_queue_delayed_work(d->kworker, &d->work, ret ? msecs_to_jiffies(10) : msecs_to_jiffies(500)); } /** * ice_dpll_release_pins - release pins resources from dpll subsystem * @pins: pointer to pins array * @count: number of pins * * Release resources of given pins array in the dpll subsystem. */ static void ice_dpll_release_pins(struct ice_dpll_pin *pins, int count) { int i; for (i = 0; i < count; i++) dpll_pin_put(pins[i].pin); } /** * ice_dpll_get_pins - get pins from dpll subsystem * @pf: board private structure * @pins: pointer to pins array * @start_idx: get starts from this pin idx value * @count: number of pins * @clock_id: clock_id of dpll device * * Get pins - allocate - in dpll subsystem, store them in pin field of given * pins array. * * Return: * * 0 - success * * negative - allocation failure reason */ static int ice_dpll_get_pins(struct ice_pf *pf, struct ice_dpll_pin *pins, int start_idx, int count, u64 clock_id) { int i, ret; for (i = 0; i < count; i++) { pins[i].pin = dpll_pin_get(clock_id, i + start_idx, THIS_MODULE, &pins[i].prop); if (IS_ERR(pins[i].pin)) { ret = PTR_ERR(pins[i].pin); goto release_pins; } } return 0; release_pins: while (--i >= 0) dpll_pin_put(pins[i].pin); return ret; } /** * ice_dpll_unregister_pins - unregister pins from a dpll * @dpll: dpll device pointer * @pins: pointer to pins array * @ops: callback ops registered with the pins * @count: number of pins * * Unregister pins of a given array of pins from given dpll device registered in * dpll subsystem. */ static void ice_dpll_unregister_pins(struct dpll_device *dpll, struct ice_dpll_pin *pins, const struct dpll_pin_ops *ops, int count) { int i; for (i = 0; i < count; i++) dpll_pin_unregister(dpll, pins[i].pin, ops, &pins[i]); } /** * ice_dpll_register_pins - register pins with a dpll * @dpll: dpll pointer to register pins with * @pins: pointer to pins array * @ops: callback ops registered with the pins * @count: number of pins * * Register pins of a given array with given dpll in dpll subsystem. * * Return: * * 0 - success * * negative - registration failure reason */ static int ice_dpll_register_pins(struct dpll_device *dpll, struct ice_dpll_pin *pins, const struct dpll_pin_ops *ops, int count) { int ret, i; for (i = 0; i < count; i++) { ret = dpll_pin_register(dpll, pins[i].pin, ops, &pins[i]); if (ret) goto unregister_pins; } return 0; unregister_pins: while (--i >= 0) dpll_pin_unregister(dpll, pins[i].pin, ops, &pins[i]); return ret; } /** * ice_dpll_deinit_direct_pins - deinitialize direct pins * @cgu: if cgu is present and controlled by this NIC * @pins: pointer to pins array * @count: number of pins * @ops: callback ops registered with the pins * @first: dpll device pointer * @second: dpll device pointer * * If cgu is owned unregister pins from given dplls. * Release pins resources to the dpll subsystem. */ static void ice_dpll_deinit_direct_pins(bool cgu, struct ice_dpll_pin *pins, int count, const struct dpll_pin_ops *ops, struct dpll_device *first, struct dpll_device *second) { if (cgu) { ice_dpll_unregister_pins(first, pins, ops, count); ice_dpll_unregister_pins(second, pins, ops, count); } ice_dpll_release_pins(pins, count); } /** * ice_dpll_init_direct_pins - initialize direct pins * @pf: board private structure * @cgu: if cgu is present and controlled by this NIC * @pins: pointer to pins array * @start_idx: on which index shall allocation start in dpll subsystem * @count: number of pins * @ops: callback ops registered with the pins * @first: dpll device pointer * @second: dpll device pointer * * Allocate directly connected pins of a given array in dpll subsystem. * If cgu is owned register allocated pins with given dplls. * * Return: * * 0 - success * * negative - registration failure reason */ static int ice_dpll_init_direct_pins(struct ice_pf *pf, bool cgu, struct ice_dpll_pin *pins, int start_idx, int count, const struct dpll_pin_ops *ops, struct dpll_device *first, struct dpll_device *second) { int ret; ret = ice_dpll_get_pins(pf, pins, start_idx, count, pf->dplls.clock_id); if (ret) return ret; if (cgu) { ret = ice_dpll_register_pins(first, pins, ops, count); if (ret) goto release_pins; ret = ice_dpll_register_pins(second, pins, ops, count); if (ret) goto unregister_first; } return 0; unregister_first: ice_dpll_unregister_pins(first, pins, ops, count); release_pins: ice_dpll_release_pins(pins, count); return ret; } /** * ice_dpll_deinit_rclk_pin - release rclk pin resources * @pf: board private structure * * Deregister rclk pin from parent pins and release resources in dpll subsystem. */ static void ice_dpll_deinit_rclk_pin(struct ice_pf *pf) { struct ice_dpll_pin *rclk = &pf->dplls.rclk; struct ice_vsi *vsi = ice_get_main_vsi(pf); struct dpll_pin *parent; int i; for (i = 0; i < rclk->num_parents; i++) { parent = pf->dplls.inputs[rclk->parent_idx[i]].pin; if (!parent) continue; dpll_pin_on_pin_unregister(parent, rclk->pin, &ice_dpll_rclk_ops, rclk); } if (WARN_ON_ONCE(!vsi || !vsi->netdev)) return; netdev_dpll_pin_clear(vsi->netdev); dpll_pin_put(rclk->pin); } /** * ice_dpll_init_rclk_pins - initialize recovered clock pin * @pf: board private structure * @pin: pin to register * @start_idx: on which index shall allocation start in dpll subsystem * @ops: callback ops registered with the pins * * Allocate resource for recovered clock pin in dpll subsystem. Register the * pin with the parents it has in the info. Register pin with the pf's main vsi * netdev. * * Return: * * 0 - success * * negative - registration failure reason */ static int ice_dpll_init_rclk_pins(struct ice_pf *pf, struct ice_dpll_pin *pin, int start_idx, const struct dpll_pin_ops *ops) { struct ice_vsi *vsi = ice_get_main_vsi(pf); struct dpll_pin *parent; int ret, i; ret = ice_dpll_get_pins(pf, pin, start_idx, ICE_DPLL_RCLK_NUM_PER_PF, pf->dplls.clock_id); if (ret) return ret; for (i = 0; i < pf->dplls.rclk.num_parents; i++) { parent = pf->dplls.inputs[pf->dplls.rclk.parent_idx[i]].pin; if (!parent) { ret = -ENODEV; goto unregister_pins; } ret = dpll_pin_on_pin_register(parent, pf->dplls.rclk.pin, ops, &pf->dplls.rclk); if (ret) goto unregister_pins; } if (WARN_ON((!vsi || !vsi->netdev))) return -EINVAL; netdev_dpll_pin_set(vsi->netdev, pf->dplls.rclk.pin); return 0; unregister_pins: while (i) { parent = pf->dplls.inputs[pf->dplls.rclk.parent_idx[--i]].pin; dpll_pin_on_pin_unregister(parent, pf->dplls.rclk.pin, &ice_dpll_rclk_ops, &pf->dplls.rclk); } ice_dpll_release_pins(pin, ICE_DPLL_RCLK_NUM_PER_PF); return ret; } /** * ice_dpll_deinit_pins - deinitialize direct pins * @pf: board private structure * @cgu: if cgu is controlled by this pf * * If cgu is owned unregister directly connected pins from the dplls. * Release resources of directly connected pins from the dpll subsystem. */ static void ice_dpll_deinit_pins(struct ice_pf *pf, bool cgu) { struct ice_dpll_pin *outputs = pf->dplls.outputs; struct ice_dpll_pin *inputs = pf->dplls.inputs; int num_outputs = pf->dplls.num_outputs; int num_inputs = pf->dplls.num_inputs; struct ice_dplls *d = &pf->dplls; struct ice_dpll *de = &d->eec; struct ice_dpll *dp = &d->pps; ice_dpll_deinit_rclk_pin(pf); if (cgu) { ice_dpll_unregister_pins(dp->dpll, inputs, &ice_dpll_input_ops, num_inputs); ice_dpll_unregister_pins(de->dpll, inputs, &ice_dpll_input_ops, num_inputs); } ice_dpll_release_pins(inputs, num_inputs); if (cgu) { ice_dpll_unregister_pins(dp->dpll, outputs, &ice_dpll_output_ops, num_outputs); ice_dpll_unregister_pins(de->dpll, outputs, &ice_dpll_output_ops, num_outputs); ice_dpll_release_pins(outputs, num_outputs); } } /** * ice_dpll_init_pins - init pins and register pins with a dplls * @pf: board private structure * @cgu: if cgu is present and controlled by this NIC * * Initialize directly connected pf's pins within pf's dplls in a Linux dpll * subsystem. * * Return: * * 0 - success * * negative - initialization failure reason */ static int ice_dpll_init_pins(struct ice_pf *pf, bool cgu) { u32 rclk_idx; int ret; ret = ice_dpll_init_direct_pins(pf, cgu, pf->dplls.inputs, 0, pf->dplls.num_inputs, &ice_dpll_input_ops, pf->dplls.eec.dpll, pf->dplls.pps.dpll); if (ret) return ret; if (cgu) { ret = ice_dpll_init_direct_pins(pf, cgu, pf->dplls.outputs, pf->dplls.num_inputs, pf->dplls.num_outputs, &ice_dpll_output_ops, pf->dplls.eec.dpll, pf->dplls.pps.dpll); if (ret) goto deinit_inputs; } rclk_idx = pf->dplls.num_inputs + pf->dplls.num_outputs + pf->hw.pf_id; ret = ice_dpll_init_rclk_pins(pf, &pf->dplls.rclk, rclk_idx, &ice_dpll_rclk_ops); if (ret) goto deinit_outputs; return 0; deinit_outputs: ice_dpll_deinit_direct_pins(cgu, pf->dplls.outputs, pf->dplls.num_outputs, &ice_dpll_output_ops, pf->dplls.pps.dpll, pf->dplls.eec.dpll); deinit_inputs: ice_dpll_deinit_direct_pins(cgu, pf->dplls.inputs, pf->dplls.num_inputs, &ice_dpll_input_ops, pf->dplls.pps.dpll, pf->dplls.eec.dpll); return ret; } /** * ice_dpll_deinit_dpll - deinitialize dpll device * @pf: board private structure * @d: pointer to ice_dpll * @cgu: if cgu is present and controlled by this NIC * * If cgu is owned unregister the dpll from dpll subsystem. * Release resources of dpll device from dpll subsystem. */ static void ice_dpll_deinit_dpll(struct ice_pf *pf, struct ice_dpll *d, bool cgu) { if (cgu) dpll_device_unregister(d->dpll, &ice_dpll_ops, d); dpll_device_put(d->dpll); } /** * ice_dpll_init_dpll - initialize dpll device in dpll subsystem * @pf: board private structure * @d: dpll to be initialized * @cgu: if cgu is present and controlled by this NIC * @type: type of dpll being initialized * * Allocate dpll instance for this board in dpll subsystem, if cgu is controlled * by this NIC, register dpll with the callback ops. * * Return: * * 0 - success * * negative - initialization failure reason */ static int ice_dpll_init_dpll(struct ice_pf *pf, struct ice_dpll *d, bool cgu, enum dpll_type type) { u64 clock_id = pf->dplls.clock_id; int ret; d->dpll = dpll_device_get(clock_id, d->dpll_idx, THIS_MODULE); if (IS_ERR(d->dpll)) { ret = PTR_ERR(d->dpll); dev_err(ice_pf_to_dev(pf), "dpll_device_get failed (%p) err=%d\n", d, ret); return ret; } d->pf = pf; if (cgu) { ice_dpll_update_state(pf, d, true); ret = dpll_device_register(d->dpll, type, &ice_dpll_ops, d); if (ret) { dpll_device_put(d->dpll); return ret; } } return 0; } /** * ice_dpll_deinit_worker - deinitialize dpll kworker * @pf: board private structure * * Stop dpll's kworker, release it's resources. */ static void ice_dpll_deinit_worker(struct ice_pf *pf) { struct ice_dplls *d = &pf->dplls; kthread_cancel_delayed_work_sync(&d->work); kthread_destroy_worker(d->kworker); } /** * ice_dpll_init_worker - Initialize DPLLs periodic worker * @pf: board private structure * * Create and start DPLLs periodic worker. * * Context: Shall be called after pf->dplls.lock is initialized. * Return: * * 0 - success * * negative - create worker failure */ static int ice_dpll_init_worker(struct ice_pf *pf) { struct ice_dplls *d = &pf->dplls; struct kthread_worker *kworker; kthread_init_delayed_work(&d->work, ice_dpll_periodic_work); kworker = kthread_create_worker(0, "ice-dplls-%s", dev_name(ice_pf_to_dev(pf))); if (IS_ERR(kworker)) return PTR_ERR(kworker); d->kworker = kworker; d->cgu_state_acq_err_num = 0; kthread_queue_delayed_work(d->kworker, &d->work, 0); return 0; } /** * ice_dpll_init_info_direct_pins - initializes direct pins info * @pf: board private structure * @pin_type: type of pins being initialized * * Init information for directly connected pins, cache them in pf's pins * structures. * * Return: * * 0 - success * * negative - init failure reason */ static int ice_dpll_init_info_direct_pins(struct ice_pf *pf, enum ice_dpll_pin_type pin_type) { struct ice_dpll *de = &pf->dplls.eec, *dp = &pf->dplls.pps; int num_pins, i, ret = -EINVAL; struct ice_hw *hw = &pf->hw; struct ice_dpll_pin *pins; unsigned long caps; u8 freq_supp_num; bool input; switch (pin_type) { case ICE_DPLL_PIN_TYPE_INPUT: pins = pf->dplls.inputs; num_pins = pf->dplls.num_inputs; input = true; break; case ICE_DPLL_PIN_TYPE_OUTPUT: pins = pf->dplls.outputs; num_pins = pf->dplls.num_outputs; input = false; break; default: return -EINVAL; } for (i = 0; i < num_pins; i++) { caps = 0; pins[i].idx = i; pins[i].prop.board_label = ice_cgu_get_pin_name(hw, i, input); pins[i].prop.type = ice_cgu_get_pin_type(hw, i, input); if (input) { ret = ice_aq_get_cgu_ref_prio(hw, de->dpll_idx, i, &de->input_prio[i]); if (ret) return ret; ret = ice_aq_get_cgu_ref_prio(hw, dp->dpll_idx, i, &dp->input_prio[i]); if (ret) return ret; caps |= (DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE | DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE); pins[i].prop.phase_range.min = pf->dplls.input_phase_adj_max; pins[i].prop.phase_range.max = -pf->dplls.input_phase_adj_max; } else { pins[i].prop.phase_range.min = pf->dplls.output_phase_adj_max; pins[i].prop.phase_range.max = -pf->dplls.output_phase_adj_max; ret = ice_cgu_get_output_pin_state_caps(hw, i, &caps); if (ret) return ret; } pins[i].prop.capabilities = caps; ret = ice_dpll_pin_state_update(pf, &pins[i], pin_type, NULL); if (ret) return ret; pins[i].prop.freq_supported = ice_cgu_get_pin_freq_supp(hw, i, input, &freq_supp_num); pins[i].prop.freq_supported_num = freq_supp_num; pins[i].pf = pf; } return ret; } /** * ice_dpll_init_info_rclk_pin - initializes rclk pin information * @pf: board private structure * * Init information for rclk pin, cache them in pf->dplls.rclk. * * Return: * * 0 - success * * negative - init failure reason */ static int ice_dpll_init_info_rclk_pin(struct ice_pf *pf) { struct ice_dpll_pin *pin = &pf->dplls.rclk; pin->prop.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT; pin->prop.capabilities |= DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE; pin->pf = pf; return ice_dpll_pin_state_update(pf, pin, ICE_DPLL_PIN_TYPE_RCLK_INPUT, NULL); } /** * ice_dpll_init_pins_info - init pins info wrapper * @pf: board private structure * @pin_type: type of pins being initialized * * Wraps functions for pin initialization. * * Return: * * 0 - success * * negative - init failure reason */ static int ice_dpll_init_pins_info(struct ice_pf *pf, enum ice_dpll_pin_type pin_type) { switch (pin_type) { case ICE_DPLL_PIN_TYPE_INPUT: case ICE_DPLL_PIN_TYPE_OUTPUT: return ice_dpll_init_info_direct_pins(pf, pin_type); case ICE_DPLL_PIN_TYPE_RCLK_INPUT: return ice_dpll_init_info_rclk_pin(pf); default: return -EINVAL; } } /** * ice_dpll_deinit_info - release memory allocated for pins info * @pf: board private structure * * Release memory allocated for pins by ice_dpll_init_info function. */ static void ice_dpll_deinit_info(struct ice_pf *pf) { kfree(pf->dplls.inputs); kfree(pf->dplls.outputs); kfree(pf->dplls.eec.input_prio); kfree(pf->dplls.pps.input_prio); } /** * ice_dpll_init_info - prepare pf's dpll information structure * @pf: board private structure * @cgu: if cgu is present and controlled by this NIC * * Acquire (from HW) and set basic dpll information (on pf->dplls struct). * * Return: * * 0 - success * * negative - init failure reason */ static int ice_dpll_init_info(struct ice_pf *pf, bool cgu) { struct ice_aqc_get_cgu_abilities abilities; struct ice_dpll *de = &pf->dplls.eec; struct ice_dpll *dp = &pf->dplls.pps; struct ice_dplls *d = &pf->dplls; struct ice_hw *hw = &pf->hw; int ret, alloc_size, i; d->clock_id = ice_generate_clock_id(pf); ret = ice_aq_get_cgu_abilities(hw, &abilities); if (ret) { dev_err(ice_pf_to_dev(pf), "err:%d %s failed to read cgu abilities\n", ret, ice_aq_str(hw->adminq.sq_last_status)); return ret; } de->dpll_idx = abilities.eec_dpll_idx; dp->dpll_idx = abilities.pps_dpll_idx; d->num_inputs = abilities.num_inputs; d->num_outputs = abilities.num_outputs; d->input_phase_adj_max = le32_to_cpu(abilities.max_in_phase_adj); d->output_phase_adj_max = le32_to_cpu(abilities.max_out_phase_adj); alloc_size = sizeof(*d->inputs) * d->num_inputs; d->inputs = kzalloc(alloc_size, GFP_KERNEL); if (!d->inputs) return -ENOMEM; alloc_size = sizeof(*de->input_prio) * d->num_inputs; de->input_prio = kzalloc(alloc_size, GFP_KERNEL); if (!de->input_prio) return -ENOMEM; dp->input_prio = kzalloc(alloc_size, GFP_KERNEL); if (!dp->input_prio) return -ENOMEM; ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_INPUT); if (ret) goto deinit_info; if (cgu) { alloc_size = sizeof(*d->outputs) * d->num_outputs; d->outputs = kzalloc(alloc_size, GFP_KERNEL); if (!d->outputs) { ret = -ENOMEM; goto deinit_info; } ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_OUTPUT); if (ret) goto deinit_info; } ret = ice_get_cgu_rclk_pin_info(&pf->hw, &d->base_rclk_idx, &pf->dplls.rclk.num_parents); if (ret) return ret; for (i = 0; i < pf->dplls.rclk.num_parents; i++) pf->dplls.rclk.parent_idx[i] = d->base_rclk_idx + i; ret = ice_dpll_init_pins_info(pf, ICE_DPLL_PIN_TYPE_RCLK_INPUT); if (ret) return ret; de->mode = DPLL_MODE_AUTOMATIC; dp->mode = DPLL_MODE_AUTOMATIC; 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); return 0; deinit_info: dev_err(ice_pf_to_dev(pf), "%s - fail: d->inputs:%p, de->input_prio:%p, dp->input_prio:%p, d->outputs:%p\n", __func__, d->inputs, de->input_prio, dp->input_prio, d->outputs); ice_dpll_deinit_info(pf); return ret; } /** * ice_dpll_deinit - Disable the driver/HW support for dpll subsystem * the dpll device. * @pf: board private structure * * Handles the cleanup work required after dpll initialization, freeing * resources and unregistering the dpll, pin and all resources used for * handling them. * * Context: Destroys pf->dplls.lock mutex. Call only if ICE_FLAG_DPLL was set. */ void ice_dpll_deinit(struct ice_pf *pf) { bool cgu = ice_is_feature_supported(pf, ICE_F_CGU); clear_bit(ICE_FLAG_DPLL, pf->flags); if (cgu) ice_dpll_deinit_worker(pf); ice_dpll_deinit_pins(pf, cgu); ice_dpll_deinit_dpll(pf, &pf->dplls.pps, cgu); ice_dpll_deinit_dpll(pf, &pf->dplls.eec, cgu); ice_dpll_deinit_info(pf); mutex_destroy(&pf->dplls.lock); } /** * ice_dpll_init - initialize support for dpll subsystem * @pf: board private structure * * Set up the device dplls, register them and pins connected within Linux dpll * subsystem. Allow userspace to obtain state of DPLL and handling of DPLL * configuration requests. * * Context: Initializes pf->dplls.lock mutex. */ void ice_dpll_init(struct ice_pf *pf) { bool cgu = ice_is_feature_supported(pf, ICE_F_CGU); struct ice_dplls *d = &pf->dplls; int err = 0; err = ice_dpll_init_info(pf, cgu); if (err) goto err_exit; err = ice_dpll_init_dpll(pf, &pf->dplls.eec, cgu, DPLL_TYPE_EEC); if (err) goto deinit_info; err = ice_dpll_init_dpll(pf, &pf->dplls.pps, cgu, DPLL_TYPE_PPS); if (err) goto deinit_eec; err = ice_dpll_init_pins(pf, cgu); if (err) goto deinit_pps; mutex_init(&d->lock); if (cgu) { err = ice_dpll_init_worker(pf); if (err) goto deinit_pins; } set_bit(ICE_FLAG_DPLL, pf->flags); return; deinit_pins: ice_dpll_deinit_pins(pf, cgu); deinit_pps: ice_dpll_deinit_dpll(pf, &pf->dplls.pps, cgu); deinit_eec: ice_dpll_deinit_dpll(pf, &pf->dplls.eec, cgu); deinit_info: ice_dpll_deinit_info(pf); err_exit: mutex_destroy(&d->lock); dev_warn(ice_pf_to_dev(pf), "DPLLs init failure err:%d\n", err); }