1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> 4 */ 5 6 #include <linux/clk.h> 7 #include <linux/regmap.h> 8 #include <linux/units.h> 9 10 #include <drm/drm_atomic.h> 11 #include <drm/drm_atomic_helper.h> 12 #include <drm/drm_print.h> 13 #include <drm/drm_managed.h> 14 #include <drm/drm_vblank_helper.h> 15 16 #include "vs_crtc_regs.h" 17 #include "vs_crtc.h" 18 #include "vs_dc.h" 19 #include "vs_dc_top_regs.h" 20 #include "vs_drm.h" 21 #include "vs_plane.h" 22 23 static void vs_crtc_atomic_disable(struct drm_crtc *crtc, 24 struct drm_atomic_state *state) 25 { 26 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 27 struct vs_dc *dc = vcrtc->dc; 28 unsigned int output = vcrtc->id; 29 30 drm_crtc_vblank_off(crtc); 31 32 clk_disable_unprepare(dc->pix_clk[output]); 33 } 34 35 static void vs_crtc_atomic_enable(struct drm_crtc *crtc, 36 struct drm_atomic_state *state) 37 { 38 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 39 struct vs_dc *dc = vcrtc->dc; 40 unsigned int output = vcrtc->id; 41 42 drm_WARN_ON(&dc->drm_dev->base, 43 clk_prepare_enable(dc->pix_clk[output])); 44 45 drm_crtc_vblank_on(crtc); 46 } 47 48 static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc) 49 { 50 struct drm_display_mode *mode = &crtc->state->adjusted_mode; 51 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 52 struct vs_dc *dc = vcrtc->dc; 53 unsigned int output = vcrtc->id; 54 55 regmap_write(dc->regs, VSDC_DISP_HSIZE(output), 56 VSDC_DISP_HSIZE_DISP(mode->hdisplay) | 57 VSDC_DISP_HSIZE_TOTAL(mode->htotal)); 58 regmap_write(dc->regs, VSDC_DISP_VSIZE(output), 59 VSDC_DISP_VSIZE_DISP(mode->vdisplay) | 60 VSDC_DISP_VSIZE_TOTAL(mode->vtotal)); 61 regmap_write(dc->regs, VSDC_DISP_HSYNC(output), 62 VSDC_DISP_HSYNC_START(mode->hsync_start) | 63 VSDC_DISP_HSYNC_END(mode->hsync_end) | 64 VSDC_DISP_HSYNC_EN); 65 if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) 66 regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output), 67 VSDC_DISP_HSYNC_POL); 68 regmap_write(dc->regs, VSDC_DISP_VSYNC(output), 69 VSDC_DISP_VSYNC_START(mode->vsync_start) | 70 VSDC_DISP_VSYNC_END(mode->vsync_end) | 71 VSDC_DISP_VSYNC_EN); 72 if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) 73 regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output), 74 VSDC_DISP_VSYNC_POL); 75 76 WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000)); 77 } 78 79 static enum drm_mode_status 80 vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) 81 { 82 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 83 struct vs_dc *dc = vcrtc->dc; 84 unsigned int output = vcrtc->id; 85 long rate; 86 87 if (mode->htotal > VSDC_DISP_TIMING_VALUE_MAX) 88 return MODE_BAD_HVALUE; 89 if (mode->vtotal > VSDC_DISP_TIMING_VALUE_MAX) 90 return MODE_BAD_VVALUE; 91 92 rate = clk_round_rate(dc->pix_clk[output], mode->clock * HZ_PER_KHZ); 93 if (rate <= 0) 94 return MODE_CLOCK_RANGE; 95 96 return MODE_OK; 97 } 98 99 static bool vs_crtc_mode_fixup(struct drm_crtc *crtc, 100 const struct drm_display_mode *m, 101 struct drm_display_mode *adjusted_mode) 102 { 103 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 104 struct vs_dc *dc = vcrtc->dc; 105 unsigned int output = vcrtc->id; 106 long clk_rate; 107 108 drm_mode_set_crtcinfo(adjusted_mode, 0); 109 110 /* Feedback the pixel clock to crtc_clock */ 111 clk_rate = adjusted_mode->crtc_clock * HZ_PER_KHZ; 112 clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate); 113 if (clk_rate <= 0) 114 return false; 115 116 adjusted_mode->crtc_clock = clk_rate / HZ_PER_KHZ; 117 118 return true; 119 } 120 121 static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = { 122 .atomic_flush = drm_crtc_vblank_atomic_flush, 123 .atomic_enable = vs_crtc_atomic_enable, 124 .atomic_disable = vs_crtc_atomic_disable, 125 .mode_set_nofb = vs_crtc_mode_set_nofb, 126 .mode_valid = vs_crtc_mode_valid, 127 .mode_fixup = vs_crtc_mode_fixup, 128 }; 129 130 static int vs_crtc_enable_vblank(struct drm_crtc *crtc) 131 { 132 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 133 struct vs_dc *dc = vcrtc->dc; 134 135 regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id)); 136 137 return 0; 138 } 139 140 static void vs_crtc_disable_vblank(struct drm_crtc *crtc) 141 { 142 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 143 struct vs_dc *dc = vcrtc->dc; 144 145 regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id)); 146 } 147 148 static const struct drm_crtc_funcs vs_crtc_funcs = { 149 .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, 150 .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, 151 .page_flip = drm_atomic_helper_page_flip, 152 .reset = drm_atomic_helper_crtc_reset, 153 .set_config = drm_atomic_helper_set_config, 154 .enable_vblank = vs_crtc_enable_vblank, 155 .disable_vblank = vs_crtc_disable_vblank, 156 }; 157 158 struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, 159 unsigned int output) 160 { 161 struct vs_crtc *vcrtc; 162 struct drm_plane *primary; 163 int ret; 164 165 vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL); 166 if (!vcrtc) 167 return ERR_PTR(-ENOMEM); 168 vcrtc->dc = dc; 169 vcrtc->id = output; 170 171 /* Create our primary plane */ 172 primary = vs_primary_plane_init(drm_dev, dc); 173 if (IS_ERR(primary)) { 174 drm_err(drm_dev, "Couldn't create the primary plane\n"); 175 return ERR_PTR(PTR_ERR(primary)); 176 } 177 178 ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base, 179 primary, 180 NULL, 181 &vs_crtc_funcs, 182 NULL); 183 if (ret) { 184 drm_err(drm_dev, "Couldn't initialize CRTC\n"); 185 return ERR_PTR(ret); 186 } 187 188 drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs); 189 190 return vcrtc; 191 } 192