new file mode 100644
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2021, Celeno Communications Ltd. */
+
+#include "reg/reg_access.h"
+#include "reg/reg_defs.h"
+#include "utils.h"
+#include "ela.h"
+
+#define CL_ELA_MODE_DFLT_ALIAS "default"
+#define CL_ELA_MODE_DFLT_SYMB_LINK "lcu_default.conf"
+#define CL_ELA_MODE_DFLT_OFF "OFF"
+#define CL_ELA_LCU_CONF_TOKENS_CNT 3 /* cmd addr1 addr2 */
+#define CL_ELA_LCU_MEM_WRITE_CMD_STR "mem_write"
+#define CL_ELA_LCU_MEM_WRITE_CMD_SZ sizeof(CL_ELA_LCU_MEM_WRITE_CMD_STR)
+#define CL_ELA_LCU_UNKNOWN_CMD_TYPE 0
+#define CL_ELA_LCU_MEM_WRITE_CMD_TYPE 1
+#define CL_ELA_LCU_UNKNOWN_CMD_STR "unknown"
+
+static int __must_check get_lcu_cmd_type(char *cmd)
+{
+ if (!strncmp(CL_ELA_LCU_MEM_WRITE_CMD_STR, cmd, CL_ELA_LCU_MEM_WRITE_CMD_SZ))
+ return CL_ELA_LCU_MEM_WRITE_CMD_TYPE;
+
+ return CL_ELA_LCU_UNKNOWN_CMD_TYPE;
+}
+
+static int add_lcu_cmd(struct cl_ela_db *ed, u32 type, u32 offset, u32 value)
+{
+ struct cl_lcu_cmd *cmd = NULL;
+
+ cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ cmd->type = type;
+ cmd->offset = offset;
+ cmd->value = value;
+
+ list_add_tail(&cmd->cmd_list, &ed->cmd_head);
+
+ return 0;
+}
+
+static void remove_lcu_cmd(struct cl_lcu_cmd *cmd)
+{
+ list_del(&cmd->cmd_list);
+ kfree(cmd);
+}
+
+static void reset_stats(struct cl_ela_db *db)
+{
+ memset(&db->stats, 0, sizeof(db->stats));
+}
+
+static int load_cmds_from_buf(struct cl_chip *chip, char *buf, size_t size)
+{
+ struct cl_ela_db *ed = &chip->ela_db;
+ char *line = buf;
+ char cmd[STR_LEN_256B];
+ u32 type = CL_ELA_LCU_UNKNOWN_CMD_TYPE;
+ u32 offset = 0;
+ u32 value = 0;
+ int tokens_cnt = 0;
+ int ret = 0;
+
+ while (line && strlen(line) && (line != (buf + size))) {
+ if ((*line == '#') || (*line == '\n')) {
+ /* Skip comment or blank line */
+ line = strstr(line, "\n") + 1;
+ } else if (*line) {
+ tokens_cnt = sscanf(line, "%s %x %x\n", cmd, &offset, &value);
+ cl_dbg_chip_trace(chip,
+ "tokens(%d):cmd(%s), offset(0x%X), value(0x%X)\n",
+ tokens_cnt, cmd, offset, value);
+
+ type = get_lcu_cmd_type(cmd);
+ if (type == CL_ELA_LCU_UNKNOWN_CMD_TYPE) {
+ cl_dbg_chip_trace(chip, "Detected extra token, skipping\n");
+ goto newline;
+ }
+ if (tokens_cnt != CL_ELA_LCU_CONF_TOKENS_CNT) {
+ cl_dbg_chip_err(chip,
+ "Tokens count is wrong! (%d != %d)\n",
+ CL_ELA_LCU_CONF_TOKENS_CNT,
+ tokens_cnt);
+ ret = -EBADMSG;
+ goto exit;
+ }
+
+ ret = add_lcu_cmd(ed, type, offset, value);
+ if (ret)
+ goto exit;
+
+newline:
+ line = strstr(line, "\n") + 1;
+ }
+ }
+
+exit:
+ ed->stats.adaptations_cnt++;
+ return ret;
+}
+
+void cl_ela_lcu_reset(struct cl_chip *chip)
+{
+ lcu_common_sw_rst_set(chip, 0x1);
+
+ if (chip->cl_hw_tcv0)
+ lcu_phy_lcu_sw_rst_set(chip->cl_hw_tcv0, 0x1);
+
+ if (chip->cl_hw_tcv1)
+ lcu_phy_lcu_sw_rst_set(chip->cl_hw_tcv1, 0x1);
+}
+
+void cl_ela_lcu_apply_config(struct cl_chip *chip)
+{
+ struct cl_ela_db *ed = &chip->ela_db;
+ struct cl_lcu_cmd *cmd = NULL;
+ unsigned long flags;
+
+ if (!cl_ela_lcu_is_valid_config(chip)) {
+ cl_dbg_chip_err(chip, "Active ELA LCU config is not valid\n");
+ return;
+ }
+
+ /* Extra safety to avoid local CPU interference during LCU reconfiguration */
+ local_irq_save(flags);
+ list_for_each_entry(cmd, &ed->cmd_head, cmd_list) {
+ cl_dbg_chip_info(chip, "Writing cmd (0x%X, 0x%X)\n",
+ cmd->offset, cmd->value);
+ if (!chip->cl_hw_tcv1 && cl_reg_is_phy_tcv1(cmd->offset)) {
+ ed->stats.tcv1_skips_cnt++;
+ continue;
+ } else if (!chip->cl_hw_tcv0 && cl_reg_is_phy_tcv0(cmd->offset)) {
+ ed->stats.tcv0_skips_cnt++;
+ continue;
+ }
+ cl_reg_write_chip(chip, cmd->offset, cmd->value);
+ }
+ local_irq_restore(flags);
+ ed->stats.applications_cnt++;
+}
+
+bool cl_ela_is_on(struct cl_chip *chip)
+{
+ return !!strncmp(CL_ELA_MODE_DFLT_OFF, chip->conf->ce_ela_mode,
+ sizeof(chip->conf->ce_ela_mode));
+}
+
+bool cl_ela_is_default(struct cl_chip *chip)
+{
+ return !strncmp(CL_ELA_MODE_DFLT_ALIAS, chip->conf->ce_ela_mode,
+ sizeof(chip->conf->ce_ela_mode));
+}
+
+bool cl_ela_lcu_is_valid_config(struct cl_chip *chip)
+{
+ struct cl_ela_db *ed = &chip->ela_db;
+
+ return ed->error_state == 0;
+}
+
+char *cl_ela_lcu_config_name(struct cl_chip *chip)
+{
+ if (!cl_ela_is_on(chip))
+ return CL_ELA_MODE_DFLT_OFF;
+
+ if (cl_ela_is_default(chip))
+ return CL_ELA_MODE_DFLT_SYMB_LINK;
+
+ return chip->conf->ce_ela_mode;
+}
+
+int cl_ela_lcu_config_read(struct cl_chip *chip)
+{
+ struct cl_ela_db *ed = &chip->ela_db;
+ char filename[CL_FILENAME_MAX] = {0};
+ size_t size = 0;
+ int ret = 0;
+
+ if (!cl_ela_is_on(chip)) {
+ ret = -EOPNOTSUPP;
+ goto exit;
+ }
+
+ reset_stats(ed);
+
+ snprintf(filename, sizeof(filename), "%s", cl_ela_lcu_config_name(chip));
+ size = cl_file_open_and_read(chip, filename, &ed->raw_lcu_config);
+ if (!ed->raw_lcu_config) {
+ ret = -ENODATA;
+ goto exit;
+ }
+
+ ret = load_cmds_from_buf(chip, ed->raw_lcu_config, size);
+exit:
+ ed->error_state = ret;
+ return ret;
+}
+
+int cl_ela_init(struct cl_chip *chip)
+{
+ struct cl_ela_db *ed = &chip->ela_db;
+ int ret = 0;
+
+ INIT_LIST_HEAD(&ed->cmd_head);
+
+ if (!cl_ela_is_on(chip))
+ return 0;
+
+ ret = cl_ela_lcu_config_read(chip);
+ if (ret == 0) {
+ cl_ela_lcu_reset(chip);
+ cl_ela_lcu_apply_config(chip);
+ }
+
+ return ret;
+}
+
+void cl_ela_deinit(struct cl_chip *chip)
+{
+ struct cl_ela_db *ed = &chip->ela_db;
+ struct cl_lcu_cmd *cmd = NULL, *cmd_tmp = NULL;
+
+ kfree(ed->raw_lcu_config);
+ ed->raw_lcu_config = NULL;
+
+ list_for_each_entry_safe(cmd, cmd_tmp, &ed->cmd_head, cmd_list)
+ remove_lcu_cmd(cmd);
+}