root/sys/dev/drm/amd/display/dc/dce110/dce110_timing_generator_v.c
/*
 * Copyright 2017 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "dm_services.h"

/* include DCE11 register header files */
#include "dce/dce_11_0_d.h"
#include "dce/dce_11_0_sh_mask.h"

#include "dc_types.h"
#include "dc_bios_types.h"
#include "dc.h"

#include "include/grph_object_id.h"
#include "include/logger_interface.h"
#include "dce110_timing_generator.h"
#include "dce110_timing_generator_v.h"

#include "timing_generator.h"

#define DC_LOGGER \
        tg->ctx->logger
/** ********************************************************************************
 *
 * DCE11 Timing Generator Implementation
 *
 **********************************************************************************/

/**
* Enable CRTCV
*/

static bool dce110_timing_generator_v_enable_crtc(struct timing_generator *tg)
{
/*
* Set MASTER_UPDATE_MODE to 0
* This is needed for DRR, and also suggested to be default value by Syed.
*/

        uint32_t value;

        value = 0;
        set_reg_field_value(value, 0,
                        CRTCV_MASTER_UPDATE_MODE, MASTER_UPDATE_MODE);
        dm_write_reg(tg->ctx,
                        mmCRTCV_MASTER_UPDATE_MODE, value);

        /* TODO: may want this on for looking for underflow */
        value = 0;
        dm_write_reg(tg->ctx, mmCRTCV_MASTER_UPDATE_MODE, value);

        value = 0;
        set_reg_field_value(value, 1,
                        CRTCV_MASTER_EN, CRTC_MASTER_EN);
        dm_write_reg(tg->ctx,
                        mmCRTCV_MASTER_EN, value);

        return true;
}

static bool dce110_timing_generator_v_disable_crtc(struct timing_generator *tg)
{
        uint32_t value;

        value = dm_read_reg(tg->ctx,
                        mmCRTCV_CONTROL);
        set_reg_field_value(value, 0,
                        CRTCV_CONTROL, CRTC_DISABLE_POINT_CNTL);
        set_reg_field_value(value, 0,
                                CRTCV_CONTROL, CRTC_MASTER_EN);
        dm_write_reg(tg->ctx,
                        mmCRTCV_CONTROL, value);
        /*
         * TODO: call this when adding stereo support
         * tg->funcs->disable_stereo(tg);
         */
        return true;
}

static void dce110_timing_generator_v_blank_crtc(struct timing_generator *tg)
{
        uint32_t addr = mmCRTCV_BLANK_CONTROL;
        uint32_t value = dm_read_reg(tg->ctx, addr);

        set_reg_field_value(
                value,
                1,
                CRTCV_BLANK_CONTROL,
                CRTC_BLANK_DATA_EN);

        set_reg_field_value(
                value,
                0,
                CRTCV_BLANK_CONTROL,
                CRTC_BLANK_DE_MODE);

        dm_write_reg(tg->ctx, addr, value);
}

static void dce110_timing_generator_v_unblank_crtc(struct timing_generator *tg)
{
        uint32_t addr = mmCRTCV_BLANK_CONTROL;
        uint32_t value = dm_read_reg(tg->ctx, addr);

        set_reg_field_value(
                value,
                0,
                CRTCV_BLANK_CONTROL,
                CRTC_BLANK_DATA_EN);

        set_reg_field_value(
                value,
                0,
                CRTCV_BLANK_CONTROL,
                CRTC_BLANK_DE_MODE);

        dm_write_reg(tg->ctx, addr, value);
}

static bool dce110_timing_generator_v_is_in_vertical_blank(
                struct timing_generator *tg)
{
        uint32_t addr = 0;
        uint32_t value = 0;
        uint32_t field = 0;

        addr = mmCRTCV_STATUS;
        value = dm_read_reg(tg->ctx, addr);
        field = get_reg_field_value(value, CRTCV_STATUS, CRTC_V_BLANK);
        return field == 1;
}

