diff options
Diffstat (limited to 'drivers/net/ethernet/intel/ice/ice_tspll.c')
-rw-r--r-- | drivers/net/ethernet/intel/ice/ice_tspll.c | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/drivers/net/ethernet/intel/ice/ice_tspll.c b/drivers/net/ethernet/intel/ice/ice_tspll.c new file mode 100644 index 000000000000..66320a4ab86f --- /dev/null +++ b/drivers/net/ethernet/intel/ice/ice_tspll.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025, Intel Corporation. */ + +#include "ice.h" +#include "ice_lib.h" +#include "ice_ptp_hw.h" + +static const struct +ice_tspll_params_e82x e82x_tspll_params[NUM_ICE_TSPLL_FREQ] = { + [ICE_TSPLL_FREQ_25_000] = { + .refclk_pre_div = 1, + .post_pll_div = 6, + .feedback_div = 197, + .frac_n_div = 2621440, + }, + [ICE_TSPLL_FREQ_122_880] = { + .refclk_pre_div = 5, + .post_pll_div = 7, + .feedback_div = 223, + .frac_n_div = 524288 + }, + [ICE_TSPLL_FREQ_125_000] = { + .refclk_pre_div = 5, + .post_pll_div = 7, + .feedback_div = 223, + .frac_n_div = 524288 + }, + [ICE_TSPLL_FREQ_153_600] = { + .refclk_pre_div = 5, + .post_pll_div = 6, + .feedback_div = 159, + .frac_n_div = 1572864 + }, + [ICE_TSPLL_FREQ_156_250] = { + .refclk_pre_div = 5, + .post_pll_div = 6, + .feedback_div = 159, + .frac_n_div = 1572864 + }, + [ICE_TSPLL_FREQ_245_760] = { + .refclk_pre_div = 10, + .post_pll_div = 7, + .feedback_div = 223, + .frac_n_div = 524288 + }, +}; + +/** + * ice_tspll_clk_freq_str - Convert time_ref_freq to string + * @clk_freq: Clock frequency + * + * Return: specified TIME_REF clock frequency converted to a string. + */ +static const char *ice_tspll_clk_freq_str(enum ice_tspll_freq clk_freq) +{ + switch (clk_freq) { + case ICE_TSPLL_FREQ_25_000: + return "25 MHz"; + case ICE_TSPLL_FREQ_122_880: + return "122.88 MHz"; + case ICE_TSPLL_FREQ_125_000: + return "125 MHz"; + case ICE_TSPLL_FREQ_153_600: + return "153.6 MHz"; + case ICE_TSPLL_FREQ_156_250: + return "156.25 MHz"; + case ICE_TSPLL_FREQ_245_760: + return "245.76 MHz"; + default: + return "Unknown"; + } +} + +/** + * ice_tspll_default_freq - Return default frequency for a MAC type + * @mac_type: MAC type + * + * Return: default TSPLL frequency for a correct MAC type, -ERANGE otherwise. + */ +static enum ice_tspll_freq ice_tspll_default_freq(enum ice_mac_type mac_type) +{ + switch (mac_type) { + case ICE_MAC_GENERIC: + return ICE_TSPLL_FREQ_25_000; + case ICE_MAC_GENERIC_3K_E825: + return ICE_TSPLL_FREQ_156_250; + default: + return -ERANGE; + } +} + +/** + * ice_tspll_check_params - Check if TSPLL params are correct + * @hw: Pointer to the HW struct + * @clk_freq: Clock frequency to program + * @clk_src: Clock source to select (TIME_REF or TCXO) + * + * Return: true if TSPLL params are correct, false otherwise. + */ +static bool ice_tspll_check_params(struct ice_hw *hw, + enum ice_tspll_freq clk_freq, + enum ice_clk_src clk_src) +{ + if (clk_freq >= NUM_ICE_TSPLL_FREQ) { + dev_warn(ice_hw_to_dev(hw), "Invalid TSPLL frequency %u\n", + clk_freq); + return false; + } + + if (clk_src >= NUM_ICE_CLK_SRC) { + dev_warn(ice_hw_to_dev(hw), "Invalid clock source %u\n", + clk_src); + return false; + } + + if ((hw->mac_type == ICE_MAC_GENERIC_3K_E825 || + clk_src == ICE_CLK_SRC_TCXO) && + clk_freq != ice_tspll_default_freq(hw->mac_type)) { + dev_warn(ice_hw_to_dev(hw), "Unsupported frequency for this clock source\n"); + return false; + } + + return true; +} + +/** + * ice_tspll_clk_src_str - Convert time_ref_src to string + * @clk_src: Clock source + * + * Return: specified clock source converted to its string name + */ +static const char *ice_tspll_clk_src_str(enum ice_clk_src clk_src) +{ + switch (clk_src) { + case ICE_CLK_SRC_TCXO: + return "TCXO"; + case ICE_CLK_SRC_TIME_REF: + return "TIME_REF"; + default: + return "Unknown"; + } +} + +/** + * ice_tspll_log_cfg - Log current/new TSPLL configuration + * @hw: Pointer to the HW struct + * @enable: CGU enabled/disabled + * @clk_src: Current clock source + * @tspll_freq: Current clock frequency + * @lock: CGU lock status + * @new_cfg: true if this is a new config + */ +static void ice_tspll_log_cfg(struct ice_hw *hw, bool enable, u8 clk_src, + u8 tspll_freq, bool lock, bool new_cfg) +{ + dev_dbg(ice_hw_to_dev(hw), + "%s TSPLL configuration -- %s, src %s, freq %s, PLL %s\n", + new_cfg ? "New" : "Current", str_enabled_disabled(enable), + ice_tspll_clk_src_str((enum ice_clk_src)clk_src), + ice_tspll_clk_freq_str((enum ice_tspll_freq)tspll_freq), + lock ? "locked" : "unlocked"); +} + +/** + * ice_tspll_cfg_e82x - Configure the Clock Generation Unit TSPLL + * @hw: Pointer to the HW struct + * @clk_freq: Clock frequency to program + * @clk_src: Clock source to select (TIME_REF, or TCXO) + * + * Configure the Clock Generation Unit with the desired clock frequency and + * time reference, enabling the PLL which drives the PTP hardware clock. + * + * Return: + * * %0 - success + * * %-EINVAL - input parameters are incorrect + * * %-EBUSY - failed to lock TSPLL + * * %other - CGU read/write failure + */ +static int ice_tspll_cfg_e82x(struct ice_hw *hw, enum ice_tspll_freq clk_freq, + enum ice_clk_src clk_src) +{ + u32 val, r9, r24; + int err; + + err = ice_read_cgu_reg(hw, ICE_CGU_R9, &r9); + if (err) + return err; + + err = ice_read_cgu_reg(hw, ICE_CGU_R24, &r24); + if (err) + return err; + + err = ice_read_cgu_reg(hw, ICE_CGU_RO_BWM_LF, &val); + if (err) + return err; + + ice_tspll_log_cfg(hw, !!FIELD_GET(ICE_CGU_R23_R24_TSPLL_ENABLE, r24), + FIELD_GET(ICE_CGU_R23_R24_TIME_REF_SEL, r24), + FIELD_GET(ICE_CGU_R9_TIME_REF_FREQ_SEL, r9), + !!FIELD_GET(ICE_CGU_RO_BWM_LF_TRUE_LOCK, val), + false); + + /* Disable the PLL before changing the clock source or frequency */ + if (FIELD_GET(ICE_CGU_R23_R24_TSPLL_ENABLE, r24)) { + r24 &= ~ICE_CGU_R23_R24_TSPLL_ENABLE; + + err = ice_write_cgu_reg(hw, ICE_CGU_R24, r24); + if (err) + return err; + } + + /* Set the frequency */ + r9 &= ~ICE_CGU_R9_TIME_REF_FREQ_SEL; + r9 |= FIELD_PREP(ICE_CGU_R9_TIME_REF_FREQ_SEL, clk_freq); + err = ice_write_cgu_reg(hw, ICE_CGU_R9, r9); + if (err) + return err; + + /* Configure the TSPLL feedback divisor */ + err = ice_read_cgu_reg(hw, ICE_CGU_R19, &val); + if (err) + return err; + + val &= ~(ICE_CGU_R19_TSPLL_FBDIV_INTGR_E82X | ICE_CGU_R19_TSPLL_NDIVRATIO); + val |= FIELD_PREP(ICE_CGU_R19_TSPLL_FBDIV_INTGR_E82X, + e82x_tspll_params[clk_freq].feedback_div); + val |= FIELD_PREP(ICE_CGU_R19_TSPLL_NDIVRATIO, 1); + + err = ice_write_cgu_reg(hw, ICE_CGU_R19, val); + if (err) + return err; + + /* Configure the TSPLL post divisor */ + err = ice_read_cgu_reg(hw, ICE_CGU_R22, &val); + if (err) + return err; + + val &= ~(ICE_CGU_R22_TIME1588CLK_DIV | + ICE_CGU_R22_TIME1588CLK_DIV2); + val |= FIELD_PREP(ICE_CGU_R22_TIME1588CLK_DIV, + e82x_tspll_params[clk_freq].post_pll_div); + + err = ice_write_cgu_reg(hw, ICE_CGU_R22, val); + if (err) + return err; + + /* Configure the TSPLL pre divisor and clock source */ + err = ice_read_cgu_reg(hw, ICE_CGU_R24, &r24); + if (err) + return err; + + r24 &= ~(ICE_CGU_R23_R24_REF1588_CK_DIV | ICE_CGU_R24_FBDIV_FRAC | + ICE_CGU_R23_R24_TIME_REF_SEL); + r24 |= FIELD_PREP(ICE_CGU_R23_R24_REF1588_CK_DIV, + e82x_tspll_params[clk_freq].refclk_pre_div); + r24 |= FIELD_PREP(ICE_CGU_R24_FBDIV_FRAC, + e82x_tspll_params[clk_freq].frac_n_div); + r24 |= FIELD_PREP(ICE_CGU_R23_R24_TIME_REF_SEL, clk_src); + + err = ice_write_cgu_reg(hw, ICE_CGU_R24, r24); + if (err) + return err; + + /* Wait to ensure everything is stable */ + usleep_range(10, 20); + + /* Finally, enable the PLL */ + r24 |= ICE_CGU_R23_R24_TSPLL_ENABLE; + + err = ice_write_cgu_reg(hw, ICE_CGU_R24, r24); + if (err) + return err; + + /* Wait at least 1 ms to verify if the PLL locks */ + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + + err = ice_read_cgu_reg(hw, ICE_CGU_RO_BWM_LF, &val); + if (err) + return err; + + if (!(val & ICE_CGU_RO_BWM_LF_TRUE_LOCK)) { + dev_warn(ice_hw_to_dev(hw), "CGU PLL failed to lock\n"); + return -EBUSY; + } + + err = ice_read_cgu_reg(hw, ICE_CGU_R9, &r9); + if (err) + return err; + err = ice_read_cgu_reg(hw, ICE_CGU_R24, &r24); + if (err) + return err; + + ice_tspll_log_cfg(hw, !!FIELD_GET(ICE_CGU_R23_R24_TSPLL_ENABLE, r24), + FIELD_GET(ICE_CGU_R23_R24_TIME_REF_SEL, r24), + FIELD_GET(ICE_CGU_R9_TIME_REF_FREQ_SEL, r9), + true, true); + + return 0; +} + +/** + * ice_tspll_dis_sticky_bits_e82x - disable TSPLL sticky bits + * @hw: Pointer to the HW struct + * + * Configure the Clock Generation Unit TSPLL sticky bits so they don't latch on + * losing TSPLL lock, but always show current state. + * + * Return: 0 on success, other error codes when failed to read/write CGU. + */ +static int ice_tspll_dis_sticky_bits_e82x(struct ice_hw *hw) +{ + u32 val; + int err; + + err = ice_read_cgu_reg(hw, ICE_CGU_CNTR_BIST, &val); + if (err) + return err; + + val &= ~(ICE_CGU_CNTR_BIST_PLLLOCK_SEL_0 | + ICE_CGU_CNTR_BIST_PLLLOCK_SEL_1); + + return ice_write_cgu_reg(hw, ICE_CGU_CNTR_BIST, val); +} + +/** + * ice_tspll_cfg_e825c - Configure the TSPLL for E825-C + * @hw: Pointer to the HW struct + * @clk_freq: Clock frequency to program + * @clk_src: Clock source to select (TIME_REF, or TCXO) + * + * Configure the Clock Generation Unit with the desired clock frequency and + * time reference, enabling the PLL which drives the PTP hardware clock. + * + * Return: + * * %0 - success + * * %-EINVAL - input parameters are incorrect + * * %-EBUSY - failed to lock TSPLL + * * %other - CGU read/write failure + */ +static int ice_tspll_cfg_e825c(struct ice_hw *hw, enum ice_tspll_freq clk_freq, + enum ice_clk_src clk_src) +{ + u32 val, r9, r23; + int err; + + err = ice_read_cgu_reg(hw, ICE_CGU_R9, &r9); + if (err) + return err; + + err = ice_read_cgu_reg(hw, ICE_CGU_R23, &r23); + if (err) + return err; + + err = ice_read_cgu_reg(hw, ICE_CGU_RO_LOCK, &val); + if (err) + return err; + + ice_tspll_log_cfg(hw, !!FIELD_GET(ICE_CGU_R23_R24_TSPLL_ENABLE, r23), + FIELD_GET(ICE_CGU_R23_R24_TIME_REF_SEL, r23), + FIELD_GET(ICE_CGU_R9_TIME_REF_FREQ_SEL, r9), + !!FIELD_GET(ICE_CGU_RO_LOCK_TRUE_LOCK, val), + false); + + /* Disable the PLL before changing the clock source or frequency */ + if (FIELD_GET(ICE_CGU_R23_R24_TSPLL_ENABLE, r23)) { + r23 &= ~ICE_CGU_R23_R24_TSPLL_ENABLE; + + err = ice_write_cgu_reg(hw, ICE_CGU_R23, r23); + if (err) + return err; + } + + if (FIELD_GET(ICE_CGU_R9_TIME_SYNC_EN, r9)) { + r9 &= ~ICE_CGU_R9_TIME_SYNC_EN; + + err = ice_write_cgu_reg(hw, ICE_CGU_R9, r9); + if (err) + return err; + } + + /* Set the frequency and enable the correct receiver */ + r9 &= ~(ICE_CGU_R9_TIME_REF_FREQ_SEL | ICE_CGU_R9_CLK_EREF0_EN | + ICE_CGU_R9_TIME_REF_EN); + r9 |= FIELD_PREP(ICE_CGU_R9_TIME_REF_FREQ_SEL, clk_freq); + if (clk_src == ICE_CLK_SRC_TCXO) + r9 |= ICE_CGU_R9_CLK_EREF0_EN; + else + r9 |= ICE_CGU_R9_TIME_REF_EN; + r9 |= ICE_CGU_R9_TIME_SYNC_EN; + err = ice_write_cgu_reg(hw, ICE_CGU_R9, r9); + if (err) + return err; + + /* Choose the referenced frequency */ + err = ice_read_cgu_reg(hw, ICE_CGU_R16, &val); + if (err) + return err; + val &= ~ICE_CGU_R16_TSPLL_CK_REFCLKFREQ; + val |= FIELD_PREP(ICE_CGU_R16_TSPLL_CK_REFCLKFREQ, + ICE_TSPLL_CK_REFCLKFREQ_E825); + err = ice_write_cgu_reg(hw, ICE_CGU_R16, val); + if (err) + return err; + + /* Configure the TSPLL feedback divisor */ + err = ice_read_cgu_reg(hw, ICE_CGU_R19, &val); + if (err) + return err; + + val &= ~(ICE_CGU_R19_TSPLL_FBDIV_INTGR_E825 | + ICE_CGU_R19_TSPLL_NDIVRATIO); + val |= FIELD_PREP(ICE_CGU_R19_TSPLL_FBDIV_INTGR_E825, + ICE_TSPLL_FBDIV_INTGR_E825); + val |= FIELD_PREP(ICE_CGU_R19_TSPLL_NDIVRATIO, + ICE_TSPLL_NDIVRATIO_E825); + + err = ice_write_cgu_reg(hw, ICE_CGU_R19, val); + if (err) + return err; + + /* Configure the TSPLL post divisor, these two are constant */ + err = ice_read_cgu_reg(hw, ICE_CGU_R22, &val); + if (err) + return err; + + val &= ~(ICE_CGU_R22_TIME1588CLK_DIV | + ICE_CGU_R22_TIME1588CLK_DIV2); + val |= FIELD_PREP(ICE_CGU_R22_TIME1588CLK_DIV, 5); + + err = ice_write_cgu_reg(hw, ICE_CGU_R22, val); + if (err) + return err; + + /* Configure the TSPLL pre divisor (constant) and clock source */ + err = ice_read_cgu_reg(hw, ICE_CGU_R23, &r23); + if (err) + return err; + + r23 &= ~(ICE_CGU_R23_R24_REF1588_CK_DIV | ICE_CGU_R23_R24_TIME_REF_SEL); + r23 |= FIELD_PREP(ICE_CGU_R23_R24_TIME_REF_SEL, clk_src); + + err = ice_write_cgu_reg(hw, ICE_CGU_R23, r23); + if (err) + return err; + + /* Clear the R24 register. */ + err = ice_write_cgu_reg(hw, ICE_CGU_R24, 0); + if (err) + return err; + + /* Wait to ensure everything is stable */ + usleep_range(10, 20); + + /* Finally, enable the PLL */ + r23 |= ICE_CGU_R23_R24_TSPLL_ENABLE; + + err = ice_write_cgu_reg(hw, ICE_CGU_R23, r23); + if (err) + return err; + + /* Wait at least 1 ms to verify if the PLL locks */ + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + + err = ice_read_cgu_reg(hw, ICE_CGU_RO_LOCK, &val); + if (err) + return err; + + if (!(val & ICE_CGU_RO_LOCK_TRUE_LOCK)) { + dev_warn(ice_hw_to_dev(hw), "CGU PLL failed to lock\n"); + return -EBUSY; + } + + err = ice_read_cgu_reg(hw, ICE_CGU_R9, &r9); + if (err) + return err; + err = ice_read_cgu_reg(hw, ICE_CGU_R23, &r23); + if (err) + return err; + + ice_tspll_log_cfg(hw, !!FIELD_GET(ICE_CGU_R23_R24_TSPLL_ENABLE, r23), + FIELD_GET(ICE_CGU_R23_R24_TIME_REF_SEL, r23), + FIELD_GET(ICE_CGU_R9_TIME_REF_FREQ_SEL, r9), + true, true); + + return 0; +} + +/** + * ice_tspll_dis_sticky_bits_e825c - disable TSPLL sticky bits for E825-C + * @hw: Pointer to the HW struct + * + * Configure the Clock Generation Unit TSPLL sticky bits so they don't latch on + * losing TSPLL lock, but always show current state. + * + * Return: 0 on success, other error codes when failed to read/write CGU. + */ +static int ice_tspll_dis_sticky_bits_e825c(struct ice_hw *hw) +{ + u32 val; + int err; + + err = ice_read_cgu_reg(hw, ICE_CGU_BW_TDC, &val); + if (err) + return err; + + val &= ~ICE_CGU_BW_TDC_PLLLOCK_SEL; + + return ice_write_cgu_reg(hw, ICE_CGU_BW_TDC, val); +} + +/** + * ice_tspll_cfg_pps_out_e825c - Enable/disable 1PPS output and set amplitude + * @hw: pointer to the HW struct + * @enable: true to enable 1PPS output, false to disable it + * + * Return: 0 on success, other negative error code when CGU read/write failed. + */ +int ice_tspll_cfg_pps_out_e825c(struct ice_hw *hw, bool enable) +{ + u32 val; + int err; + + err = ice_read_cgu_reg(hw, ICE_CGU_R9, &val); + if (err) + return err; + + val &= ~(ICE_CGU_R9_ONE_PPS_OUT_EN | ICE_CGU_R9_ONE_PPS_OUT_AMP); + val |= FIELD_PREP(ICE_CGU_R9_ONE_PPS_OUT_EN, enable) | + ICE_CGU_R9_ONE_PPS_OUT_AMP; + + return ice_write_cgu_reg(hw, ICE_CGU_R9, val); +} + +/** + * ice_tspll_cfg - Configure the Clock Generation Unit TSPLL + * @hw: Pointer to the HW struct + * @clk_freq: Clock frequency to program + * @clk_src: Clock source to select (TIME_REF, or TCXO) + * + * Configure the Clock Generation Unit with the desired clock frequency and + * time reference, enabling the TSPLL which drives the PTP hardware clock. + * + * Return: 0 on success, -ERANGE on unsupported MAC type, other negative error + * codes when failed to configure CGU. + */ +static int ice_tspll_cfg(struct ice_hw *hw, enum ice_tspll_freq clk_freq, + enum ice_clk_src clk_src) +{ + switch (hw->mac_type) { + case ICE_MAC_GENERIC: + return ice_tspll_cfg_e82x(hw, clk_freq, clk_src); + case ICE_MAC_GENERIC_3K_E825: + return ice_tspll_cfg_e825c(hw, clk_freq, clk_src); + default: + return -ERANGE; + } +} + +/** + * ice_tspll_dis_sticky_bits - disable TSPLL sticky bits + * @hw: Pointer to the HW struct + * + * Configure the Clock Generation Unit TSPLL sticky bits so they don't latch on + * losing TSPLL lock, but always show current state. + * + * Return: 0 on success, -ERANGE on unsupported MAC type. + */ +static int ice_tspll_dis_sticky_bits(struct ice_hw *hw) +{ + switch (hw->mac_type) { + case ICE_MAC_GENERIC: + return ice_tspll_dis_sticky_bits_e82x(hw); + case ICE_MAC_GENERIC_3K_E825: + return ice_tspll_dis_sticky_bits_e825c(hw); + default: + return -ERANGE; + } +} + +/** + * ice_tspll_init - Initialize TSPLL with settings from firmware + * @hw: Pointer to the HW structure + * + * Initialize the Clock Generation Unit of the E82X/E825 device. + * + * Return: 0 on success, other error codes when failed to read/write/cfg CGU. + */ +int ice_tspll_init(struct ice_hw *hw) +{ + struct ice_ts_func_info *ts_info = &hw->func_caps.ts_func_info; + enum ice_tspll_freq tspll_freq; + enum ice_clk_src clk_src; + int err; + + /* Only E822, E823 and E825 products support TSPLL */ + if (hw->mac_type != ICE_MAC_GENERIC && + hw->mac_type != ICE_MAC_GENERIC_3K_E825) + return 0; + + tspll_freq = (enum ice_tspll_freq)ts_info->time_ref; + clk_src = (enum ice_clk_src)ts_info->clk_src; + if (!ice_tspll_check_params(hw, tspll_freq, clk_src)) + return -EINVAL; + + /* Disable sticky lock detection so lock status reported is accurate */ + err = ice_tspll_dis_sticky_bits(hw); + if (err) + return err; + + /* Configure the TSPLL using the parameters from the function + * capabilities. + */ + err = ice_tspll_cfg(hw, tspll_freq, clk_src); + if (err) { + dev_warn(ice_hw_to_dev(hw), "Failed to lock TSPLL to predefined frequency. Retrying with fallback frequency.\n"); + + /* Try to lock to internal TCXO as a fallback. */ + tspll_freq = ice_tspll_default_freq(hw->mac_type); + clk_src = ICE_CLK_SRC_TCXO; + err = ice_tspll_cfg(hw, tspll_freq, clk_src); + if (err) + dev_warn(ice_hw_to_dev(hw), "Failed to lock TSPLL to fallback frequency.\n"); + } + + return err; +} |