new file mode 100644
@@ -0,0 +1,611 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "reg/reg_modem_gcu.h"
+#include "reg/reg_macdsp_api.h"
+#include "reg/reg_cmu.h"
+#include "reg/reg_access.h"
+#include "reg/ceva.h"
+#include "dsp.h"
+#include "hw.h"
+#include <linux/firmware.h>
+
+#define BUSY_WAIT_LIMIT 10000
+
+static int dsp_busy_wait(struct cl_hw *cl_hw, u32 control_reg)
+{
+ int i;
+
+ for (i = 0; i < BUSY_WAIT_LIMIT; i++) {
+ /* Poll Bit29 to verify DMA transfer has ended. */
+ if ((cl_reg_read(cl_hw, control_reg) & 0x20000000) == 0)
+ return 0;
+
+ cpu_relax();
+ }
+
+ return -EIO;
+}
+
+static void cl_dsp_boot(struct cl_hw *cl_hw)
+{
+ struct cl_chip *chip = cl_hw->chip;
+
+ if (cl_hw_is_tcv0(cl_hw)) {
+ /* Disable ceva_free_clk */
+ cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 0);
+ /* Assert Ceva reset */
+ cmu_phy_0_rst_ceva_0_global_rst_n_setf(chip, 0);
+ } else {
+ /* Disable ceva_free_clk */
+ cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 0);
+ /* Assert Ceva reset */
+ cmu_phy_1_rst_ceva_1_global_rst_n_setf(chip, 0);
+ }
+
+ /* Set Ceva boot=1 */
+ modem_gcu_ceva_ctrl_boot_setf(cl_hw, 1);
+ /* Set Ceva vector */
+ modem_gcu_ceva_vec_set(cl_hw, 0);
+
+ if (cl_hw_is_tcv0(cl_hw)) {
+ /* Enable ceva_clk */
+ cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 1);
+ /* Disabel ceva_clk */
+ cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 0);
+ /* De-Assert Ceva reset - Reset Release */
+ cmu_phy_0_rst_ceva_0_global_rst_n_setf(chip, 1);
+ /* Enable ceva_clk */
+ cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 1);
+ } else {
+ /* Enable ceva_clk */
+ cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 1);
+ /* Disabel ceva_clk */
+ cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 0);
+ /* De-Assert Ceva reset - Reset Release */
+ cmu_phy_1_rst_ceva_1_global_rst_n_setf(chip, 1);
+ /* Enable ceva_clk */
+ cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 1);
+ }
+
+ /* Release Ceva external_wait */
+ modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0);
+ /* Set Ceva boot=0 */
+ modem_gcu_ceva_ctrl_boot_setf(cl_hw, 0);
+}
+
+static void config_dma_for_code_copy(struct cl_hw *cl_hw, u32 page)
+{
+ /* Configure Program DMA to copy FW code from Shared PMEM to internal PMEM. */
+
+ /* External address to read from. */
+ cl_reg_write(cl_hw, CEVA_CPM_PDEA_REG, CEVA_SHARED_PMEM_BASE_ADDR_INTERNAL);
+ /* Internal address to write to. */
+ cl_reg_write(cl_hw, CEVA_CPM_PDIA_REG, CEVA_SHARED_PMEM_SIZE * page);
+ /* Page size */
+ cl_reg_write(cl_hw, CEVA_CPM_PDTC_REG, CEVA_SHARED_PMEM_SIZE);
+}
+
+static void config_dma_for_external_data_copy(struct cl_hw *cl_hw)
+{
+ /* Configure Program DMA to copy FW code from Shared XMEM to internal XMEM. */
+
+ /* External address to read from. */
+ cl_reg_write(cl_hw, CEVA_CPM_DDEA_REG, CEVA_SHARED_XMEM_BASE_ADDR_INTERNAL);
+ /* Internal address to write to. */
+ cl_reg_write(cl_hw, CEVA_CPM_DDIA_REG, 0);
+ /* Page size + DMA direction is write */
+ cl_reg_write(cl_hw, CEVA_CPM_DDTC_REG,
+ CEVA_SHARED_XMEM_SIZE | CEVA_CPM_DDTC_WRITE_COMMAND);
+}
+
+static int cl_dsp_hex_load(struct cl_hw *cl_hw, const u8 *buf,
+ off_t offset, size_t size, size_t buf_size)
+{
+ u8 single_buf[4] = {0};
+ u32 bin_data = 0;
+ u8 next_byte;
+ u8 byte_num = 0;
+ int ret = 0;
+ ssize_t oft = 0;
+ size_t real_size = min(size * 3, buf_size);
+ /*
+ * CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST is global and we don't
+ * want to add TCV reg offset.
+ */
+ bool chip_reg = (offset == CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST);
+
+ if (buf_size % 3) {
+ cl_dbg_err(cl_hw, "DSP size %zu must be divided by 3 !!!\n",
+ buf_size);
+ return -EINVAL;
+ }
+
+ while (oft < real_size) {
+ memcpy(single_buf, buf + oft, 3);
+ /* Each line contains 2 hex digits + a line feed, i.e. 3 bytes */
+ ret = kstrtou8(single_buf, 16, &next_byte);
+ if (ret < 0) {
+ cl_dbg_err(cl_hw,
+ "ret = %d, oft = %zu,"
+ "single_buf = 0x%x 0x%x 0x%x 0x%x\n",
+ ret, oft, single_buf[0], single_buf[1],
+ single_buf[2], single_buf[3]);
+ return ret;
+ }
+
+ /* Little-endian order. */
+ bin_data += next_byte << (8 * byte_num);
+ byte_num = (byte_num + 1) % 4;
+
+ /* Read 4 lines from the file, and then write. */
+ if (byte_num == 0) {
+ if (chip_reg)
+ cl_reg_write_chip(cl_hw->chip, offset, bin_data);
+ else
+ cl_reg_write_direct(cl_hw, offset, bin_data);
+ offset += 4;
+ bin_data = 0;
+ }
+
+ memset(&single_buf, 0, sizeof(single_buf));
+ oft += 3;
+ }
+
+ return 0;
+}
+
+static int load_dsp_code(struct cl_hw *cl_hw)
+{
+ struct cl_chip *chip = cl_hw->chip;
+ u32 real_size;
+ u32 page;
+ const struct firmware *fw;
+ size_t size = 0;
+ u8 *buf = NULL;
+ char path_name[CL_PATH_MAX] = {0};
+ int ret;
+
+ snprintf(path_name, sizeof(path_name), "cl8k/%s",
+ cl_hw->conf->ce_dsp_code);
+
+ cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_code);
+
+ ret = request_firmware(&fw, path_name, chip->dev);
+
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+ path_name, ret);
+ goto out;
+ }
+
+ size = fw->size;
+ buf = (u8 *)fw->data;
+
+ for (page = 0; page < CEVA_MAX_PAGES; page++) {
+ /* Copy DSP code (one page each time) */
+ ret = cl_dsp_hex_load(cl_hw, buf,
+ CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST,
+ CEVA_SHARED_PMEM_SIZE, size);
+ if (ret != 0) {
+ cl_dbg_err(cl_hw, "Failed to load pmem page 0x%x!\n", page);
+ break;
+ }
+
+ config_dma_for_code_copy(cl_hw, page);
+ ret = dsp_busy_wait(cl_hw, CEVA_CPM_PDTC_REG);
+
+ if (ret) {
+ cl_dbg_err(cl_hw, "dsp_busy_wait failed!\n");
+ goto out;
+ }
+
+ real_size = min_t(u32, CEVA_SHARED_PMEM_SIZE * 3, size);
+ buf += real_size;
+ size -= real_size;
+ }
+
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int load_dsp_data(struct cl_hw *cl_hw)
+{
+ struct cl_chip *chip = cl_hw->chip;
+ const struct firmware *fw;
+ size_t size = 0;
+ char path_name[CL_PATH_MAX] = {0};
+ int ret;
+
+ snprintf(path_name, sizeof(path_name), "cl8k/%s",
+ cl_hw->conf->ce_dsp_data);
+
+ cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_data);
+
+ ret = request_firmware(&fw, path_name, chip->dev);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+ path_name, ret);
+ goto out;
+ }
+
+ size = fw->size;
+
+ ret = cl_dsp_hex_load(cl_hw, fw->data, REG_MACDSP_API_BASE_ADDR,
+ CEVA_DSP_DATA_SIZE, size);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load HEX file\n");
+ goto out;
+ }
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int load_dsp_external_data(struct cl_hw *cl_hw)
+{
+ /*
+ * Shared XMEM is not accessible by host.
+ * Copy the XMEM section to DRAM first and then use CEVA internal DMA to copy to
+ * SHARED XMEM.
+ */
+ struct cl_chip *chip = cl_hw->chip;
+ const struct firmware *fw;
+ size_t size = 0;
+ char path_name[CL_PATH_MAX] = {0};
+ int ret;
+
+ snprintf(path_name, sizeof(path_name), "cl8k/%s",
+ cl_hw->conf->ce_dsp_external_data);
+
+ cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_external_data);
+
+ ret = request_firmware(&fw, path_name, chip->dev);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+ path_name, ret);
+ goto out;
+ }
+
+ size = fw->size;
+
+ ret = cl_dsp_hex_load(cl_hw, fw->data, REG_MACDSP_API_BASE_ADDR,
+ CEVA_DSP_EXT_DATA_SIZE, size);
+
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load HEX file\n");
+ goto out;
+ }
+
+ config_dma_for_external_data_copy(cl_hw);
+ ret = dsp_busy_wait(cl_hw, CEVA_CPM_DDTC_REG);
+
+ if (ret) {
+ cl_dbg_err(cl_hw, "dsp_busy_wait failed!\n");
+ goto out;
+ }
+
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static bool cl_dsp_is_universal_file(struct cl_chip *chip)
+{
+ return (cl_chip_is_tcv0_enabled(chip) &&
+ cl_chip_is_tcv1_enabled(chip) &&
+ !strcmp(chip->cl_hw_tcv0->conf->ce_dsp_code,
+ chip->cl_hw_tcv1->conf->ce_dsp_code));
+}
+
+static int load_dsp_code_dual(struct cl_chip *chip, const char *filename)
+{
+ struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+ struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+ u32 real_size;
+ u32 page;
+ const struct firmware *fw;
+ size_t size = 0;
+ u8 *buf = NULL;
+ char path_name[CL_PATH_MAX] = {0};
+ int ret;
+
+ snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+ cl_dbg_chip_verbose(chip, "from %s\n", filename);
+ ret = request_firmware(&fw, path_name, chip->dev);
+
+ if (ret) {
+ cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+ path_name, ret);
+ goto out;
+ }
+
+ size = fw->size;
+ buf = (u8 *)fw->data;
+
+ for (page = 0; page < CEVA_MAX_PAGES; page++) {
+ /* Copy DSP code (one page each time) */
+ ret = cl_dsp_hex_load(chip->cl_hw_tcv0, buf,
+ CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST,
+ CEVA_SHARED_PMEM_SIZE, size);
+ if (ret) {
+ cl_dbg_chip_err(chip, "Failed to load pmem page 0x%x!\n", page);
+ break;
+ }
+
+ config_dma_for_code_copy(cl_hw_tcv0, page);
+ ret = dsp_busy_wait(cl_hw_tcv0, CEVA_CPM_PDTC_REG);
+
+ if (ret) {
+ cl_dbg_err(cl_hw_tcv0, "dsp_busy_wait failed\n");
+ goto out;
+ }
+
+ config_dma_for_code_copy(cl_hw_tcv1, page);
+ ret = dsp_busy_wait(cl_hw_tcv1, CEVA_CPM_PDTC_REG);
+
+ if (ret) {
+ cl_dbg_err(cl_hw_tcv1, "dsp_busy_wait failed\n");
+ goto out;
+ }
+
+ real_size = min_t(u32, CEVA_SHARED_PMEM_SIZE * 3, size);
+ buf += real_size;
+ size -= real_size;
+ }
+
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int load_dsp_external_data_dual(struct cl_chip *chip, const char *filename)
+{
+ /*
+ * Shared XMEM is not accessible by host.
+ * Copy the XMEM section to DRAM first and then use CEVA internal DMA to copy to
+ * SHARED XMEM.
+ */
+ struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+ struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+ const struct firmware *fw;
+ size_t size = 0;
+ char path_name[CL_PATH_MAX] = {0};
+ int ret;
+
+ snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+ cl_dbg_chip_verbose(chip, "from %s\n", filename);
+ ret = request_firmware(&fw, path_name, chip->dev);
+
+ if (ret) {
+ cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+ path_name, ret);
+ goto out;
+ }
+
+ size = fw->size;
+
+ /* TCV0 */
+ ret = cl_dsp_hex_load(cl_hw_tcv0, fw->data, REG_MACDSP_API_BASE_ADDR,
+ CEVA_DSP_EXT_DATA_SIZE, size);
+
+ if (ret) {
+ cl_dbg_err(cl_hw_tcv0, "Failed to load HEX file\n");
+ goto out;
+ }
+
+ config_dma_for_external_data_copy(cl_hw_tcv0);
+ ret = dsp_busy_wait(cl_hw_tcv0, CEVA_CPM_DDTC_REG);
+
+ if (ret) {
+ cl_dbg_err(cl_hw_tcv0, "dsp_busy_wait failed!\n");
+ goto out;
+ }
+
+ /* TCV1 */
+ ret = cl_dsp_hex_load(cl_hw_tcv1, fw->data, REG_MACDSP_API_BASE_ADDR,
+ CEVA_DSP_EXT_DATA_SIZE, size);
+
+ if (ret) {
+ cl_dbg_err(cl_hw_tcv1, "Failed to load HEX file\n");
+ goto out;
+ }
+
+ config_dma_for_external_data_copy(cl_hw_tcv1);
+ ret = dsp_busy_wait(cl_hw_tcv1, CEVA_CPM_DDTC_REG);
+
+ if (ret) {
+ cl_dbg_err(cl_hw_tcv1, "dsp_busy_wait failed!\n");
+ goto out;
+ }
+
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int load_dsp_data_dual(struct cl_chip *chip, const char *filename)
+{
+ struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+ struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+ const struct firmware *fw;
+ size_t size = 0;
+ char path_name[CL_PATH_MAX] = {0};
+ int ret;
+
+ snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+ cl_dbg_chip_verbose(chip, "from %s\n", filename);
+ ret = request_firmware(&fw, path_name, chip->dev);
+
+ if (ret) {
+ cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+ path_name, ret);
+ goto out;
+ }
+
+ size = fw->size;
+
+ ret = cl_dsp_hex_load(cl_hw_tcv0, fw->data,
+ REG_MACDSP_API_BASE_ADDR,
+ CEVA_DSP_DATA_SIZE, size);
+
+ if (ret != 0) {
+ cl_dbg_err(cl_hw_tcv0, "Failed to load HEX file\n");
+ goto out;
+ }
+
+ ret = cl_dsp_hex_load(cl_hw_tcv1, fw->data,
+ REG_MACDSP_API_BASE_ADDR,
+ CEVA_DSP_DATA_SIZE, size);
+
+ if (ret != 0) {
+ cl_dbg_err(cl_hw_tcv1, "Failed to load HEX file\n");
+ goto out;
+ }
+
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static void print_ceva_core_info(struct cl_hw *cl_hw)
+{
+ cl_dbg_trace(cl_hw, "CEVA_CORE_VERSION_ADDR=0x%X.\n",
+ cl_reg_read(cl_hw, CEVA_CORE_VERSION_ADDR));
+ cl_dbg_trace(cl_hw, "CEVA_CORE_ID_ADDR=0x%X.\n",
+ cl_reg_read(cl_hw, CEVA_CORE_ID_ADDR));
+}
+
+static int cl_dsp_load_dual(struct cl_chip *chip)
+{
+ int ret = 0;
+ struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+ struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+ struct cl_tcv_conf *tcv0_conf = cl_hw_tcv0->conf;
+
+ modem_gcu_ceva_ctrl_external_wait_setf(cl_hw_tcv0, 0x1);
+ modem_gcu_ceva_ctrl_external_wait_setf(cl_hw_tcv1, 0x1);
+
+ print_ceva_core_info(cl_hw_tcv0);
+ print_ceva_core_info(cl_hw_tcv1);
+
+ ret = load_dsp_code_dual(chip, tcv0_conf->ce_dsp_code);
+ if (ret != 0) {
+ cl_dbg_chip_err(chip,
+ "Failed to load DSP code. Error code %d.\n",
+ ret);
+ return ret;
+ }
+
+ ret = load_dsp_external_data_dual(chip, tcv0_conf->ce_dsp_external_data);
+ if (ret != 0) {
+ cl_dbg_chip_err(chip,
+ "Failed to load DSP external data. Error code %d.\n",
+ ret);
+ return ret;
+ }
+
+ ret = load_dsp_data_dual(chip, tcv0_conf->ce_dsp_data);
+ if (ret != 0) {
+ cl_dbg_chip_err(chip,
+ "Failed to load DSP data. Error code %d.\n",
+ ret);
+ return ret;
+ }
+
+ macdsp_api_config_space_set(cl_hw_tcv0, 0);
+ /* Release DSP wait. */
+ cl_dsp_boot(cl_hw_tcv0);
+
+ macdsp_api_config_space_set(cl_hw_tcv1, 0);
+ /* Release DSP wait. */
+ cl_dsp_boot(cl_hw_tcv1);
+
+ return ret;
+}
+
+static int _cl_dsp_load(struct cl_hw *cl_hw)
+{
+ int ret = 0;
+
+ modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0x1);
+ print_ceva_core_info(cl_hw);
+
+ ret = load_dsp_code(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load DSP code %d\n", ret);
+ return ret;
+ }
+
+ ret = load_dsp_external_data(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load DSP external data %d\n", ret);
+ return ret;
+ }
+
+ ret = load_dsp_data(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load DSP data %d\n", ret);
+ return ret;
+ }
+
+ macdsp_api_config_space_set(cl_hw, 0);
+ /* Release DSP wait */
+ cl_dsp_boot(cl_hw);
+
+ return ret;
+}
+
+int cl_dsp_load_regular(struct cl_chip *chip)
+{
+ int ret = 0;
+
+ if (cl_dsp_is_universal_file(chip))
+ return cl_dsp_load_dual(chip);
+
+ if (cl_chip_is_tcv0_enabled(chip)) {
+ ret = _cl_dsp_load(chip->cl_hw_tcv0);
+ if (ret)
+ return ret;
+ }
+
+ if (cl_chip_is_tcv1_enabled(chip)) {
+ ret = _cl_dsp_load(chip->cl_hw_tcv1);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+int cl_dsp_load_recovery(struct cl_hw *cl_hw)
+{
+ int ret = 0;
+
+ modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0x1);
+
+ ret = load_dsp_external_data(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load DSP external data %d\n", ret);
+ return ret;
+ }
+
+ ret = load_dsp_data(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "Failed to load DSP data %d\n", ret);
+ return ret;
+ }
+
+ macdsp_api_config_space_set(cl_hw, 0);
+ /* Release DSP wait. */
+ cl_dsp_boot(cl_hw);
+
+ return ret;
+}