199 lines
5.4 KiB
C
199 lines
5.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/units.h>
|
|
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_managed.h>
|
|
#include <drm/drm_vblank_helper.h>
|
|
|
|
#include "vs_crtc_regs.h"
|
|
#include "vs_crtc.h"
|
|
#include "vs_dc.h"
|
|
#include "vs_dc_top_regs.h"
|
|
#include "vs_drm.h"
|
|
#include "vs_plane.h"
|
|
|
|
static void vs_crtc_atomic_disable(struct drm_crtc *crtc,
|
|
struct drm_atomic_commit *state)
|
|
{
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
unsigned int output = vcrtc->id;
|
|
|
|
drm_crtc_vblank_off(crtc);
|
|
|
|
clk_disable_unprepare(dc->pix_clk[output]);
|
|
}
|
|
|
|
static void vs_crtc_atomic_enable(struct drm_crtc *crtc,
|
|
struct drm_atomic_commit *state)
|
|
{
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
unsigned int output = vcrtc->id;
|
|
|
|
drm_WARN_ON(&dc->drm_dev->base,
|
|
clk_prepare_enable(dc->pix_clk[output]));
|
|
|
|
drm_crtc_vblank_on(crtc);
|
|
}
|
|
|
|
static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
unsigned int output = vcrtc->id;
|
|
|
|
regmap_write(dc->regs, VSDC_DISP_HSIZE(output),
|
|
VSDC_DISP_HSIZE_DISP(mode->hdisplay) |
|
|
VSDC_DISP_HSIZE_TOTAL(mode->htotal));
|
|
regmap_write(dc->regs, VSDC_DISP_VSIZE(output),
|
|
VSDC_DISP_VSIZE_DISP(mode->vdisplay) |
|
|
VSDC_DISP_VSIZE_TOTAL(mode->vtotal));
|
|
regmap_write(dc->regs, VSDC_DISP_HSYNC(output),
|
|
VSDC_DISP_HSYNC_START(mode->hsync_start) |
|
|
VSDC_DISP_HSYNC_END(mode->hsync_end) |
|
|
VSDC_DISP_HSYNC_EN);
|
|
if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
|
|
regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output),
|
|
VSDC_DISP_HSYNC_POL);
|
|
regmap_write(dc->regs, VSDC_DISP_VSYNC(output),
|
|
VSDC_DISP_VSYNC_START(mode->vsync_start) |
|
|
VSDC_DISP_VSYNC_END(mode->vsync_end) |
|
|
VSDC_DISP_VSYNC_EN);
|
|
if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
|
|
regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output),
|
|
VSDC_DISP_VSYNC_POL);
|
|
|
|
WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000));
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
|
|
{
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
unsigned int output = vcrtc->id;
|
|
long rate;
|
|
|
|
if (mode->htotal > VSDC_DISP_TIMING_VALUE_MAX)
|
|
return MODE_BAD_HVALUE;
|
|
if (mode->vtotal > VSDC_DISP_TIMING_VALUE_MAX)
|
|
return MODE_BAD_VVALUE;
|
|
|
|
rate = clk_round_rate(dc->pix_clk[output], mode->clock * HZ_PER_KHZ);
|
|
if (rate <= 0)
|
|
return MODE_CLOCK_RANGE;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static bool vs_crtc_mode_fixup(struct drm_crtc *crtc,
|
|
const struct drm_display_mode *m,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
unsigned int output = vcrtc->id;
|
|
long clk_rate;
|
|
|
|
drm_mode_set_crtcinfo(adjusted_mode, 0);
|
|
|
|
/* Feedback the pixel clock to crtc_clock */
|
|
clk_rate = adjusted_mode->crtc_clock * HZ_PER_KHZ;
|
|
clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate);
|
|
if (clk_rate <= 0)
|
|
return false;
|
|
|
|
adjusted_mode->crtc_clock = clk_rate / HZ_PER_KHZ;
|
|
|
|
return true;
|
|
}
|
|
|
|
static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = {
|
|
.atomic_flush = drm_crtc_vblank_atomic_flush,
|
|
.atomic_enable = vs_crtc_atomic_enable,
|
|
.atomic_disable = vs_crtc_atomic_disable,
|
|
.mode_set_nofb = vs_crtc_mode_set_nofb,
|
|
.mode_valid = vs_crtc_mode_valid,
|
|
.mode_fixup = vs_crtc_mode_fixup,
|
|
};
|
|
|
|
static int vs_crtc_enable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
|
|
regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vs_crtc_disable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
|
|
struct vs_dc *dc = vcrtc->dc;
|
|
|
|
regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
|
|
}
|
|
|
|
static const struct drm_crtc_funcs vs_crtc_funcs = {
|
|
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
|
|
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
|
|
.page_flip = drm_atomic_helper_page_flip,
|
|
.reset = drm_atomic_helper_crtc_reset,
|
|
.set_config = drm_atomic_helper_set_config,
|
|
.enable_vblank = vs_crtc_enable_vblank,
|
|
.disable_vblank = vs_crtc_disable_vblank,
|
|
};
|
|
|
|
struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
|
|
unsigned int output)
|
|
{
|
|
struct vs_crtc *vcrtc;
|
|
struct drm_plane *primary, *cursor;
|
|
int ret;
|
|
|
|
vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL);
|
|
if (!vcrtc)
|
|
return ERR_PTR(-ENOMEM);
|
|
vcrtc->dc = dc;
|
|
vcrtc->id = output;
|
|
|
|
/* Create our primary plane */
|
|
primary = vs_primary_plane_init(drm_dev, dc);
|
|
if (IS_ERR(primary)) {
|
|
drm_err(drm_dev, "Couldn't create the primary plane\n");
|
|
return ERR_PTR(PTR_ERR(primary));
|
|
}
|
|
|
|
/* Create our cursor plane */
|
|
cursor = vs_cursor_plane_init(drm_dev, dc);
|
|
if (IS_ERR(cursor)) {
|
|
drm_err(drm_dev, "Couldn't create the cursor plane\n");
|
|
return ERR_CAST(cursor);
|
|
}
|
|
|
|
ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base,
|
|
primary,
|
|
cursor,
|
|
&vs_crtc_funcs,
|
|
NULL);
|
|
if (ret) {
|
|
drm_err(drm_dev, "Couldn't initialize CRTC\n");
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs);
|
|
|
|
return vcrtc;
|
|
}
|