Add support for bee equalization algorithm used by kr training:
3-Taps Bit Edge Equalization (BEE) algorithm

Signed-off-by: Florinel Iordache <florinel.iorda...@nxp.com>
---
 drivers/net/phy/backplane/Kconfig  |   11 +
 drivers/net/phy/backplane/Makefile |    1 +
 drivers/net/phy/backplane/eq_bee.c | 1076 ++++++++++++++++++++++++++++++++++++
 3 files changed, 1088 insertions(+)
 create mode 100644 drivers/net/phy/backplane/eq_bee.c

diff --git a/drivers/net/phy/backplane/Kconfig 
b/drivers/net/phy/backplane/Kconfig
index 3e20a78..ee5cf1c 100644
--- a/drivers/net/phy/backplane/Kconfig
+++ b/drivers/net/phy/backplane/Kconfig
@@ -19,6 +19,17 @@ config ETH_BACKPLANE_FIXED
          No Equalization algorithm is used to adapt the initial coefficients
          initially set by the user.
 
+config ETH_BACKPLANE_BEE
+       tristate "3-Taps Bit Edge Equalization (BEE) algorithm"
+       depends on ETH_BACKPLANE
+       help
+         This module provides a driver for BEE algorithm: 3-Taps
+         Bit Edge Equalization. This algorithm is using a method
+         based on 3-taps coefficients for mitigating intersymbol
+         interference (ISI) in high-speed backplane applications.
+         The initial values for algorithm coefficient values are
+         user configurable and used as a starting point of the algorithm.
+
 config ETH_BACKPLANE_QORIQ
        tristate "QorIQ Ethernet Backplane driver"
        depends on ETH_BACKPLANE
diff --git a/drivers/net/phy/backplane/Makefile 
b/drivers/net/phy/backplane/Makefile
index 3ae3d8b..30a2ebb 100644
--- a/drivers/net/phy/backplane/Makefile
+++ b/drivers/net/phy/backplane/Makefile
@@ -5,6 +5,7 @@
 
 obj-$(CONFIG_ETH_BACKPLANE) += eth_backplane.o
 obj-$(CONFIG_ETH_BACKPLANE_FIXED) += eq_fixed.o
+obj-$(CONFIG_ETH_BACKPLANE_BEE) += eq_bee.o
 obj-$(CONFIG_ETH_BACKPLANE_QORIQ) += eth_backplane_qoriq.o
 
 eth_backplane-objs     := backplane.o link_training.o