static bool dce110_timing_generator_v_is_counter_moving(struct timing_generator *tg)
{
        uint32_t value;
        uint32_t h1 = 0;
        uint32_t h2 = 0;
        uint32_t v1 = 0;
        uint32_t v2 = 0;

        value = dm_read_reg(tg->ctx, mmCRTCV_STATUS_POSITION);

        h1 = get_reg_field_value(
                        value,
                        CRTCV_STATUS_POSITION,
                        CRTC_HORZ_COUNT);

        v1 = get_reg_field_value(
                        value,
                        CRTCV_STATUS_POSITION,
                        CRTC_VERT_COUNT);

        value = dm_read_reg(tg->ctx, mmCRTCV_STATUS_POSITION);

        h2 = get_reg_field_value(
                        value,
                        CRTCV_STATUS_POSITION,
                        CRTC_HORZ_COUNT);

        v2 = get_reg_field_value(
                        value,
                        CRTCV_STATUS_POSITION,
                        CRTC_VERT_COUNT);

        if (h1 == h2 && v1 == v2)
                return false;
        else
                return true;
}

static void dce110_timing_generator_v_wait_for_vblank(struct timing_generator *tg)
{
        /* We want to catch beginning of VBlank here, so if the first try are
         * in VBlank, we might be very close to Active, in this case wait for
         * another frame
         */
        while (dce110_timing_generator_v_is_in_vertical_blank(tg)) {
                if (!dce110_timing_generator_v_is_counter_moving(tg)) {
                        /* error - no point to wait if counter is not moving */
                        break;
                }
        }

        while (!dce110_timing_generator_v_is_in_vertical_blank(tg)) {
                if (!dce110_timing_generator_v_is_counter_moving(tg)) {
                        /* error - no point to wait if counter is not moving */
                        break;
                }
        }
}

/**
* Wait till we are in VActive (anywhere in VActive)
*/
static void dce110_timing_generator_v_wait_for_vactive(struct timing_generator *tg)
{
        while (dce110_timing_generator_v_is_in_vertical_blank(tg)) {
                if (!dce110_timing_generator_v_is_counter_moving(tg)) {
                        /* error - no point to wait if counter is not moving */
                        break;
                }
        }
}

static void dce110_timing_generator_v_wait_for_state(struct timing_generator *tg,
        enum crtc_state state)
{
        switch (state) {
        case CRTC_STATE_VBLANK:
                dce110_timing_generator_v_wait_for_vblank(tg);
                break;

        case CRTC_STATE_VACTIVE:
                dce110_timing_generator_v_wait_for_vactive(tg);
                break;

        default:
                break;
        }
}

static void dce110_timing_generator_v_program_blanking(
        struct timing_generator *tg,
        const struct dc_crtc_timing *timing)
{
        uint32_t vsync_offset = timing->v_border_bottom +
                        timing->v_front_porch;
        uint32_t v_sync_start = timing->v_addressable + vsync_offset;

        uint32_t hsync_offset = timing->h_border_right +
                        timing->h_front_porch;
        uint32_t h_sync_start = timing->h_addressable + hsync_offset;

        struct dc_context *ctx = tg->ctx;
        uint32_t value = 0;
        uint32_t addr = 0;
        uint32_t tmp = 0;

        addr = mmCRTCV_H_TOTAL;
        value = dm_read_reg(ctx, addr);
        set_reg_field_value(
                value,
                timing->h_total - 1,
                CRTCV_H_TOTAL,
                CRTC_H_TOTAL);
        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_V_TOTAL;
        value = dm_read_reg(ctx, addr);
        set_reg_field_value(
                value,
                timing->v_total - 1,
                CRTCV_V_TOTAL,
                CRTC_V_TOTAL);
        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_H_BLANK_START_END;
        value = dm_read_reg(ctx, addr);

        tmp = timing->h_total -
                (h_sync_start + timing->h_border_left);

        set_reg_field_value(
                value,
                tmp,
                CRTCV_H_BLANK_START_END,
                CRTC_H_BLANK_END);

        tmp = tmp + timing->h_addressable +
                timing->h_border_left + timing->h_border_right;

        set_reg_field_value(
                value,
                tmp,
                CRTCV_H_BLANK_START_END,
                CRTC_H_BLANK_START);

        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_V_BLANK_START_END;
        value = dm_read_reg(ctx, addr);

        tmp = timing->v_total - (v_sync_start + timing->v_border_top);

        set_reg_field_value(
                value,
                tmp,
                CRTCV_V_BLANK_START_END,
                CRTC_V_BLANK_END);

        tmp = tmp + timing->v_addressable + timing->v_border_top +
                timing->v_border_bottom;

        set_reg_field_value(
                value,
                tmp,
                CRTCV_V_BLANK_START_END,
                CRTC_V_BLANK_START);

        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_H_SYNC_A;
        value = 0;
        set_reg_field_value(
                value,
                timing->h_sync_width,
                CRTCV_H_SYNC_A,
                CRTC_H_SYNC_A_END);
        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_H_SYNC_A_CNTL;
        value = dm_read_reg(ctx, addr);
        if (timing->flags.HSYNC_POSITIVE_POLARITY) {
                set_reg_field_value(
                        value,
                        0,
                        CRTCV_H_SYNC_A_CNTL,
                        CRTC_H_SYNC_A_POL);
        } else {
                set_reg_field_value(
                        value,
                        1,
                        CRTCV_H_SYNC_A_CNTL,
                        CRTC_H_SYNC_A_POL);
        }
        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_V_SYNC_A;
        value = 0;
        set_reg_field_value(
                value,
                timing->v_sync_width,
                CRTCV_V_SYNC_A,
                CRTC_V_SYNC_A_END);
        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_V_SYNC_A_CNTL;
        value = dm_read_reg(ctx, addr);
        if (timing->flags.VSYNC_POSITIVE_POLARITY) {
                set_reg_field_value(
                        value,
                        0,
                        CRTCV_V_SYNC_A_CNTL,
                        CRTC_V_SYNC_A_POL);
        } else {
                set_reg_field_value(
                        value,
                        1,
                        CRTCV_V_SYNC_A_CNTL,
                        CRTC_V_SYNC_A_POL);
        }
        dm_write_reg(ctx, addr, value);

        addr = mmCRTCV_INTERLACE_CONTROL;
        value = dm_read_reg(ctx, addr);
        set_reg_field_value(
                value,
                timing->flags.INTERLACE,
                CRTCV_INTERLACE_CONTROL,
                CRTC_INTERLACE_ENABLE);
        dm_write_reg(ctx, addr, value);
}

