root/drivers/gpu/drm/imx/dc/dc-crtc.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2024 NXP
 */

#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/pm_runtime.h>
#include <linux/spinlock.h>

#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_modes.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>

#include "dc-de.h"
#include "dc-drv.h"
#include "dc-kms.h"
#include "dc-pe.h"

#define dc_crtc_dbg(crtc, fmt, ...)                                     \
do {                                                                    \
        struct drm_crtc *_crtc = (crtc);                                \
        drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,                    \
                    _crtc->base.id, _crtc->name, ##__VA_ARGS__);        \
} while (0)

#define dc_crtc_err(crtc, fmt, ...)                                     \
do {                                                                    \
        struct drm_crtc *_crtc = (crtc);                                \
        drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,                        \
                _crtc->base.id, _crtc->name, ##__VA_ARGS__);            \
} while (0)

#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)                          \
do {                                                                    \
        unsigned long ret;                                              \
        ret = wait_for_completion_timeout(&dc_crtc->c, HZ);             \
        if (ret == 0)                                                   \
                dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",      \
                                                        __func__);      \
} while (0)

#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)                                 \
do {                                                                    \
        struct dc_fg *_fg = (fg);                                       \
        if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {         \
                dc_fg_secondary_clear_channel_status(_fg);              \
                dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",          \
                                                        __func__);      \
        }                                                               \
} while (0)

#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)                  \
do {                                                                    \
        if (dc_fg_wait_for_secondary_syncup(fg))                        \
                dc_crtc_err(crtc,                                       \
                        "%s: FrameGen secondary channel isn't syncup\n",\
                                                        __func__);      \
} while (0)

static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
{
        return container_of(crtc, struct dc_crtc, base);
}

static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
{
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);

        return dc_fg_get_frame_index(dc_crtc->fg);
}

static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
{
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);

        enable_irq(dc_crtc->irq_dec_framecomplete);

        return 0;
}

static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
{
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);

        /* nosync due to atomic context */
        disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
}

static const struct drm_crtc_funcs dc_crtc_funcs = {
        .reset                  = drm_atomic_helper_crtc_reset,
        .destroy                = drm_crtc_cleanup,
        .set_config             = drm_atomic_helper_set_config,
        .page_flip              = drm_atomic_helper_page_flip,
        .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
        .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
        .get_vblank_counter     = dc_crtc_get_vblank_counter,
        .enable_vblank          = dc_crtc_enable_vblank,
        .disable_vblank         = dc_crtc_disable_vblank,
        .get_vblank_timestamp   = drm_crtc_vblank_helper_get_vblank_timestamp,
};

static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
{
        struct drm_crtc *crtc = crtc_state->crtc;
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);

        spin_lock_irq(&crtc->dev->event_lock);
        if (crtc_state->event) {
                WARN_ON(drm_crtc_vblank_get(crtc));
                WARN_ON(dc_crtc->event);
                dc_crtc->event = crtc_state->event;
                crtc_state->event = NULL;
        }
        spin_unlock_irq(&crtc->dev->event_lock);
}

static inline enum drm_mode_status
dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
{
        return dc_fg_check_clock(dc_crtc->fg, clk_khz);
}

static enum drm_mode_status
dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
{
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
        enum drm_mode_status status;

        status = dc_crtc_check_clock(dc_crtc, mode->clock);
        if (status != MODE_OK)
                return status;

        if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
                return MODE_CLOCK_HIGH;

        return MODE_OK;
}

static int
dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
        struct drm_crtc_state *new_crtc_state =
                                drm_atomic_get_new_crtc_state(state, crtc);
        struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
        enum drm_mode_status status;

        status = dc_crtc_check_clock(dc_crtc, adj->clock);
        if (status != MODE_OK)
                return -EINVAL;

        return 0;
}

static void
dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
        struct drm_crtc_state *new_crtc_state =
                                drm_atomic_get_new_crtc_state(state, crtc);
        struct dc_drm_device *dc_drm = to_dc_drm_device(crtc->dev);
        int idx, ret;

        if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
            !new_crtc_state->active)
                return;

        if (!drm_dev_enter(crtc->dev, &idx))
                return;

        /* request pixel engine power-on when CRTC starts to be active */
        ret = pm_runtime_resume_and_get(dc_drm->pe->dev);
        if (ret)
                dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
                            ret);

        drm_dev_exit(idx);
}

static void
dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
        struct drm_crtc_state *old_crtc_state =
                                drm_atomic_get_old_crtc_state(state, crtc);
        struct drm_crtc_state *new_crtc_state =
                                drm_atomic_get_new_crtc_state(state, crtc);
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
        int idx;

        if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
            (!old_crtc_state->active && !new_crtc_state->active))
                return;

        if (!drm_dev_enter(crtc->dev, &idx))
                goto out;

        enable_irq(dc_crtc->irq_ed_cont_shdload);

        /* flush plane update out to display */
        dc_ed_pec_sync_trigger(dc_crtc->ed_cont);

        DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdload_done);

        disable_irq(dc_crtc->irq_ed_cont_shdload);

        DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);

        drm_dev_exit(idx);

