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