static void dce110_timing_generator_v_enable_advanced_request(
        struct timing_generator *tg,
        bool enable,
        const struct dc_crtc_timing *timing)
{
        uint32_t addr = mmCRTCV_START_LINE_CONTROL;
        uint32_t value = dm_read_reg(tg->ctx, addr);

        if (enable) {
                if ((timing->v_sync_width + timing->v_front_porch) <= 3) {
                        set_reg_field_value(
                                value,
                                3,
                                CRTCV_START_LINE_CONTROL,
                                CRTC_ADVANCED_START_LINE_POSITION);
                } else {
                        set_reg_field_value(
                                value,
                                4,
                                CRTCV_START_LINE_CONTROL,
                                CRTC_ADVANCED_START_LINE_POSITION);
                }
                set_reg_field_value(
                        value,
                        0,
                        CRTCV_START_LINE_CONTROL,
                        CRTC_LEGACY_REQUESTOR_EN);
        } else {
                set_reg_field_value(
                        value,
                        2,
                        CRTCV_START_LINE_CONTROL,
                        CRTC_ADVANCED_START_LINE_POSITION);
                set_reg_field_value(
                        value,
                        1,
                        CRTCV_START_LINE_CONTROL,
                        CRTC_LEGACY_REQUESTOR_EN);
        }

        dm_write_reg(tg->ctx, addr, value);
}

static void dce110_timing_generator_v_set_blank(struct timing_generator *tg,
                bool enable_blanking)
{
        if (enable_blanking)
                dce110_timing_generator_v_blank_crtc(tg);
        else
                dce110_timing_generator_v_unblank_crtc(tg);
}

static void dce110_timing_generator_v_program_timing(struct timing_generator *tg,
        const struct dc_crtc_timing *timing,
        bool use_vbios)
{
        if (use_vbios)
                dce110_timing_generator_program_timing_generator(tg, timing);
        else
                dce110_timing_generator_v_program_blanking(tg, timing);
}

static void dce110_timing_generator_v_program_blank_color(
                struct timing_generator *tg,
                const struct tg_color *black_color)
{
        uint32_t addr = mmCRTCV_BLACK_COLOR;
        uint32_t value = dm_read_reg(tg->ctx, addr);

        set_reg_field_value(
                value,
                black_color->color_b_cb,
                CRTCV_BLACK_COLOR,
                CRTC_BLACK_COLOR_B_CB);
        set_reg_field_value(
                value,
                black_color->color_g_y,
                CRTCV_BLACK_COLOR,
                CRTC_BLACK_COLOR_G_Y);
        set_reg_field_value(
                value,
                black_color->color_r_cr,
                CRTCV_BLACK_COLOR,
                CRTC_BLACK_COLOR_R_CR);

        dm_write_reg(tg->ctx, addr, value);
}

