// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS) * * Authors: * Icenowy Zheng */ #include #include #include #include #include #include #include #include #include #include #include #include "vs_crtc.h" #include "vs_plane.h" #include "vs_dc.h" #include "vs_hwdb.h" #include "vs_cursor_plane_regs.h" #define VSDC_MIN_CURSOR_SIZE 32 #define VSDC_MAX_CURSOR_SIZE 256 #define VSDC_CURSOR_LOCATION_MAX_POSITIVE BIT_MASK(15) #define VSDC_CURSOR_LOCATION_MAX_NEGATIVE BIT_MASK(5) static bool vs_cursor_plane_check_coord(int32_t coord) { if (coord >= 0) return coord <= VSDC_CURSOR_LOCATION_MAX_POSITIVE; else return (-coord) <= VSDC_CURSOR_LOCATION_MAX_NEGATIVE; } static int vs_cursor_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state) { struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); struct drm_crtc *crtc = new_plane_state->crtc; struct drm_framebuffer *fb = new_plane_state->fb; struct drm_crtc_state *crtc_state = NULL; struct vs_crtc *vcrtc; struct vs_dc *dc; int ret; if (crtc) crtc_state = drm_atomic_get_new_crtc_state(state, crtc); ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING, true, true); if (ret) return ret; if (!new_plane_state->visible) return 0; /* Skip validity check */ vcrtc = drm_crtc_to_vs_crtc(crtc); dc = vcrtc->dc; /* Only certain PoT square sizes is supported. */ if (!is_power_of_2(new_plane_state->crtc_w) || new_plane_state->crtc_w < VSDC_MIN_CURSOR_SIZE || new_plane_state->crtc_w > dc->identity.max_cursor_size) return -EINVAL; if (new_plane_state->crtc_w != new_plane_state->crtc_h) return -EINVAL; /* Check if the cursor is inside the register fields' range */ if (!vs_cursor_plane_check_coord(new_plane_state->crtc_x) || !vs_cursor_plane_check_coord(new_plane_state->crtc_y)) return -EINVAL; /* Extra line padding isn't supported */ if (fb->pitches[0] != drm_format_info_min_pitch(fb->format, 0, new_plane_state->crtc_w)) return -EINVAL; return 0; } static void vs_cursor_plane_commit(struct vs_dc *dc, unsigned int output) { regmap_set_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_COMMIT | VSDC_CURSOR_CONFIG_IMG_UPDATE); } static void vs_cursor_plane_atomic_enable(struct drm_plane *plane, struct drm_atomic_commit *atomic_state) { struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, plane); struct drm_crtc *crtc = state->crtc; struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); unsigned int output = vcrtc->id; struct vs_dc *dc = vcrtc->dc; regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_FMT_MASK, VSDC_CURSOR_CONFIG_FMT_ARGB8888); vs_cursor_plane_commit(dc, output); } static void vs_cursor_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_commit *atomic_state) { struct drm_plane_state *state = drm_atomic_get_old_plane_state(atomic_state, plane); struct drm_crtc *crtc = state->crtc; struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); unsigned int output = vcrtc->id; struct vs_dc *dc = vcrtc->dc; regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_FMT_MASK, VSDC_CURSOR_CONFIG_FMT_OFF); vs_cursor_plane_commit(dc, output); } static void vs_cursor_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *atomic_state) { struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, plane); struct drm_framebuffer *fb = state->fb; struct drm_crtc *crtc = state->crtc; struct vs_dc *dc; struct vs_crtc *vcrtc; unsigned int output; dma_addr_t dma_addr; if (!state->visible) { vs_cursor_plane_atomic_disable(plane, atomic_state); return; } vcrtc = drm_crtc_to_vs_crtc(crtc); output = vcrtc->id; dc = vcrtc->dc; /* Other sizes should be rejected by atomic_check */ switch (state->crtc_w) { case 32: regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_SIZE_MASK, VSDC_CURSOR_CONFIG_SIZE_32); break; case 64: regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_SIZE_MASK, VSDC_CURSOR_CONFIG_SIZE_64); break; case 128: regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_SIZE_MASK, VSDC_CURSOR_CONFIG_SIZE_128); break; case 256: regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_SIZE_MASK, VSDC_CURSOR_CONFIG_SIZE_256); break; } dma_addr = vs_fb_get_dma_addr(fb, &state->src); regmap_write(dc->regs, VSDC_CURSOR_ADDRESS(output), lower_32_bits(dma_addr)); /* * The X_OFF and Y_OFF fields define which point does the LOCATION * register represent in the cursor image, and LOCATION register * values are unsigned. To for positive left-top coordinates the * offset is set to 0 and the location is set to the coordinate, for * negative coordinates the location is set to 0 and the offset * is set to the opposite number of the coordinate to offset the * cursor image partly off-screen. */ if (state->crtc_x >= 0) { regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_X_OFF_MASK, 0); regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), VSDC_CURSOR_LOCATION_X_MASK, VSDC_CURSOR_LOCATION_X(state->crtc_x)); } else { regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_X_OFF_MASK, -state->crtc_x); regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), VSDC_CURSOR_LOCATION_X_MASK, 0); } if (state->crtc_y >= 0) { regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_Y_OFF_MASK, 0); regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), VSDC_CURSOR_LOCATION_Y_MASK, VSDC_CURSOR_LOCATION_Y(state->crtc_y)); } else { regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_Y_OFF_MASK, -state->crtc_y); regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), VSDC_CURSOR_LOCATION_Y_MASK, 0); } vs_cursor_plane_commit(dc, output); } static const struct drm_plane_helper_funcs vs_cursor_plane_helper_funcs = { .atomic_check = vs_cursor_plane_atomic_check, .atomic_update = vs_cursor_plane_atomic_update, .atomic_enable = vs_cursor_plane_atomic_enable, .atomic_disable = vs_cursor_plane_atomic_disable, }; static const struct drm_plane_funcs vs_cursor_plane_funcs = { .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .disable_plane = drm_atomic_helper_disable_plane, .reset = drm_atomic_helper_plane_reset, .update_plane = drm_atomic_helper_update_plane, }; static const u32 vs_cursor_plane_formats[] = { DRM_FORMAT_ARGB8888, }; static const u64 vs_cursor_plane_modifiers[] = { DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID, /* sentinel */ }; struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, struct vs_dc *dc) { int32_t max_cursor_size = dc->identity.max_cursor_size; struct drm_plane *plane; if (drm_WARN_ON_ONCE(drm_dev, max_cursor_size < VSDC_MIN_CURSOR_SIZE || max_cursor_size > VSDC_MAX_CURSOR_SIZE)) return ERR_PTR(-EINVAL); plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, &vs_cursor_plane_funcs, vs_cursor_plane_formats, ARRAY_SIZE(vs_cursor_plane_formats), vs_cursor_plane_modifiers, DRM_PLANE_TYPE_CURSOR, NULL); if (IS_ERR(plane)) return plane; drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); return plane; }