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
vs_cursor_plane_check_coord(int32_t coord)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
vs_cursor_plane_atomic_check(struct drm_plane * plane,struct drm_atomic_commit * state)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
vs_cursor_plane_commit(struct vs_dc * dc,unsigned int output)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
vs_cursor_plane_atomic_enable(struct drm_plane * plane,struct drm_atomic_commit * atomic_state)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
vs_cursor_plane_atomic_disable(struct drm_plane * plane,struct drm_atomic_commit * atomic_state)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
vs_cursor_plane_atomic_update(struct drm_plane * plane,struct drm_atomic_commit * atomic_state)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
vs_cursor_plane_init(struct drm_device * drm_dev,struct vs_dc * dc)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