out:
        dc_crtc_queue_state_event(new_crtc_state);
}

static void
dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
        struct drm_crtc_state *new_crtc_state =
                                drm_atomic_get_new_crtc_state(state, crtc);
        struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
        enum dc_link_id cf_link;
        int idx, ret;

        dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));

        drm_crtc_vblank_on(crtc);

        if (!drm_dev_enter(crtc->dev, &idx))
                goto out;

        /* request display engine power-on when CRTC is enabled */
        ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
        if (ret < 0)
                dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
                            ret);

        enable_irq(dc_crtc->irq_dec_shdload);
        enable_irq(dc_crtc->irq_ed_cont_shdload);
        enable_irq(dc_crtc->irq_ed_safe_shdload);

        dc_fg_cfg_videomode(dc_crtc->fg, adj);

        dc_cf_framedimensions(dc_crtc->cf_cont,
                              adj->crtc_hdisplay, adj->crtc_vdisplay);
        dc_cf_framedimensions(dc_crtc->cf_safe,
                              adj->crtc_hdisplay, adj->crtc_vdisplay);

        /* constframe in safety stream shows blue frame */
        dc_cf_constantcolor_blue(dc_crtc->cf_safe);
        cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
        dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);

        /* show CRTC background if no plane is enabled */
        if (new_crtc_state->plane_mask == 0) {
                /* constframe in content stream shows black frame */
                dc_cf_constantcolor_black(dc_crtc->cf_cont);

                cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
                dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
        }

        dc_fg_enable_clock(dc_crtc->fg);
        dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
        dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
        dc_fg_shdtokgen(dc_crtc->fg);
        dc_fg_enable(dc_crtc->fg);

        DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdload_done);
        DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdload_done);
        DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdload_done);

        disable_irq(dc_crtc->irq_ed_safe_shdload);
        disable_irq(dc_crtc->irq_ed_cont_shdload);
        disable_irq(dc_crtc->irq_dec_shdload);

        DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);

        DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);

        drm_dev_exit(idx);

out:
        dc_crtc_queue_state_event(new_crtc_state);
}

static void
dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
        struct drm_crtc_state *new_crtc_state =
                                drm_atomic_get_new_crtc_state(state, crtc);
        struct dc_drm_device *dc_drm = to_dc_drm_device(crtc->dev);
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
        int idx;

        if (!drm_dev_enter(crtc->dev, &idx))
                goto out;

        enable_irq(dc_crtc->irq_dec_seqcomplete);
        dc_fg_disable(dc_crtc->fg);
        DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
        disable_irq(dc_crtc->irq_dec_seqcomplete);

        dc_fg_disable_clock(dc_crtc->fg);

        /* request pixel engine power-off as plane is off too */
        pm_runtime_put(dc_drm->pe->dev);

        /* request display engine power-off when CRTC is disabled */
        pm_runtime_put(dc_crtc->de->dev);

        drm_dev_exit(idx);

out:
        drm_crtc_vblank_off(crtc);

        spin_lock_irq(&crtc->dev->event_lock);
        if (new_crtc_state->event && !new_crtc_state->active) {
                drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
                new_crtc_state->event = NULL;
        }
        spin_unlock_irq(&crtc->dev->event_lock);
}

static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
                                         bool in_vblank_irq,
                                         int *vpos, int *hpos,
                                         ktime_t *stime, ktime_t *etime,
                                         const struct drm_display_mode *mode)
{
        struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
        int vdisplay = mode->crtc_vdisplay;
        int vtotal = mode->crtc_vtotal;
        bool reliable;
        int line;
        int idx;

        if (stime)
                *stime = ktime_get();

        if (!drm_dev_enter(crtc->dev, &idx)) {
                reliable = false;
                *vpos = 0;
                *hpos = 0;
                goto out;
        }

        /* line index starts with 0 for the first active output line */
        line = dc_fg_get_line_index(dc_crtc->fg);

        if (line < vdisplay)
                /* active scanout area - positive */
                *vpos = line + 1;
        else
                /* inside vblank - negative */
                *vpos = line - (vtotal - 1);

        *hpos = 0;

        reliable = true;

        drm_dev_exit(idx);
out:
        if (etime)
                *etime = ktime_get();

        return reliable;
}

static const struct drm_crtc_helper_funcs dc_helper_funcs = {
        .mode_valid             = dc_crtc_mode_valid,
        .atomic_check           = dc_crtc_atomic_check,
        .atomic_begin           = dc_crtc_atomic_begin,
        .atomic_flush           = dc_crtc_atomic_flush,
        .atomic_enable          = dc_crtc_atomic_enable,
        .atomic_disable         = dc_crtc_atomic_disable,
        .get_scanout_position   = dc_crtc_get_scanout_position,
};

