|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +/* |
| 3 | + * Permission is hereby granted, free of charge, to any person obtaining a |
| 4 | + * copy of this software and associated documentation files (the |
| 5 | + * "Software"), to deal in the Software without restriction, including |
| 6 | + * without limitation the rights to use, copy, modify, merge, publish, |
| 7 | + * distribute, sub license, and/or sell copies of the Software, and to |
| 8 | + * permit persons to whom the Software is furnished to do so, subject to |
| 9 | + * the following conditions: |
| 10 | + * |
| 11 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 12 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
| 14 | + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, |
| 15 | + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| 16 | + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| 17 | + * USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 18 | + * |
| 19 | + * The above copyright notice and this permission notice (including the |
| 20 | + * next paragraph) shall be included in all copies or substantial portions |
| 21 | + * of the Software. |
| 22 | + */ |
| 23 | + |
| 24 | +#include <linux/bits.h> |
| 25 | +#include <linux/sizes.h> |
| 26 | + |
| 27 | +#include <drm/drm_atomic.h> |
| 28 | +#include <drm/drm_damage_helper.h> |
| 29 | +#include <drm/drm_format_helper.h> |
| 30 | +#include <drm/drm_gem_atomic_helper.h> |
| 31 | +#include <drm/drm_print.h> |
| 32 | + |
| 33 | +#include "ast_drv.h" |
| 34 | + |
| 35 | +/* |
| 36 | + * Hardware cursor |
| 37 | + */ |
| 38 | + |
| 39 | +/* define for signature structure */ |
| 40 | +#define AST_HWC_SIGNATURE_CHECKSUM 0x00 |
| 41 | +#define AST_HWC_SIGNATURE_SizeX 0x04 |
| 42 | +#define AST_HWC_SIGNATURE_SizeY 0x08 |
| 43 | +#define AST_HWC_SIGNATURE_X 0x0C |
| 44 | +#define AST_HWC_SIGNATURE_Y 0x10 |
| 45 | +#define AST_HWC_SIGNATURE_HOTSPOTX 0x14 |
| 46 | +#define AST_HWC_SIGNATURE_HOTSPOTY 0x18 |
| 47 | + |
| 48 | +static u32 ast_cursor_calculate_checksum(const void *src, unsigned int width, unsigned int height) |
| 49 | +{ |
| 50 | + u32 csum = 0; |
| 51 | + unsigned int one_pixel_copy = width & BIT(0); |
| 52 | + unsigned int two_pixel_copy = width - one_pixel_copy; |
| 53 | + unsigned int trailing_bytes = (AST_MAX_HWC_WIDTH - width) * sizeof(u16); |
| 54 | + unsigned int x, y; |
| 55 | + |
| 56 | + for (y = 0; y < height; y++) { |
| 57 | + for (x = 0; x < two_pixel_copy; x += 2) { |
| 58 | + const u32 *src32 = (const u32 *)src; |
| 59 | + |
| 60 | + csum += *src32; |
| 61 | + src += SZ_4; |
| 62 | + } |
| 63 | + if (one_pixel_copy) { |
| 64 | + const u16 *src16 = (const u16 *)src; |
| 65 | + |
| 66 | + csum += *src16; |
| 67 | + src += SZ_2; |
| 68 | + } |
| 69 | + src += trailing_bytes; |
| 70 | + } |
| 71 | + |
| 72 | + return csum; |
| 73 | +} |
| 74 | + |
| 75 | +static void ast_set_cursor_image(struct ast_device *ast, const u8 *src, |
| 76 | + unsigned int width, unsigned int height) |
| 77 | +{ |
| 78 | + u8 __iomem *dst = ast->cursor_plane.base.vaddr; |
| 79 | + u32 csum; |
| 80 | + |
| 81 | + csum = ast_cursor_calculate_checksum(src, width, height); |
| 82 | + |
| 83 | + /* write pixel data */ |
| 84 | + memcpy_toio(dst, src, AST_HWC_SIZE); |
| 85 | + |
| 86 | + /* write checksum + signature */ |
| 87 | + dst += AST_HWC_SIZE; |
| 88 | + writel(csum, dst); |
| 89 | + writel(width, dst + AST_HWC_SIGNATURE_SizeX); |
| 90 | + writel(height, dst + AST_HWC_SIGNATURE_SizeY); |
| 91 | + writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTX); |
| 92 | + writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTY); |
| 93 | +} |
| 94 | + |
| 95 | +static void ast_set_cursor_base(struct ast_device *ast, u64 address) |
| 96 | +{ |
| 97 | + u8 addr0 = (address >> 3) & 0xff; |
| 98 | + u8 addr1 = (address >> 11) & 0xff; |
| 99 | + u8 addr2 = (address >> 19) & 0xff; |
| 100 | + |
| 101 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc8, addr0); |
| 102 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc9, addr1); |
| 103 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xca, addr2); |
| 104 | +} |
| 105 | + |
| 106 | +static void ast_set_cursor_location(struct ast_device *ast, u16 x, u16 y, |
| 107 | + u8 x_offset, u8 y_offset) |
| 108 | +{ |
| 109 | + u8 x0 = (x & 0x00ff); |
| 110 | + u8 x1 = (x & 0x0f00) >> 8; |
| 111 | + u8 y0 = (y & 0x00ff); |
| 112 | + u8 y1 = (y & 0x0700) >> 8; |
| 113 | + |
| 114 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc2, x_offset); |
| 115 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc3, y_offset); |
| 116 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc4, x0); |
| 117 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc5, x1); |
| 118 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc6, y0); |
| 119 | + ast_set_index_reg(ast, AST_IO_VGACRI, 0xc7, y1); |
| 120 | +} |
| 121 | + |
| 122 | +static void ast_set_cursor_enabled(struct ast_device *ast, bool enabled) |
| 123 | +{ |
| 124 | + static const u8 mask = (u8)~(AST_IO_VGACRCB_HWC_16BPP | |
| 125 | + AST_IO_VGACRCB_HWC_ENABLED); |
| 126 | + |
| 127 | + u8 vgacrcb = AST_IO_VGACRCB_HWC_16BPP; |
| 128 | + |
| 129 | + if (enabled) |
| 130 | + vgacrcb |= AST_IO_VGACRCB_HWC_ENABLED; |
| 131 | + |
| 132 | + ast_set_index_reg_mask(ast, AST_IO_VGACRI, 0xcb, mask, vgacrcb); |
| 133 | +} |
| 134 | + |
| 135 | +/* |
| 136 | + * Cursor plane |
| 137 | + */ |
| 138 | + |
| 139 | +static const uint32_t ast_cursor_plane_formats[] = { |
| 140 | + DRM_FORMAT_ARGB4444, |
| 141 | + DRM_FORMAT_ARGB8888, |
| 142 | +}; |
| 143 | + |
| 144 | +static int ast_cursor_plane_helper_atomic_check(struct drm_plane *plane, |
| 145 | + struct drm_atomic_state *state) |
| 146 | +{ |
| 147 | + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); |
| 148 | + struct drm_framebuffer *new_fb = new_plane_state->fb; |
| 149 | + struct drm_crtc_state *new_crtc_state = NULL; |
| 150 | + int ret; |
| 151 | + |
| 152 | + if (new_plane_state->crtc) |
| 153 | + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_plane_state->crtc); |
| 154 | + |
| 155 | + ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, |
| 156 | + DRM_PLANE_NO_SCALING, |
| 157 | + DRM_PLANE_NO_SCALING, |
| 158 | + true, true); |
| 159 | + if (ret || !new_plane_state->visible) |
| 160 | + return ret; |
| 161 | + |
| 162 | + if (new_fb->width > AST_MAX_HWC_WIDTH || new_fb->height > AST_MAX_HWC_HEIGHT) |
| 163 | + return -EINVAL; |
| 164 | + |
| 165 | + return 0; |
| 166 | +} |
| 167 | + |
| 168 | +static void ast_cursor_plane_helper_atomic_update(struct drm_plane *plane, |
| 169 | + struct drm_atomic_state *state) |
| 170 | +{ |
| 171 | + struct ast_cursor_plane *ast_cursor_plane = to_ast_cursor_plane(plane); |
| 172 | + struct ast_plane *ast_plane = to_ast_plane(plane); |
| 173 | + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); |
| 174 | + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); |
| 175 | + struct drm_framebuffer *fb = plane_state->fb; |
| 176 | + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); |
| 177 | + struct ast_device *ast = to_ast_device(plane->dev); |
| 178 | + struct drm_rect damage; |
| 179 | + u64 dst_off = ast_plane->offset; |
| 180 | + u8 __iomem *dst = ast_plane->vaddr; /* TODO: Use mapping abstraction properly */ |
| 181 | + u8 __iomem *sig = dst + AST_HWC_SIZE; /* TODO: Use mapping abstraction properly */ |
| 182 | + unsigned int offset_x, offset_y; |
| 183 | + u16 x, y; |
| 184 | + u8 x_offset, y_offset; |
| 185 | + |
| 186 | + /* |
| 187 | + * Do data transfer to hardware buffer and point the scanout |
| 188 | + * engine to the offset. |
| 189 | + */ |
| 190 | + |
| 191 | + if (drm_atomic_helper_damage_merged(old_plane_state, plane_state, &damage)) { |
| 192 | + u8 *argb4444; |
| 193 | + |
| 194 | + switch (fb->format->format) { |
| 195 | + case DRM_FORMAT_ARGB4444: |
| 196 | + argb4444 = shadow_plane_state->data[0].vaddr; |
| 197 | + break; |
| 198 | + default: |
| 199 | + argb4444 = ast_cursor_plane->argb4444; |
| 200 | + { |
| 201 | + struct iosys_map argb4444_dst[DRM_FORMAT_MAX_PLANES] = { |
| 202 | + IOSYS_MAP_INIT_VADDR(argb4444), |
| 203 | + }; |
| 204 | + unsigned int argb4444_dst_pitch[DRM_FORMAT_MAX_PLANES] = { |
| 205 | + AST_HWC_PITCH, |
| 206 | + }; |
| 207 | + |
| 208 | + drm_fb_argb8888_to_argb4444(argb4444_dst, argb4444_dst_pitch, |
| 209 | + shadow_plane_state->data, fb, &damage, |
| 210 | + &shadow_plane_state->fmtcnv_state); |
| 211 | + } |
| 212 | + break; |
| 213 | + } |
| 214 | + ast_set_cursor_image(ast, argb4444, fb->width, fb->height); |
| 215 | + ast_set_cursor_base(ast, dst_off); |
| 216 | + } |
| 217 | + |
| 218 | + /* |
| 219 | + * Update location in HWC signature and registers. |
| 220 | + */ |
| 221 | + |
| 222 | + writel(plane_state->crtc_x, sig + AST_HWC_SIGNATURE_X); |
| 223 | + writel(plane_state->crtc_y, sig + AST_HWC_SIGNATURE_Y); |
| 224 | + |
| 225 | + offset_x = AST_MAX_HWC_WIDTH - fb->width; |
| 226 | + offset_y = AST_MAX_HWC_HEIGHT - fb->height; |
| 227 | + |
| 228 | + if (plane_state->crtc_x < 0) { |
| 229 | + x_offset = (-plane_state->crtc_x) + offset_x; |
| 230 | + x = 0; |
| 231 | + } else { |
| 232 | + x_offset = offset_x; |
| 233 | + x = plane_state->crtc_x; |
| 234 | + } |
| 235 | + if (plane_state->crtc_y < 0) { |
| 236 | + y_offset = (-plane_state->crtc_y) + offset_y; |
| 237 | + y = 0; |
| 238 | + } else { |
| 239 | + y_offset = offset_y; |
| 240 | + y = plane_state->crtc_y; |
| 241 | + } |
| 242 | + |
| 243 | + ast_set_cursor_location(ast, x, y, x_offset, y_offset); |
| 244 | + |
| 245 | + /* Dummy write to enable HWC and make the HW pick-up the changes. */ |
| 246 | + ast_set_cursor_enabled(ast, true); |
| 247 | +} |
| 248 | + |
| 249 | +static void ast_cursor_plane_helper_atomic_disable(struct drm_plane *plane, |
| 250 | + struct drm_atomic_state *state) |
| 251 | +{ |
| 252 | + struct ast_device *ast = to_ast_device(plane->dev); |
| 253 | + |
| 254 | + ast_set_cursor_enabled(ast, false); |
| 255 | +} |
| 256 | + |
| 257 | +static const struct drm_plane_helper_funcs ast_cursor_plane_helper_funcs = { |
| 258 | + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, |
| 259 | + .atomic_check = ast_cursor_plane_helper_atomic_check, |
| 260 | + .atomic_update = ast_cursor_plane_helper_atomic_update, |
| 261 | + .atomic_disable = ast_cursor_plane_helper_atomic_disable, |
| 262 | +}; |
| 263 | + |
| 264 | +static const struct drm_plane_funcs ast_cursor_plane_funcs = { |
| 265 | + .update_plane = drm_atomic_helper_update_plane, |
| 266 | + .disable_plane = drm_atomic_helper_disable_plane, |
| 267 | + .destroy = drm_plane_cleanup, |
| 268 | + DRM_GEM_SHADOW_PLANE_FUNCS, |
| 269 | +}; |
| 270 | + |
| 271 | +int ast_cursor_plane_init(struct ast_device *ast) |
| 272 | +{ |
| 273 | + struct drm_device *dev = &ast->base; |
| 274 | + struct ast_cursor_plane *ast_cursor_plane = &ast->cursor_plane; |
| 275 | + struct ast_plane *ast_plane = &ast_cursor_plane->base; |
| 276 | + struct drm_plane *cursor_plane = &ast_plane->base; |
| 277 | + size_t size; |
| 278 | + void __iomem *vaddr; |
| 279 | + u64 offset; |
| 280 | + int ret; |
| 281 | + |
| 282 | + /* |
| 283 | + * Allocate backing storage for cursors. The BOs are permanently |
| 284 | + * pinned to the top end of the VRAM. |
| 285 | + */ |
| 286 | + |
| 287 | + size = roundup(AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE, PAGE_SIZE); |
| 288 | + |
| 289 | + if (ast->vram_fb_available < size) |
| 290 | + return -ENOMEM; |
| 291 | + |
| 292 | + vaddr = ast->vram + ast->vram_fb_available - size; |
| 293 | + offset = ast->vram_fb_available - size; |
| 294 | + |
| 295 | + ret = ast_plane_init(dev, ast_plane, vaddr, offset, size, |
| 296 | + 0x01, &ast_cursor_plane_funcs, |
| 297 | + ast_cursor_plane_formats, ARRAY_SIZE(ast_cursor_plane_formats), |
| 298 | + NULL, DRM_PLANE_TYPE_CURSOR); |
| 299 | + if (ret) { |
| 300 | + drm_err(dev, "ast_plane_init() failed: %d\n", ret); |
| 301 | + return ret; |
| 302 | + } |
| 303 | + drm_plane_helper_add(cursor_plane, &ast_cursor_plane_helper_funcs); |
| 304 | + drm_plane_enable_fb_damage_clips(cursor_plane); |
| 305 | + |
| 306 | + ast->vram_fb_available -= size; |
| 307 | + |
| 308 | + return 0; |
| 309 | +} |
0 commit comments