static void dce110_timing_generator_v_set_overscan_color_black(
        struct timing_generator *tg,
        const struct tg_color *color)
{
        struct dc_context *ctx = tg->ctx;
        uint32_t addr;
        uint32_t value = 0;

        set_reg_field_value(
                        value,
                        color->color_b_cb,
                        CRTC_OVERSCAN_COLOR,
                        CRTC_OVERSCAN_COLOR_BLUE);

        set_reg_field_value(
                        value,
                        color->color_r_cr,
                        CRTC_OVERSCAN_COLOR,
                        CRTC_OVERSCAN_COLOR_RED);

        set_reg_field_value(
                        value,
                        color->color_g_y,
                        CRTC_OVERSCAN_COLOR,
                        CRTC_OVERSCAN_COLOR_GREEN);

        addr = mmCRTCV_OVERSCAN_COLOR;
        dm_write_reg(ctx, addr, value);
        addr = mmCRTCV_BLACK_COLOR;
        dm_write_reg(ctx, addr, value);
        /* This is desirable to have a constant DAC output voltage during the
         * blank time that is higher than the 0 volt reference level that the
         * DAC outputs when the NBLANK signal
         * is asserted low, such as for output to an analog TV. */
        addr = mmCRTCV_BLANK_DATA_COLOR;
        dm_write_reg(ctx, addr, value);

        /* TO DO we have to program EXT registers and we need to know LB DATA
         * format because it is used when more 10 , i.e. 12 bits per color
         *
         * m_mmDxCRTC_OVERSCAN_COLOR_EXT
         * m_mmDxCRTC_BLACK_COLOR_EXT
         * m_mmDxCRTC_BLANK_DATA_COLOR_EXT
         */
}

static void dce110_tg_v_program_blank_color(struct timing_generator *tg,
                const struct tg_color *black_color)
{
        uint32_t addr = mmCRTCV_BLACK_COLOR;
        uint32_t value = dm_read_reg(tg->ctx, addr);

        set_reg_field_value(
                value,
                black_color->color_b_cb,
                CRTCV_BLACK_COLOR,
                CRTC_BLACK_COLOR_B_CB);
        set_reg_field_value(
                value,
                black_color->color_g_y,
                CRTCV_BLACK_COLOR,
                CRTC_BLACK_COLOR_G_Y);
        set_reg_field_value(
                value,
                black_color->color_r_cr,
                CRTCV_BLACK_COLOR,
                CRTC_BLACK_COLOR_R_CR);

        dm_write_reg(tg->ctx, addr, value);

        addr = mmCRTCV_BLANK_DATA_COLOR;
        dm_write_reg(tg->ctx, addr, value);
}

static void dce110_timing_generator_v_set_overscan_color(struct timing_generator *tg,
        const struct tg_color *overscan_color)
{
        struct dc_context *ctx = tg->ctx;
        uint32_t value = 0;
        uint32_t addr;

        set_reg_field_value(
                value,
                overscan_color->color_b_cb,
                CRTCV_OVERSCAN_COLOR,
                CRTC_OVERSCAN_COLOR_BLUE);

        set_reg_field_value(
                value,
                overscan_color->color_g_y,
                CRTCV_OVERSCAN_COLOR,
                CRTC_OVERSCAN_COLOR_GREEN);

        set_reg_field_value(
                value,
                overscan_color->color_r_cr,
                CRTCV_OVERSCAN_COLOR,
                CRTC_OVERSCAN_COLOR_RED);

        addr = mmCRTCV_OVERSCAN_COLOR;
        dm_write_reg(ctx, addr, value);
}

static void dce110_timing_generator_v_set_colors(struct timing_generator *tg,
        const struct tg_color *blank_color,
        const struct tg_color *overscan_color)
{
        if (blank_color != NULL)
                dce110_tg_v_program_blank_color(tg, blank_color);
        if (overscan_color != NULL)
                dce110_timing_generator_v_set_overscan_color(tg, overscan_color);
}

