root/drivers/gpu/drm/sti/sti_awg_utils.c
// 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;
}