static irqreturn_t dc_crtc_irq_handler_dec_framecomplete(int irq, void *dev_id)
{
        struct dc_crtc *dc_crtc = dev_id;
        struct drm_crtc *crtc = &dc_crtc->base;
        unsigned long flags;

        drm_crtc_handle_vblank(crtc);

        spin_lock_irqsave(&crtc->dev->event_lock, flags);
        if (dc_crtc->event) {
                drm_crtc_send_vblank_event(crtc, dc_crtc->event);
                dc_crtc->event = NULL;
                drm_crtc_vblank_put(crtc);
        }
        spin_unlock_irqrestore(&crtc->dev->event_lock, flags);

        return IRQ_HANDLED;
}

static irqreturn_t
dc_crtc_irq_handler_dec_seqcomplete_done(int irq, void *dev_id)
{
        struct dc_crtc *dc_crtc = dev_id;

        complete(&dc_crtc->dec_seqcomplete_done);

        return IRQ_HANDLED;
}

static irqreturn_t dc_crtc_irq_handler_dec_shdload_done(int irq, void *dev_id)
{
        struct dc_crtc *dc_crtc = dev_id;

        complete(&dc_crtc->dec_shdload_done);

        return IRQ_HANDLED;
}

static irqreturn_t
dc_crtc_irq_handler_ed_cont_shdload_done(int irq, void *dev_id)
{
        struct dc_crtc *dc_crtc = dev_id;

        complete(&dc_crtc->ed_cont_shdload_done);

        return IRQ_HANDLED;
}

static irqreturn_t
dc_crtc_irq_handler_ed_safe_shdload_done(int irq, void *dev_id)
{
        struct dc_crtc *dc_crtc = dev_id;

        complete(&dc_crtc->ed_safe_shdload_done);

        return IRQ_HANDLED;
}

static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
{
        struct {
                struct device *dev;
                unsigned int irq;
                irqreturn_t (*irq_handler)(int irq, void *dev_id);
        } irqs[DC_CRTC_IRQS] = {
                {
                        dc_crtc->de->dev,
                        dc_crtc->irq_dec_framecomplete,
                        dc_crtc_irq_handler_dec_framecomplete,
                }, {
                        dc_crtc->de->dev,
                        dc_crtc->irq_dec_seqcomplete,
                        dc_crtc_irq_handler_dec_seqcomplete_done,
                }, {
                        dc_crtc->de->dev,
                        dc_crtc->irq_dec_shdload,
                        dc_crtc_irq_handler_dec_shdload_done,
                }, {
                        dc_crtc->ed_cont->dev,
                        dc_crtc->irq_ed_cont_shdload,
                        dc_crtc_irq_handler_ed_cont_shdload_done,
                }, {
                        dc_crtc->ed_safe->dev,
                        dc_crtc->irq_ed_safe_shdload,
                        dc_crtc_irq_handler_ed_safe_shdload_done,
                },
        };
        int i, ret;

        for (i = 0; i < DC_CRTC_IRQS; i++) {
                struct dc_crtc_irq *irq = &dc_crtc->irqs[i];

                ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
                                       irqs[i].irq_handler, IRQF_NO_AUTOEN,
                                       dev_name(irqs[i].dev), dc_crtc);
                if (ret) {
                        dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
                                irqs[i].irq, ret);
                        return ret;
                }

                irq->dc_crtc = dc_crtc;
                irq->irq = irqs[i].irq;
        }

        return 0;
}

int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
{
        struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
        struct drm_device *drm = &dc_drm->base;
        struct dc_de *de = dc_drm->de[crtc_index];
        struct dc_pe *pe = dc_drm->pe;
        struct dc_plane *dc_primary;
        int ret;

        dc_crtc->de = de;

        init_completion(&dc_crtc->dec_seqcomplete_done);
        init_completion(&dc_crtc->dec_shdload_done);
        init_completion(&dc_crtc->ed_cont_shdload_done);
        init_completion(&dc_crtc->ed_safe_shdload_done);

        dc_crtc->cf_cont = pe->cf_cont[crtc_index];
        dc_crtc->cf_safe = pe->cf_safe[crtc_index];
        dc_crtc->ed_cont = pe->ed_cont[crtc_index];
        dc_crtc->ed_safe = pe->ed_safe[crtc_index];
        dc_crtc->fg = de->fg;

        dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
        dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
        dc_crtc->irq_dec_shdload = de->irq_shdload;
        dc_crtc->irq_ed_safe_shdload = dc_crtc->ed_safe->irq_shdload;
        dc_crtc->irq_ed_cont_shdload = dc_crtc->ed_cont->irq_shdload;

        dc_primary = &dc_drm->dc_primary[crtc_index];
        ret = dc_plane_init(dc_drm, dc_primary);
        if (ret) {
                dev_err(de->dev, "failed to initialize primary plane: %d\n",
                        ret);
                return ret;
        }

        drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);

        ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
                                        NULL, &dc_crtc_funcs, NULL);
        if (ret)
                dev_err(de->dev, "failed to add CRTC: %d\n", ret);

        return ret;
}

int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
{
        struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
        struct drm_device *drm = &dc_drm->base;

        return dc_crtc_request_irqs(drm, dc_crtc);
}