| // 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; |
| } |