1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS) 4 * 5 * Authors: 6 * Icenowy Zheng <zhengxingda@iscas.ac.cn> 7 */ 8 9 #include <linux/log2.h> 10 #include <linux/regmap.h> 11 12 #include <drm/drm_atomic.h> 13 #include <drm/drm_atomic_helper.h> 14 #include <drm/drm_blend.h> 15 #include <drm/drm_crtc.h> 16 #include <drm/drm_fb_dma_helper.h> 17 #include <drm/drm_fourcc.h> 18 #include <drm/drm_framebuffer.h> 19 #include <drm/drm_gem_atomic_helper.h> 20 #include <drm/drm_modeset_helper_vtables.h> 21 #include <drm/drm_plane.h> 22 #include <drm/drm_print.h> 23 24 #include "vs_crtc.h" 25 #include "vs_plane.h" 26 #include "vs_dc.h" 27 #include "vs_hwdb.h" 28 #include "vs_cursor_plane_regs.h" 29 30 #define VSDC_MIN_CURSOR_SIZE 32 31 #define VSDC_MAX_CURSOR_SIZE 256 32 33 #define VSDC_CURSOR_LOCATION_MAX_POSITIVE BIT_MASK(15) 34 #define VSDC_CURSOR_LOCATION_MAX_NEGATIVE BIT_MASK(5) 35 36 static bool vs_cursor_plane_check_coord(int32_t coord) 37 { 38 if (coord >= 0) 39 return coord <= VSDC_CURSOR_LOCATION_MAX_POSITIVE; 40 else 41 return (-coord) <= VSDC_CURSOR_LOCATION_MAX_NEGATIVE; 42 } 43 44 static int vs_cursor_plane_atomic_check(struct drm_plane *plane, 45 struct drm_atomic_commit *state) 46 { 47 struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, 48 plane); 49 struct drm_crtc *crtc = new_plane_state->crtc; 50 struct drm_framebuffer *fb = new_plane_state->fb; 51 struct drm_crtc_state *crtc_state = NULL; 52 struct vs_crtc *vcrtc; 53 struct vs_dc *dc; 54 int ret; 55 56 if (crtc) 57 crtc_state = drm_atomic_get_new_crtc_state(state, crtc); 58 59 ret = drm_atomic_helper_check_plane_state(new_plane_state, 60 crtc_state, 61 DRM_PLANE_NO_SCALING, 62 DRM_PLANE_NO_SCALING, 63 true, true); 64 if (ret) 65 return ret; 66 67 if (!new_plane_state->visible) 68 return 0; /* Skip validity check */ 69 70 vcrtc = drm_crtc_to_vs_crtc(crtc); 71 dc = vcrtc->dc; 72 73 /* Only certain PoT square sizes is supported. */ 74 if (!is_power_of_2(new_plane_state->crtc_w) || 75 new_plane_state->crtc_w < VSDC_MIN_CURSOR_SIZE || 76 new_plane_state->crtc_w > dc->identity.max_cursor_size) 77 return -EINVAL; 78 79 if (new_plane_state->crtc_w != new_plane_state->crtc_h) 80 return -EINVAL; 81 82 /* Check if the cursor is inside the register fields' range */ 83 if (!vs_cursor_plane_check_coord(new_plane_state->crtc_x) || 84 !vs_cursor_plane_check_coord(new_plane_state->crtc_y)) 85 return -EINVAL; 86 87 /* Extra line padding isn't supported */ 88 if (fb->pitches[0] != 89 drm_format_info_min_pitch(fb->format, 0, new_plane_state->crtc_w)) 90 return -EINVAL; 91 92 return 0; 93 } 94 95 static void vs_cursor_plane_commit(struct vs_dc *dc, unsigned int output) 96 { 97 regmap_set_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 98 VSDC_CURSOR_CONFIG_COMMIT | 99 VSDC_CURSOR_CONFIG_IMG_UPDATE); 100 } 101 102 static void vs_cursor_plane_atomic_enable(struct drm_plane *plane, 103 struct drm_atomic_commit *atomic_state) 104 { 105 struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, 106 plane); 107 struct drm_crtc *crtc = state->crtc; 108 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 109 unsigned int output = vcrtc->id; 110 struct vs_dc *dc = vcrtc->dc; 111 112 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 113 VSDC_CURSOR_CONFIG_FMT_MASK, 114 VSDC_CURSOR_CONFIG_FMT_ARGB8888); 115 116 vs_cursor_plane_commit(dc, output); 117 } 118 119 static void vs_cursor_plane_atomic_disable(struct drm_plane *plane, 120 struct drm_atomic_commit *atomic_state) 121 { 122 struct drm_plane_state *state = drm_atomic_get_old_plane_state(atomic_state, 123 plane); 124 struct drm_crtc *crtc = state->crtc; 125 struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); 126 unsigned int output = vcrtc->id; 127 struct vs_dc *dc = vcrtc->dc; 128 129 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 130 VSDC_CURSOR_CONFIG_FMT_MASK, 131 VSDC_CURSOR_CONFIG_FMT_OFF); 132 133 vs_cursor_plane_commit(dc, output); 134 } 135 136 static void vs_cursor_plane_atomic_update(struct drm_plane *plane, 137 struct drm_atomic_commit *atomic_state) 138 { 139 struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, 140 plane); 141 struct drm_framebuffer *fb = state->fb; 142 struct drm_crtc *crtc = state->crtc; 143 struct vs_dc *dc; 144 struct vs_crtc *vcrtc; 145 unsigned int output; 146 dma_addr_t dma_addr; 147 148 if (!state->visible) { 149 vs_cursor_plane_atomic_disable(plane, atomic_state); 150 return; 151 } 152 153 vcrtc = drm_crtc_to_vs_crtc(crtc); 154 output = vcrtc->id; 155 dc = vcrtc->dc; 156 157 /* Other sizes should be rejected by atomic_check */ 158 switch (state->crtc_w) { 159 case 32: 160 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 161 VSDC_CURSOR_CONFIG_SIZE_MASK, 162 VSDC_CURSOR_CONFIG_SIZE_32); 163 break; 164 case 64: 165 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 166 VSDC_CURSOR_CONFIG_SIZE_MASK, 167 VSDC_CURSOR_CONFIG_SIZE_64); 168 break; 169 case 128: 170 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 171 VSDC_CURSOR_CONFIG_SIZE_MASK, 172 VSDC_CURSOR_CONFIG_SIZE_128); 173 break; 174 case 256: 175 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 176 VSDC_CURSOR_CONFIG_SIZE_MASK, 177 VSDC_CURSOR_CONFIG_SIZE_256); 178 break; 179 } 180 181 /* hardware handles clipping as seen below */ 182 dma_addr = drm_fb_dma_get_gem_addr(fb, state, 0); 183 184 regmap_write(dc->regs, VSDC_CURSOR_ADDRESS(output), 185 lower_32_bits(dma_addr)); 186 187 /* 188 * The X_OFF and Y_OFF fields define which point does the LOCATION 189 * register represent in the cursor image, and LOCATION register 190 * values are unsigned. To for positive left-top coordinates the 191 * offset is set to 0 and the location is set to the coordinate, for 192 * negative coordinates the location is set to 0 and the offset 193 * is set to the opposite number of the coordinate to offset the 194 * cursor image partly off-screen. 195 */ 196 if (state->crtc_x >= 0) { 197 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 198 VSDC_CURSOR_CONFIG_X_OFF_MASK, 0); 199 regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), 200 VSDC_CURSOR_LOCATION_X_MASK, 201 VSDC_CURSOR_LOCATION_X(state->crtc_x)); 202 } else { 203 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 204 VSDC_CURSOR_CONFIG_X_OFF_MASK, 205 VSDC_CURSOR_CONFIG_X_OFF(-state->crtc_x)); 206 regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), 207 VSDC_CURSOR_LOCATION_X_MASK, 0); 208 } 209 210 if (state->crtc_y >= 0) { 211 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 212 VSDC_CURSOR_CONFIG_Y_OFF_MASK, 0); 213 regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), 214 VSDC_CURSOR_LOCATION_Y_MASK, 215 VSDC_CURSOR_LOCATION_Y(state->crtc_y)); 216 } else { 217 regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), 218 VSDC_CURSOR_CONFIG_Y_OFF_MASK, 219 VSDC_CURSOR_CONFIG_Y_OFF(-state->crtc_y)); 220 regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), 221 VSDC_CURSOR_LOCATION_Y_MASK, 0); 222 } 223 224 vs_cursor_plane_commit(dc, output); 225 } 226 227 static const struct drm_plane_helper_funcs vs_cursor_plane_helper_funcs = { 228 .atomic_check = vs_cursor_plane_atomic_check, 229 .atomic_update = vs_cursor_plane_atomic_update, 230 .atomic_enable = vs_cursor_plane_atomic_enable, 231 .atomic_disable = vs_cursor_plane_atomic_disable, 232 }; 233 234 static const struct drm_plane_funcs vs_cursor_plane_funcs = { 235 .atomic_create_state = drm_atomic_helper_plane_create_state, 236 .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, 237 .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, 238 .disable_plane = drm_atomic_helper_disable_plane, 239 .update_plane = drm_atomic_helper_update_plane, 240 }; 241 242 static const u32 vs_cursor_plane_formats[] = { 243 DRM_FORMAT_ARGB8888, 244 }; 245 246 static const u64 vs_cursor_plane_modifiers[] = { 247 DRM_FORMAT_MOD_LINEAR, 248 DRM_FORMAT_MOD_INVALID, /* sentinel */ 249 }; 250 251 struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, 252 struct vs_dc *dc) 253 { 254 int32_t max_cursor_size = dc->identity.max_cursor_size; 255 struct drm_plane *plane; 256 257 if (drm_WARN_ON_ONCE(drm_dev, max_cursor_size < VSDC_MIN_CURSOR_SIZE || 258 max_cursor_size > VSDC_MAX_CURSOR_SIZE)) 259 return ERR_PTR(-EINVAL); 260 261 plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, 262 &vs_cursor_plane_funcs, 263 vs_cursor_plane_formats, 264 ARRAY_SIZE(vs_cursor_plane_formats), 265 vs_cursor_plane_modifiers, 266 DRM_PLANE_TYPE_CURSOR, 267 NULL); 268 269 if (IS_ERR(plane)) 270 return plane; 271 272 drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); 273 drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); 274 275 return plane; 276 } 277