/* SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0)
 *
 * Copyright 2008-2016 Freescale Semiconductor Inc.
 * Copyright 2016,2019 NXP
 */

#ifndef __RTA_JUMP_CMD_H__
#define __RTA_JUMP_CMD_H__

extern enum rta_sec_era rta_sec_era;

static const uint32_t jump_test_cond[][2] = {
	{ NIFP,     JUMP_COND_NIFP },
	{ NIP,      JUMP_COND_NIP },
	{ NOP,      JUMP_COND_NOP },
	{ NCP,      JUMP_COND_NCP },
	{ CALM,     JUMP_COND_CALM },
	{ SELF,     JUMP_COND_SELF },
	{ SHRD,     JUMP_COND_SHRD },
	{ JQP,      JUMP_COND_JQP },
	{ MATH_Z,   JUMP_COND_MATH_Z },
	{ MATH_N,   JUMP_COND_MATH_N },
	{ MATH_NV,  JUMP_COND_MATH_NV },
	{ MATH_C,   JUMP_COND_MATH_C },
	{ PK_0,     JUMP_COND_PK_0 },
	{ PK_GCD_1, JUMP_COND_PK_GCD_1 },
	{ PK_PRIME, JUMP_COND_PK_PRIME },
	{ CLASS1,   JUMP_CLASS_CLASS1 },
	{ CLASS2,   JUMP_CLASS_CLASS2 },
	{ BOTH,     JUMP_CLASS_BOTH }
};

static const uint32_t jump_test_math_cond[][2] = {
	{ MATH_Z,   JUMP_COND_MATH_Z },
	{ MATH_N,   JUMP_COND_MATH_N },
	{ MATH_NV,  JUMP_COND_MATH_NV },
	{ MATH_C,   JUMP_COND_MATH_C }
};

static const uint32_t jump_src_dst[][2] = {
	{ MATH0,     JUMP_SRC_DST_MATH0 },
	{ MATH1,     JUMP_SRC_DST_MATH1 },
	{ MATH2,     JUMP_SRC_DST_MATH2 },
	{ MATH3,     JUMP_SRC_DST_MATH3 },
	{ DPOVRD,    JUMP_SRC_DST_DPOVRD },
	{ SEQINSZ,   JUMP_SRC_DST_SEQINLEN },
	{ SEQOUTSZ,  JUMP_SRC_DST_SEQOUTLEN },
	{ VSEQINSZ,  JUMP_SRC_DST_VARSEQINLEN },
	{ VSEQOUTSZ, JUMP_SRC_DST_VARSEQOUTLEN }
};

static inline int
rta_jump(struct program *program, uint64_t address,
	 enum rta_jump_type jump_type,
	 enum rta_jump_cond test_type,
	 uint32_t test_condition, uint32_t src_dst)
{
	uint32_t opcode = CMD_JUMP;
	unsigned int start_pc = program->current_pc;
	int ret = -EINVAL;

	if (((jump_type == GOSUB) || (jump_type == RETURN)) &&
	    (rta_sec_era < RTA_SEC_ERA_4)) {
		pr_err("JUMP: Jump type not supported by SEC Era %d\n",
		       USER_SEC_ERA(rta_sec_era));
		goto err;
	}

	if (((jump_type == LOCAL_JUMP_INC) || (jump_type == LOCAL_JUMP_DEC)) &&
	    (rta_sec_era <= RTA_SEC_ERA_5)) {
		pr_err("JUMP_INCDEC: Jump type not supported by SEC Era %d\n",
		       USER_SEC_ERA(rta_sec_era));
		goto err;
	}

	switch (jump_type) {
	case (LOCAL_JUMP):
		/*
		 * opcode |= JUMP_TYPE_LOCAL;
		 * JUMP_TYPE_LOCAL is 0
		 */
		break;
	case (HALT):
		opcode |= JUMP_TYPE_HALT;
		break;
	case (HALT_STATUS):
		opcode |= JUMP_TYPE_HALT_USER;
		break;
	case (FAR_JUMP):
		opcode |= JUMP_TYPE_NONLOCAL;
		break;
	case (GOSUB):
		opcode |= JUMP_TYPE_GOSUB;
		break;
	case (RETURN):
		opcode |= JUMP_TYPE_RETURN;
		break;
	case (LOCAL_JUMP_INC):
		opcode |= JUMP_TYPE_LOCAL_INC;
		break;
	case (LOCAL_JUMP_DEC):
		opcode |= JUMP_TYPE_LOCAL_DEC;
		break;
	default:
		pr_err("JUMP: Invalid jump type. SEC Program Line: %d\n",
		       program->current_pc);
		goto err;
	}

	switch (test_type) {
	case (ALL_TRUE):
		/*
		 * opcode |= JUMP_TEST_ALL;
		 * JUMP_TEST_ALL is 0
		 */
		break;
	case (ALL_FALSE):
		opcode |= JUMP_TEST_INVALL;
		break;
	case (ANY_TRUE):
		opcode |= JUMP_TEST_ANY;
		break;
	case (ANY_FALSE):
		opcode |= JUMP_TEST_INVANY;
		break;
	default:
		pr_err("JUMP: test type not supported. SEC Program Line: %d\n",
		       program->current_pc);
		goto err;
	}

	/* write test condition field */
	if ((jump_type != LOCAL_JUMP_INC) && (jump_type != LOCAL_JUMP_DEC)) {
		__rta_map_flags(test_condition, jump_test_cond,
				ARRAY_SIZE(jump_test_cond), &opcode);
	} else {
		uint32_t val = 0;

		ret = __rta_map_opcode(src_dst, jump_src_dst,
				       ARRAY_SIZE(jump_src_dst), &val);
		if (ret < 0) {
			pr_err("JUMP_INCDEC: SRC_DST not supported. SEC PC: %d; Instr: %d\n",
			       program->current_pc,
			       program->current_instruction);
			goto err;
		}
		opcode |= val;

		__rta_map_flags(test_condition, jump_test_math_cond,
				ARRAY_SIZE(jump_test_math_cond), &opcode);
	}

	/* write local offset field for local jumps and user-defined halt */
	if ((jump_type == LOCAL_JUMP) || (jump_type == LOCAL_JUMP_INC) ||
	    (jump_type == LOCAL_JUMP_DEC) || (jump_type == GOSUB) ||
	    (jump_type == HALT_STATUS))
		opcode |= (uint32_t)(address & JUMP_OFFSET_MASK);

	__rta_out32(program, opcode);
	program->current_instruction++;

	if (jump_type == FAR_JUMP)
		__rta_out64(program, program->ps, address);

	return (int)start_pc;

 err:
	program->first_error_pc = start_pc;
	program->current_instruction++;
	return ret;
}

#endif /* __RTA_JUMP_CMD_H__ */