static void dce110_timing_generator_v_set_early_control(
                struct timing_generator *tg,
                uint32_t early_cntl)
{
        uint32_t regval;
        uint32_t address = mmCRTC_CONTROL;

        regval = dm_read_reg(tg->ctx, address);
        set_reg_field_value(regval, early_cntl,
                        CRTCV_CONTROL, CRTC_HBLANK_EARLY_CONTROL);
        dm_write_reg(tg->ctx, address, regval);
}

static uint32_t dce110_timing_generator_v_get_vblank_counter(struct timing_generator *tg)
{
        uint32_t addr = mmCRTCV_STATUS_FRAME_COUNT;
        uint32_t value = dm_read_reg(tg->ctx, addr);
        uint32_t field = get_reg_field_value(
                        value, CRTCV_STATUS_FRAME_COUNT, CRTC_FRAME_COUNT);

        return field;
}

static bool dce110_timing_generator_v_did_triggered_reset_occur(
        struct timing_generator *tg)
{
        DC_LOG_ERROR("Timing Sync not supported on underlay pipe\n");
        return false;
}

static void dce110_timing_generator_v_setup_global_swap_lock(
        struct timing_generator *tg,
        const struct dcp_gsl_params *gsl_params)
{
        DC_LOG_ERROR("Timing Sync not supported on underlay pipe\n");
        return;
}

static void dce110_timing_generator_v_enable_reset_trigger(
        struct timing_generator *tg,
        int source_tg_inst)
{
        DC_LOG_ERROR("Timing Sync not supported on underlay pipe\n");
        return;
}

static void dce110_timing_generator_v_disable_reset_trigger(
        struct timing_generator *tg)
{
        DC_LOG_ERROR("Timing Sync not supported on underlay pipe\n");
        return;
}

static void dce110_timing_generator_v_tear_down_global_swap_lock(
        struct timing_generator *tg)
{
        DC_LOG_ERROR("Timing Sync not supported on underlay pipe\n");
        return;
}

static void dce110_timing_generator_v_disable_vga(
        struct timing_generator *tg)
{
        return;
}

/** ********************************************************************************************
 *
 * DCE11 Timing Generator Constructor / Destructor
 *
 *********************************************************************************************/
static const struct timing_generator_funcs dce110_tg_v_funcs = {
                .validate_timing = dce110_tg_validate_timing,
                .program_timing = dce110_timing_generator_v_program_timing,
                .enable_crtc = dce110_timing_generator_v_enable_crtc,
                .disable_crtc = dce110_timing_generator_v_disable_crtc,
                .is_counter_moving = dce110_timing_generator_v_is_counter_moving,
                .get_position = NULL, /* Not to be implemented for underlay*/
                .get_frame_count = dce110_timing_generator_v_get_vblank_counter,
                .set_early_control = dce110_timing_generator_v_set_early_control,
                .wait_for_state = dce110_timing_generator_v_wait_for_state,
                .set_blank = dce110_timing_generator_v_set_blank,
                .set_colors = dce110_timing_generator_v_set_colors,
                .set_overscan_blank_color =
                                dce110_timing_generator_v_set_overscan_color_black,
                .set_blank_color = dce110_timing_generator_v_program_blank_color,
                .disable_vga = dce110_timing_generator_v_disable_vga,
                .did_triggered_reset_occur =
                                dce110_timing_generator_v_did_triggered_reset_occur,
                .setup_global_swap_lock =
                                dce110_timing_generator_v_setup_global_swap_lock,
                .enable_reset_trigger = dce110_timing_generator_v_enable_reset_trigger,
                .disable_reset_trigger = dce110_timing_generator_v_disable_reset_trigger,
                .tear_down_global_swap_lock =
                                dce110_timing_generator_v_tear_down_global_swap_lock,
                .enable_advanced_request =
                                dce110_timing_generator_v_enable_advanced_request
};

void dce110_timing_generator_v_construct(
        struct dce110_timing_generator *tg110,
        struct dc_context *ctx)
{
        tg110->controller_id = CONTROLLER_ID_UNDERLAY0;

        tg110->base.funcs = &dce110_tg_v_funcs;

        tg110->base.ctx = ctx;
        tg110->base.bp = ctx->dc_bios;

        tg110->max_h_total = CRTC_H_TOTAL__CRTC_H_TOTAL_MASK + 1;
        tg110->max_v_total = CRTC_V_TOTAL__CRTC_V_TOTAL_MASK + 1;

        tg110->min_h_blank = 56;
        tg110->min_h_front_porch = 4;
        tg110->min_h_back_porch = 4;
}