diff --git a/drivers/net/phy/backplane/eq_bee.c 
b/drivers/net/phy/backplane/eq_bee.c
new file mode 100644
index 0000000..045ed7e
--- /dev/null
+++ b/drivers/net/phy/backplane/eq_bee.c
@@ -0,0 +1,1076 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* 3-Taps Bit Edge Equalization (BEE) algorithm
+ *
+ * Copyright 2019-2020 NXP
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "equalization.h"
+
+#define ALGORITHM_NAME         "backplane_bee_3tap"
+#define ALGORITHM_DESCR                "3-Taps Bit Edge Equalization"
+#define ALGORITHM_VERSION      "1.5.5"
+
+/* BEE algorithm timeouts */
+#define TIMEOUT_LONG                           3
+#define TIMEOUT_M1                             3
+
+/* Size of equalization snapshots data collection */
+#define EQ_SNAPSHOTS_SIZE                      10
+
+/* Rx link quality conditions:
+ * The following macros are used to determine the Rx link quality
+ * which is used to decide if/when to proceed with BinLong/BinM1 modules.
+ * The code that considers Rx in good quality is always in place:
+ * Rx link is in 'good quality' if:
+ * Bin1, Bin2 and Bin3 are toggling
+ *
+ * These macros are used to enable less quality link conditions.
+ * If Rx link quality is considered good enough then proceed to BinLong/BinM1
+ */
+
+/* Rx is 'less quality' if:
+ * Bin1 is toggling
+ * AND
+ *   Bin2 is Early, GainMF stuck at max_eq_gain and Bin3 is Late
+ *   OR
+ *   Bin2 is Late, GainMF stuck at min_eq_gain and Bin3 is Early
+ */
+#define ENABLE_LESS_QUALITY_CONDITION
+
+/* Rx is 'even less quality' if:
+ * Bin1 is Early AND GainHF stuck at max_eq_gain and Bin2 is Late AND
+ *     GainMF stuck at min_eq_gain
+ * OR
+ * Bin1 is Late AND GainHF stuck at min_eq_gain AND
+ *     Bin2 is Early, GainMF stuck at max_eq_gain
+ */
+#define ENABLE_EVEN_LESS_QUALITY_CONDITION
+
+/* Rx is 'seemingly quality' if:
+ * Bin1 is always Late for all snapshots AND
+ *     GainHF is stuck at min_eq_gain
+ * AND
+ * Bin2 and Bin3 are both Toggling
+ */
+#define ENABLE_SEEMINGLY_QUALITY_CONDITION
+
+enum bin_state {
+       BIN_INVALID,
+       BIN_EARLY,
+       BIN_TOGGLE,
+       BIN_LATE
+};
+
+struct eq_data_priv {
+       /* Equalization Algorithm setup data */
+       struct equalizer_driver eqdrv;
+
+       /* Bin state */
+       enum bin_state bin_m1_state;
+       enum bin_state bin_long_state;
+       enum bin_state prev_bin_m1_state;
+       enum bin_state prev_bin_long_state;
+
+       /* Bin training status */
+       bool bin_m1_stop;
+       bool bin_long_stop;
+       int m1_min_max_cnt;
+       int long_min_max_cnt;
+
+       /* Algorithm controlled value */
+       u32 ld_update;
+
+       /* Bit edge statistics: Bin snapshots */
+       s16 bin1_snapshot[EQ_SNAPSHOTS_SIZE];
+       s16 bin2_snapshot[EQ_SNAPSHOTS_SIZE];
+       s16 bin3_snapshot[EQ_SNAPSHOTS_SIZE];
+       s16 bin_long_snapshot[EQ_SNAPSHOTS_SIZE];
+       s16 bin_m1_snapshot[EQ_SNAPSHOTS_SIZE];
+       s16 bin_offset_snapshot[EQ_SNAPSHOTS_SIZE];
+
+       /* Gain snapshots */
+       u8 gain_hf_snapshot[EQ_SNAPSHOTS_SIZE];
+       u8 gain_mf_snapshot[EQ_SNAPSHOTS_SIZE];
+
+       /* Offset status snapshot */
+       u8 osestat_snapshot[EQ_SNAPSHOTS_SIZE];
+};
+
+static enum bin_state get_bin_snapshots_state(struct eq_data_priv *priv,
+                                             enum eqc_type type)
+{
+       s16 bin_snp_av_thr_low, bin_snp_av_thr_high;
+       s16 snapshot_average, snapshot_sum = 0;
+       struct eqc_range *bin_range;
+       s16 *bin_snapshot;
+       int i;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return BIN_INVALID;
+       }
+
+       switch (type) {
+       case EQC_BIN_1:
+               bin_snapshot = priv->bin1_snapshot;
+               break;
+       case EQC_BIN_2:
+               bin_snapshot = priv->bin2_snapshot;
+               break;
+       case EQC_BIN_3:
+               bin_snapshot = priv->bin3_snapshot;
+               break;
+       case EQC_BIN_LONG:
+               bin_snapshot = priv->bin_long_snapshot;
+               break;
+       case EQC_BIN_OFFSET:
+               bin_snapshot = priv->bin_offset_snapshot;
+               break;
+       case EQC_BIN_M1:
+               bin_snapshot = priv->bin_m1_snapshot;
+               break;
+       default:
+               /* invalid bin type */
+               return BIN_INVALID;
+       }
+       if (!bin_snapshot)
+               return BIN_INVALID;
+
+       bin_range = priv->eqdrv.equalizer->ops.get_counter_range(type);
+       if (!bin_range)
+               return BIN_INVALID;
+
+       bin_snp_av_thr_low = bin_range->mid_low;
+       bin_snp_av_thr_high = bin_range->mid_high;
+
+       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++)
+               snapshot_sum += bin_snapshot[i];
+
+       snapshot_average = (s16)(snapshot_sum / EQ_SNAPSHOTS_SIZE);
+
+       if (snapshot_average >= -256 && snapshot_average < bin_snp_av_thr_low)
+               return BIN_EARLY;
+       else if (snapshot_average >= bin_snp_av_thr_low &&
+                snapshot_average < bin_snp_av_thr_high)
+               return BIN_TOGGLE;
+       else if (snapshot_average >= bin_snp_av_thr_high &&
+                snapshot_average <= 255)
+               return BIN_LATE;
+
+       return BIN_INVALID;
+}
+
+static u32 process_bin_m1_toggle(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum req_type prev_req_cm1;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       prev_req_cm1 = lt_decode_coef_update(priv->ld_update, C_M1);
+
+       /* Toggle path */
+       if (priv->prev_bin_m1_state == priv->bin_m1_state) {
+               /* Hold C- */
+               update = lt_encode_startup_request(REQ_HOLD);
+       } else {
+               update = lt_encode_startup_request(REQ_HOLD);
+               /* If previous step moved C- repeat C- move */
+               if (prev_req_cm1 == REQ_INC ||
+                   prev_req_cm1 == REQ_DEC)
+                       update = lt_encode_request(update, prev_req_cm1, C_M1);
+       }
+
+       return update;
+}
+
+static u32 process_bin_m1_prev_toggle(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum req_type prev_req_cm1;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       prev_req_cm1 = lt_decode_coef_update(priv->ld_update, C_M1);
+
+       update = lt_encode_startup_request(REQ_HOLD);
+       /* If previous step moved C- go back on C- */
+       if (prev_req_cm1 == REQ_INC)
+               update = lt_encode_request(update, REQ_DEC, C_M1);
+       if (prev_req_cm1 == REQ_DEC)
+               update = lt_encode_request(update, REQ_INC, C_M1);
+
+       return update;
+}
+
+static u32 process_bin_m1_early(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum coef_status lpst_cm1;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       /* Get LP coefficient status to determine
+        * if coefficient is in range or reached the limit thresholds
+        * IF the coefficient is at MIN/MAX and still want to INC/DEC
+        * THEN do we are done with this module
+        */
+       lpst_cm1 = lt_get_lp_coef_status(priv->eqdrv.lane, C_M1);
+
+       /* Early path */
+       if (lpst_cm1 == COEF_MAX) {
+               /* Hold C- */
+               update = lt_encode_startup_request(REQ_HOLD);
+       } else {
+               /* request Increment C- */
+               update = lt_encode_request(update, REQ_INC, C_M1);
+       }
+
+       return update;
+}
+
+static u32 process_bin_m1_late(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum coef_status lpst_cm1;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       /* Get LP coefficient status to determine
+        * if coefficient is in range or reached the limit thresholds
+        * IF the coefficient is at MIN/MAX and still want to INC/DEC
+        * THEN do we are done with this module
+        */
+       lpst_cm1 = lt_get_lp_coef_status(priv->eqdrv.lane, C_M1);
+
+       /* Late path */
+       if (lpst_cm1 == COEF_MIN) {
+               /* Hold C- */
+               update = lt_encode_startup_request(REQ_HOLD);
+       } else {
+               /* request Decrement C- */
+               update = lt_encode_request(update, REQ_DEC, C_M1);
+       }
+
+       return update;
+}
+
+static u32 process_bin_m1_antipodal(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       if (priv->bin_m1_state == BIN_LATE) {
+               /* request Decrement C- */
+               update = lt_encode_request(update, REQ_DEC, C_M1);
+       } else {
+               /* Hold C- */
+               update = lt_encode_startup_request(REQ_HOLD);
+       }
+
+       return update;
+}
+
+/* process_bin_m1
+ *
+ * Bin_M1:
+ *   contains the scoring of initial edges on pulses that are 1UI long
+ *      following non-single bits
+ *   used to adjust LP coefficient: C_M1
+ */
+static void process_bin_m1(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return;
+       }
+
+       if (priv->bin_m1_state == BIN_INVALID) {
+               phydev_err(priv->eqdrv.phydev, "Invalid Bin_M1 state\n");
+               return;
+       }
+
+       if (priv->bin_m1_state == BIN_TOGGLE) {
+               update = process_bin_m1_toggle(priv);
+       } else {
+               if (priv->prev_bin_m1_state == BIN_TOGGLE) {
+                       update = process_bin_m1_prev_toggle(priv);
+               } else {
+                       if (priv->prev_bin_m1_state == priv->bin_m1_state) {
+                               if (priv->bin_m1_state == BIN_LATE)
+                                       update = process_bin_m1_late(priv);
+                               else
+                                       update = process_bin_m1_early(priv);
+                       } else {
+                               update = process_bin_m1_antipodal(priv);
+                       }
+               }
+       }
+
+       /* Store current algorithm decision
+        * as previous algorithm ld_update for next step
+        */
+       priv->ld_update = update;
+}
+
+static u32 process_bin_long_toggle(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum req_type prev_req_cp1, prev_req_cz0;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       prev_req_cp1 = lt_decode_coef_update(priv->ld_update, C_P1);
+       prev_req_cz0 = lt_decode_coef_update(priv->ld_update, C_Z0);
+
+       /* Toggle path */
+       if (priv->prev_bin_long_state == priv->bin_long_state) {
+               /* Hold C+ and C0 */
+               update = lt_encode_startup_request(REQ_HOLD);
+       } else {
+               update = lt_encode_startup_request(REQ_HOLD);
+               /* If previous step moved C+/C0 repeat C+/C0 move */
+               if (prev_req_cp1 == REQ_INC ||
+                   prev_req_cp1 == REQ_DEC ||
+                   prev_req_cz0 == REQ_INC ||
+                   prev_req_cz0 == REQ_DEC) {
+                       update = lt_encode_request(update, prev_req_cp1, C_P1);
+                       update = lt_encode_request(update, prev_req_cz0, C_Z0);
+               }
+       }
+
+       return update;
+}
+
+static u32 process_bin_long_prev_toggle(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum req_type prev_req_cp1, prev_req_cz0;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       prev_req_cp1 = lt_decode_coef_update(priv->ld_update, C_P1);
+       prev_req_cz0 = lt_decode_coef_update(priv->ld_update, C_Z0);
+
+       /* If previous step moved C+/C0 then go back on C+/C0 */
+       if (prev_req_cp1 == REQ_INC)
+               update = lt_encode_request(update, REQ_DEC, C_P1);
+       if (prev_req_cp1 == REQ_DEC)
+               update = lt_encode_request(update, REQ_INC, C_P1);
+       if (prev_req_cz0 == REQ_INC)
+               update = lt_encode_request(update, REQ_DEC, C_Z0);
+       if (prev_req_cz0 == REQ_DEC)
+               update = lt_encode_request(update, REQ_INC, C_Z0);
+
+       return update;
+}
+
+static u32 process_bin_long_early(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum coef_status lpst_cp1, lpst_cz0;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       /* Get LP coefficient status to determine
+        * if coefficient is in range or reached the limit thresholds
+        * IF the coefficient is at MIN/MAX and still want to INC/DEC
+        * THEN do we are done with this module
+        */
+       lpst_cp1 = lt_get_lp_coef_status(priv->eqdrv.lane, C_P1);
+       lpst_cz0 = lt_get_lp_coef_status(priv->eqdrv.lane, C_Z0);
+
+       /* Early path (make edge later) */
+       if (lpst_cp1 == COEF_MAX) {
+               if (lpst_cz0 == COEF_MAX) {
+                       /* Hold C+, C0 */
+                       update = lt_encode_startup_request(REQ_HOLD);
+               } else {
+                       /* request Increment C0 and
+                        * Decrement C+
+                        */
+                       update = lt_encode_request(update, REQ_INC, C_Z0);
+                       update = lt_encode_request(update, REQ_DEC, C_P1);
+               }
+       } else {
+               /* request Increment C+ */
+               update = lt_encode_request(update, REQ_INC, C_P1);
+       }
+
+       return update;
+}
+
+static u32 process_bin_long_late(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum coef_status lpst_cp1, lpst_cz0;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       /* Get LP coefficient status to determine
+        * if coefficient is in range or reached the limit thresholds
+        * IF the coefficient is at MIN/MAX and still want to INC/DEC
+        * THEN do we are done with this module
+        */
+       lpst_cp1 = lt_get_lp_coef_status(priv->eqdrv.lane, C_P1);
+       lpst_cz0 = lt_get_lp_coef_status(priv->eqdrv.lane, C_Z0);
+
+       /* Late path (make edge earlier) */
+       if (lpst_cp1 == COEF_MIN) {
+               if (lpst_cz0 == COEF_MIN) {
+                       /* Hold C0 */
+                       update = lt_encode_startup_request(REQ_HOLD);
+               } else {
+                       /* request Decrement C0 */
+                       update = lt_encode_request(update, REQ_DEC, C_Z0);
+               }
+       } else {
+               /* request Decrement C+ */
+               update = lt_encode_request(update, REQ_DEC, C_P1);
+       }
+
+       return update;
+}
+
+static u32 process_bin_long_antipodal(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+       enum req_type prev_req_cp1;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return update;
+       }
+
+       prev_req_cp1 = lt_decode_coef_update(priv->ld_update, C_P1);
+
+       /* Request move on C+ and C0 */
+       /* If previous step moved C+ then go back on C+ */
+       if (prev_req_cp1 == REQ_INC)
+               update = lt_encode_request(update, REQ_DEC, C_P1);
+       if (prev_req_cp1 == REQ_DEC)
+               update = lt_encode_request(update, REQ_INC, C_P1);
+
+       if (priv->bin_long_state == BIN_LATE) {
+               /* request Decrement C0 */
+               update = lt_encode_request(update, REQ_DEC, C_Z0);
+       } else {
+               /* request Increment C0 */
+               update = lt_encode_request(update, REQ_INC, C_Z0);
+       }
+
+       return update;
+}
+
+/* process_bin_long
+ *
+ * Bin_Long:
+ *   contains the scoring of final edges on pulses longer than 7UI long
+ *   used to adjust LP coefficients: C_P1 and C_Z0
+ */
+static void process_bin_long(struct eq_data_priv *priv)
+{
+       u32 update = lt_encode_startup_request(REQ_HOLD);
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return;
+       }
+
+       if (priv->bin_long_state == BIN_INVALID) {
+               phydev_err(priv->eqdrv.phydev, "Invalid Bin_Long state\n");
+               return;
+       }
+
+       if (priv->bin_long_state == BIN_TOGGLE) {
+               update = process_bin_long_toggle(priv);
+       } else {
+               if (priv->prev_bin_long_state == BIN_TOGGLE) {
+                       update = process_bin_long_prev_toggle(priv);
+               } else {
+                       if (priv->prev_bin_long_state == priv->bin_long_state) {
+                               if (priv->bin_long_state == BIN_LATE)
+                                       update = process_bin_long_late(priv);
+                               else
+                                       update = process_bin_long_early(priv);
+                       } else {
+                               update = process_bin_long_antipodal(priv);
+                       }
+               }
+       }
+
+       /* Store current algorithm decision
+        * as previous algorithm ld_update for next step
+        */
+       priv->ld_update = update;
+}
+
+/* Callbacks:
+ * required by generic equalization_algorithm
+ */
+static void process_bad_state(struct eq_data_priv *priv)
+{
+       bool lp_at_init, lp_at_preset;
+       u32 req;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return;
+       }
+
+       lp_at_init = lt_is_lp_at_startup(priv->eqdrv.lane, REQ_INIT);
+       lp_at_preset = lt_is_lp_at_startup(priv->eqdrv.lane, REQ_PRESET);
+
+       if (lp_at_init) {
+               /* Try Request Preset */
+               req = lt_encode_startup_request(REQ_PRESET);
+               lt_lp_update(priv->eqdrv.lane, req);
+       } else if (lp_at_preset) {
+               /* LT ERROR
+                * set lt_error flag to prevent reaching
+                * training state = TRAINED
+                * and resume training in case of LT error
+                */
+               lt_set_error(priv->eqdrv.lane, true);
+               phydev_err(priv->eqdrv.phydev,
+                          "LT Error: CDR_LOCK is zero on Preset\n");
+       } else {
+               /* Move LP back to previous C-, C0, C+ and HOLD */
+               lt_move_lp_back(priv->eqdrv.lane);
+       }
+}
+
+static bool collect_bin_counters(struct eq_data_priv *priv,
+                                enum eqc_type type)
+{
+       const struct equalizer_ops *eqops;
+       s16 *bin_snapshot = NULL;
+       int snp_size;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return false;
+       }
+
+       /* collect Bin snapshots */
+       switch (type) {
+       case EQC_BIN_1:
+               bin_snapshot = priv->bin1_snapshot;
+               break;
+       case EQC_BIN_2:
+               bin_snapshot = priv->bin2_snapshot;
+               break;
+       case EQC_BIN_3:
+               bin_snapshot = priv->bin3_snapshot;
+               break;
+       case EQC_BIN_LONG:
+               bin_snapshot = priv->bin_long_snapshot;
+               break;
+       case EQC_BIN_OFFSET:
+               bin_snapshot = priv->bin_offset_snapshot;
+               break;
+       case EQC_BIN_M1:
+               bin_snapshot = priv->bin_m1_snapshot;
+               break;
+       default:
+               /* invalid bin type */
+               return false;
+       }
+       if (!bin_snapshot)
+               return false;
+
+       eqops = &priv->eqdrv.equalizer->ops;
+       if (!eqops) {
+               phydev_err(priv->eqdrv.phydev,
+                          "No operations for equalizer %s %s\n",
+                          priv->eqdrv.equalizer->name,
+                          priv->eqdrv.equalizer->version);
+               return false;
+       }
+       snp_size = eqops->collect_counters(priv->eqdrv.reg_base, type,
+                                          bin_snapshot, EQ_SNAPSHOTS_SIZE);
+       /* Check if snapshots collection failed */
+       if (snp_size < EQ_SNAPSHOTS_SIZE) {
+               phydev_err(priv->eqdrv.phydev,
+                          "Counters collection failed for equalizer %s %s\n",
+                          priv->eqdrv.equalizer->name,
+                          priv->eqdrv.equalizer->version);
+               return false;
+       }
+
+       /* if CDR_LOCK = 0: Statistics are invalid */
+       if (!backplane_is_cdr_lock(priv->eqdrv.lane, true)) {
+               process_bad_state(priv);
+               return false;
+       }
+
+       return true;
+}
+
+static bool collect_bit_edge_statistics(struct eq_data_priv *priv)
+{
+       enum eqc_type st_types[] = { EQC_GAIN_HF, EQC_GAIN_MF, EQC_EQOFFSET };
+       s16 status_counters[ARRAY_SIZE(st_types)][EQ_SNAPSHOTS_SIZE];
+       const struct equalizer_ops *eqops;
+       int i, snp_size;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return false;
+       }
+
+       /* collect Bin snapshots */
+       if (!collect_bin_counters(priv, EQC_BIN_1))
+               return false;
+       if (!collect_bin_counters(priv, EQC_BIN_2))
+               return false;
+       if (!collect_bin_counters(priv, EQC_BIN_3))
+               return false;
+       if (!collect_bin_counters(priv, EQC_BIN_LONG))
+               return false;
+       if (!collect_bin_counters(priv, EQC_BIN_OFFSET))
+               return false;
+       if (!collect_bin_counters(priv, EQC_BIN_M1))
+               return false;
+
+       /* collect Gains */
+       eqops = &priv->eqdrv.equalizer->ops;
+       if (!eqops) {
+               phydev_err(priv->eqdrv.phydev,
+                          "No operations for equalizer %s %s\n",
+                          priv->eqdrv.equalizer->name,
+                          priv->eqdrv.equalizer->version);
+               return false;
+       }
+       snp_size = eqops->collect_multiple_counters(priv->eqdrv.reg_base,
+                                                   st_types,
+                                                   ARRAY_SIZE(st_types),
+                                                   (s16 *)status_counters,
+                                                   EQ_SNAPSHOTS_SIZE);
+       /* Check if snapshots collection failed */
+       if (snp_size < EQ_SNAPSHOTS_SIZE) {
+               phydev_err(priv->eqdrv.phydev,
+                          "Counters collection failed for equalizer %s %s\n",
+                          priv->eqdrv.equalizer->name,
+                          priv->eqdrv.equalizer->version);
+               return false;
+       }
+
+       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+               priv->gain_hf_snapshot[i] = (u8)status_counters[0][i];
+               priv->gain_mf_snapshot[i] = (u8)status_counters[1][i];
+               priv->osestat_snapshot[i] = (u8)status_counters[2][i];
+       }
+
+       return true;
+}
+
+static void generate_3taps_request(struct eq_data_priv *priv)
+{
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return;
+       }
+
+       /* Store current state as previous state */
+       priv->prev_bin_m1_state = priv->bin_m1_state;
+       priv->prev_bin_long_state = priv->bin_long_state;
+
+       priv->bin_m1_state = get_bin_snapshots_state(priv, EQC_BIN_M1);
+       if (priv->bin_m1_state == BIN_INVALID) {
+               /* invalid state: should never happen */
+               return;
+       }
+
+       priv->bin_long_state = get_bin_snapshots_state(priv, EQC_BIN_LONG);
+       if (priv->bin_long_state == BIN_INVALID) {
+               /* invalid state: should never happen */
+               return;
+       }
+
+       /* Move to BinLong/BinM1 modules:
+        * Bin Modules order: BinLong before BinM1
+        * We try to finish BinLong before we do BinM1
+        */
+
+       /* Process BinLong module
+        * decide and ask for movement of C+/C0
+        */
+       if (!priv->bin_long_stop) {
+               process_bin_long(priv);
+               lt_lp_update(priv->eqdrv.lane, priv->ld_update);
+               if (lt_is_update_of_type(priv->ld_update, REQ_HOLD)) {
+                       /* Sent All Hold request */
+                       priv->long_min_max_cnt++;
+                       if (priv->long_min_max_cnt >= TIMEOUT_LONG)
+                               priv->bin_long_stop = true;
+               } else {
+                       /* Sent C Inc/Dec request */
+                       priv->long_min_max_cnt = 0;
+               }
+               return;
+       }
+
+       /* Process BinM1 module
+        * decide and ask for movement of C-
+        */
+       if (!priv->bin_m1_stop) {
+               process_bin_m1(priv);
+               lt_lp_update(priv->eqdrv.lane, priv->ld_update);
+               if (lt_is_update_of_type(priv->ld_update, REQ_HOLD)) {
+                       /* Sent All Hold request */
+                       priv->m1_min_max_cnt++;
+                       if (priv->m1_min_max_cnt >= TIMEOUT_M1)
+                               priv->bin_m1_stop = true;
+               } else {
+                       /* Sent C Inc/Dec request */
+                       priv->m1_min_max_cnt = 0;
+               }
+               return;
+       }
+}
+
+static bool is_rx_ok(struct eq_data_priv *priv)
+{
+       struct eqc_range *gain_range, *osestat_range;
+       u8 osestat_mid_low, osestat_mid_high;
+       enum bin_state bin1_snapshot_state;
+       enum bin_state bin2_snapshot_state;
+       enum bin_state bin3_snapshot_state;
+       const struct equalizer_ops *eqops;
+       bool rx_quality_1, rx_quality_2;
+       bool is_ok, rx_good_quality;
+       u8 min_eq_gain, max_eq_gain;
+       u8 min_snp, max_snp;
+       s16 snapshot;
+       int i;
+
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return false;
+       }
+
+       /* Checking Bins/Gains after LP has updated its TX */
+       eqops = &priv->eqdrv.equalizer->ops;
+       if (!eqops)
+               return false;
+       gain_range = eqops->get_counter_range(EQC_GAIN_HF);
+       if (!gain_range)
+               return false;
+       osestat_range = eqops->get_counter_range(EQC_EQOFFSET);
+       if (!osestat_range)
+               return false;
+
+       min_eq_gain = (u8)gain_range->min;
+       max_eq_gain = (u8)gain_range->max;
+       osestat_mid_low = (u8)osestat_range->mid_low;
+       osestat_mid_high = (u8)osestat_range->mid_high;
+
+       /* CDR_LOCK must be 1 */
+       if (!backplane_is_cdr_lock(priv->eqdrv.lane, true))
+               return false;
+
+       /* Offset Bin must NOT be 10 of the same value */
+       rx_good_quality = false;
+       snapshot = priv->bin_offset_snapshot[0];
+       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+               if (snapshot != priv->bin_offset_snapshot[i]) {
+                       rx_good_quality = true;
+                       break;
+               }
+       }
+       if (!rx_good_quality)
+               return false;
+
+       /* Offset status must dither (+/-2) around MidRange value
+        * What we want to see is that the Offset has settled to a value
+        * somewhere between: mid-range low and mid-range high and that
+        * the series of snapshot values are +/-2 of the settled value.
+        */
+       rx_good_quality = true;
+       min_snp = priv->osestat_snapshot[0];
+       max_snp = priv->osestat_snapshot[0];
+       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+               if (priv->osestat_snapshot[i] < osestat_mid_low ||
+                   priv->osestat_snapshot[i] > osestat_mid_high) {
+                       rx_good_quality = false;
+                       break;
+               }
+               if (priv->osestat_snapshot[i] < min_snp)
+                       min_snp = priv->osestat_snapshot[i];
+               if (priv->osestat_snapshot[i] > max_snp)
+                       max_snp = priv->osestat_snapshot[i];
+       }
+       if (max_snp - min_snp > 4)
+               rx_good_quality = false;
+       if (!rx_good_quality)
+               return false;
+
+       /* The Rx is in good quality if:
+        * Bin1, Bin2, and Bin3 are toggling
+        * Proceed to BinLong/BinM1 modules
+        */
+       bin1_snapshot_state = get_bin_snapshots_state(priv, EQC_BIN_1);
+       bin2_snapshot_state = get_bin_snapshots_state(priv, EQC_BIN_2);
+       bin3_snapshot_state = get_bin_snapshots_state(priv, EQC_BIN_3);
+
+       rx_good_quality = (bin1_snapshot_state == BIN_TOGGLE &&
+                          bin2_snapshot_state == BIN_TOGGLE &&
+                          bin3_snapshot_state == BIN_TOGGLE);
+
+       /* If Rx is in good quality then proceed to BinLong/BinM1 */
+       if (rx_good_quality)
+               return true;
+
+#ifdef ENABLE_LESS_QUALITY_CONDITION
+       rx_quality_1 = false;
+       rx_quality_2 = false;
+       if (bin1_snapshot_state == BIN_TOGGLE) {
+               if (bin2_snapshot_state == BIN_EARLY &&
+                   bin3_snapshot_state == BIN_LATE) {
+                       /* check if GainMF is stuck at max_eq_gain */
+                       rx_quality_1 = true;
+                       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                               if (priv->gain_mf_snapshot[i] != max_eq_gain) {
+                                       rx_quality_1 = false;
+                                       break;
+                               }
+                       }
+               }
+               if (bin2_snapshot_state == BIN_LATE &&
+                   bin3_snapshot_state == BIN_EARLY) {
+                       /* check if GainMF is stuck at min_eq_gain */
+                       rx_quality_2 = true;
+                       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                               if (priv->gain_mf_snapshot[i] != min_eq_gain) {
+                                       rx_quality_2 = false;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       /* If Rx is less quality then proceed to BinLong/BinM1 */
+       if (rx_quality_1 || rx_quality_2)
+               return true;
+#endif
+
+#ifdef ENABLE_EVEN_LESS_QUALITY_CONDITION
+       rx_quality_1 = false;
+       rx_quality_2 = false;
+       if (bin1_snapshot_state == BIN_EARLY &&
+           bin2_snapshot_state == BIN_LATE) {
+               /* check if GainHF is stuck at max_eq_gain */
+               is_ok = true;
+               for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                       if (priv->gain_hf_snapshot[i] != max_eq_gain) {
+                               is_ok = false;
+                               break;
+                       }
+               }
+               if (is_ok) {
+                       /* check if GainMF is stuck at min_eq_gain */
+                       is_ok = true;
+                       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                               if (priv->gain_mf_snapshot[i] != min_eq_gain) {
+                                       is_ok = false;
+                                       break;
+                               }
+                       }
+                       if (is_ok)
+                               rx_quality_1 = true;
+               }
+       }
+       if (bin1_snapshot_state == BIN_LATE &&
+           bin2_snapshot_state == BIN_EARLY) {
+               /* check if GainHF is stuck at min_eq_gain */
+               is_ok = true;
+               for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                       if (priv->gain_hf_snapshot[i] != min_eq_gain) {
+                               is_ok = false;
+                               break;
+                       }
+               }
+               if (is_ok) {
+                       /* check if GainMF is stuck at max_eq_gain */
+                       is_ok = true;
+                       for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                               if (priv->gain_mf_snapshot[i] != max_eq_gain) {
+                                       is_ok = false;
+                                       break;
+                               }
+                       }
+                       if (is_ok)
+                               rx_quality_2 = true;
+               }
+       }
+
+       /* If Rx is in good quality then proceed to BinLong/BinM1 */
+       if (rx_quality_1 || rx_quality_2)
+               return true;
+#endif
+
+#ifdef ENABLE_SEEMINGLY_QUALITY_CONDITION
+       rx_quality_1 = false;
+       if (bin1_snapshot_state == BIN_LATE &&
+           bin2_snapshot_state == BIN_TOGGLE &&
+           bin3_snapshot_state == BIN_TOGGLE) {
+               /* check if GainHF is stuck at min_eq_gain */
+               rx_quality_1 = true;
+               for (i = 0; i < EQ_SNAPSHOTS_SIZE; i++) {
+                       if (priv->gain_hf_snapshot[i] != min_eq_gain) {
+                               rx_quality_1 = false;
+                               break;
+                       }
+               }
+       }
+
+       if (rx_quality_1)
+               return true;
+#endif
+
+       return false;
+}
+
+static bool is_eq_done(struct eq_data_priv *priv)
+{
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return false;
+       }
+
+       return (priv->bin_m1_stop && priv->bin_long_stop);
+}
+
+/* BEE 3 TAP Algorithm API */
+
+/* Create BEE 3-TAP Equalization Algorithm */
+static struct eq_data_priv *create(struct equalizer_driver eqdrv)
+{
+       struct eq_data_priv *bee_data;
+
+       bee_data = devm_kzalloc(&eqdrv.phydev->mdio.dev,
+                               sizeof(*bee_data), GFP_KERNEL);
+       if (!bee_data)
+               return NULL;
+
+       /* initialize algorithm setup data */
+       bee_data->eqdrv = eqdrv;
+
+       /* initialize specific BEE algorithm data */
+       bee_data->bin_m1_state = BIN_INVALID;
+       bee_data->bin_long_state = BIN_INVALID;
+       bee_data->prev_bin_m1_state = BIN_INVALID;
+       bee_data->prev_bin_long_state = BIN_INVALID;
+       bee_data->m1_min_max_cnt = 0;
+       bee_data->long_min_max_cnt = 0;
+       bee_data->bin_m1_stop = false;
+       bee_data->bin_long_stop = false;
+       bee_data->ld_update = 0;
+
+       return bee_data;
+}
+
+static void destroy(struct eq_data_priv *priv)
+{
+       if (!priv) {
+               pr_err("%s: NULL private data\n", ALGORITHM_NAME);
+               return;
+       }
+
+       kfree(priv);
+}
+
+static const struct equalization_algorithm eq_alg = {
+       .name = ALGORITHM_NAME,
+       .descr = ALGORITHM_DESCR,
+       .version = ALGORITHM_VERSION,
+       .use_local_tx_training = true,
+       .use_remote_tx_training = true,
+       .ops = {
+               .create = create,
+               .destroy = destroy,
+               .is_rx_ok = is_rx_ok,
+               .is_eq_done = is_eq_done,
+               .collect_statistics = collect_bit_edge_statistics,
+               .generate_request = generate_3taps_request,
+               .process_bad_state = process_bad_state,
+               .dump_algorithm_context = NULL,
+       }
+};
+
+static const char * const alg_keys[] = {
+       "bee",
+       "BEE",
+};
+
+static int __init bee_init(void)
+{
+       int i, err;
+
+       pr_info("%s: %s algorithm version %s\n",
+               ALGORITHM_NAME, ALGORITHM_DESCR, ALGORITHM_VERSION);
+
+       /* register BEE algorithm: */
+       for (i = 0; i < ARRAY_SIZE(alg_keys); i++) {
+               err = backplane_eq_register(alg_keys[i], &eq_alg);
+               if (err) {
+                       pr_err("%s: '%s' equalization algorithm registration 
failed\n",
+                              ALGORITHM_NAME, alg_keys[i]);
+               }
+       }
+
+       return 0;
+}
+
+static void __exit bee_exit(void)
+{
+       int i;
+
+       /* unregister BEE algorithm: */
+       for (i = 0; i < ARRAY_SIZE(alg_keys); i++)
+               backplane_eq_unregister(alg_keys[i]);
+
+       pr_info("%s: %s algorithm version %s unloaded\n",
+               ALGORITHM_NAME, ALGORITHM_DESCR, ALGORITHM_VERSION);
+}
+
+module_init(bee_init);
+module_exit(bee_exit);
+
+MODULE_DESCRIPTION("Bit Edge Equalization Algorithm");
+MODULE_AUTHOR("Florinel Iordache <florinel.iorda...@nxp.com>");
+MODULE_LICENSE("Dual BSD/GPL");
-- 
1.9.1

Reply via email to