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