// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) STMicroelectronics SA 2014 * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics. */ #include <drm/drm_print.h> #include "sti_awg_utils.h" #define AWG_DELAY (-5) #define AWG_OPCODE_OFFSET 10 #define AWG_MAX_ARG 0x3ff enum opcode { SET, RPTSET, RPLSET, SKIP, STOP, REPEAT, REPLAY, JUMP, HOLD, }; static int awg_generate_instr(enum opcode opcode, long int arg, long int mux_sel, long int data_en, struct awg_code_generation_params *fwparams) { u32 instruction = 0; u32 mux = (mux_sel << 8) & 0x1ff; u32 data_enable = (data_en << 9) & 0x2ff; long int arg_tmp = arg; /* skip, repeat and replay arg should not exceed 1023. * If user wants to exceed this value, the instruction should be * duplicate and arg should be adjust for each duplicated instruction. * * mux_sel is used in case of SAV/EAV synchronization. */ while (arg_tmp > 0) { arg = arg_tmp; if (fwparams->instruction_offset >= AWG_MAX_INST) { DRM_ERROR("too many number of instructions\n"); return -EINVAL; } switch (opcode) { case SKIP: /* leave 'arg' + 1 pixel elapsing without changing * output bus */ arg--; /* pixel adjustment */ arg_tmp--; if (arg < 0) { /* SKIP instruction not needed */ return 0; } if (arg == 0) { /* SKIP 0 not permitted but we want to skip 1 * pixel. So we transform SKIP into SET * instruction */ opcode = SET; break; } mux = 0; data_enable = 0; arg &= AWG_MAX_ARG; break; case REPEAT: case REPLAY: if (arg == 0) { /* REPEAT or REPLAY instruction not needed */ return 0; } mux = 0; data_enable = 0; arg &= AWG_MAX_ARG; break; case JUMP: mux = 0; data_enable = 0; arg |= 0x40; /* for jump instruction 7th bit is 1 */ arg &= AWG_MAX_ARG; break; case STOP: arg = 0; break; case SET: case RPTSET: case RPLSET: case HOLD: arg &= (0x0ff); break; default: DRM_ERROR("instruction %d does not exist\n", opcode); return -EINVAL; } arg_tmp = arg_tmp - arg; arg = ((arg + mux) + data_enable); instruction = ((opcode) << AWG_OPCODE_OFFSET) | arg; fwparams->ram_code[fwparams->instruction_offset] = instruction & (0x3fff); fwparams->instruction_offset++; } return 0; } static int awg_generate_line_signal( struct awg_code_generation_params *fwparams, struct awg_timing *timing) { long int val; int ret = 0; if (timing->trailing_pixels > 0) { /* skip trailing pixel */ val = timing->blanking_level; ret |= awg_generate_instr(RPLSET, val, 0, 0, fwparams); val = timing->trailing_pixels - 1 + AWG_DELAY; ret |= awg_generate_instr(SKIP, val, 0, 0, fwparams); } /* set DE signal high */ val = timing->blanking_level; ret |= awg_generate_instr((timing->trailing_pixels > 0) ? SET : RPLSET, val, 0, 1, fwparams); if (timing->blanking_pixels > 0) { /* skip the number of active pixel */ val = timing->active_pixels - 1; ret |= awg_generate_instr(SKIP, val, 0, 1, fwparams); /* set DE signal low */ val = timing->blanking_level; ret |= awg_generate_instr(SET, val, 0, 0, fwparams); } return ret; } int sti_awg_generate_code_data_enable_mode( struct awg_code_generation_params *fwparams, struct awg_timing *timing) { long int val, tmp_val; int ret = 0; if (timing->trailing_lines > 0) { /* skip trailing lines */ val = timing->blanking_level; ret |= awg_generate_instr(RPLSET, val, 0, 0, fwparams); val = timing->trailing_lines - 1; ret |= awg_generate_instr(REPLAY, val, 0, 0, fwparams); } tmp_val = timing->active_lines - 1; while (tmp_val > 0) { /* generate DE signal for each line */ ret |= awg_generate_line_signal(fwparams, timing); /* replay the sequence as many active lines defined */ ret |= awg_generate_instr(REPLAY, min_t(int, AWG_MAX_ARG, tmp_val), 0, 0, fwparams); tmp_val -= AWG_MAX_ARG; } if (timing->blanking_lines > 0) { /* skip blanking lines */ val = timing->blanking_level; ret |= awg_generate_instr(RPLSET, val, 0, 0, fwparams); val = timing->blanking_lines - 1; ret |= awg_generate_instr(REPLAY, val, 0, 0, fwparams); } return ret; }