1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. 4 * Author: James.Qian.Wang <james.qian.wang@arm.com> 5 * 6 */ 7 #include <linux/overflow.h> 8 9 #include <drm/drm_device.h> 10 #include <drm/drm_fb_dma_helper.h> 11 #include <drm/drm_gem.h> 12 #include <drm/drm_gem_dma_helper.h> 13 #include <drm/drm_gem_framebuffer_helper.h> 14 #include <drm/drm_print.h> 15 16 #include "komeda_framebuffer.h" 17 #include "komeda_dev.h" 18 19 static void komeda_fb_destroy(struct drm_framebuffer *fb) 20 { 21 struct komeda_fb *kfb = to_kfb(fb); 22 u32 i; 23 24 for (i = 0; i < fb->format->num_planes; i++) 25 drm_gem_object_put(fb->obj[i]); 26 27 drm_framebuffer_cleanup(fb); 28 kfree(kfb); 29 } 30 31 static int komeda_fb_create_handle(struct drm_framebuffer *fb, 32 struct drm_file *file, u32 *handle) 33 { 34 return drm_gem_handle_create(file, fb->obj[0], handle); 35 } 36 37 static const struct drm_framebuffer_funcs komeda_fb_funcs = { 38 .destroy = komeda_fb_destroy, 39 .create_handle = komeda_fb_create_handle, 40 }; 41 42 static int 43 komeda_fb_afbc_size_check(struct komeda_fb *kfb, struct drm_file *file, 44 const struct drm_mode_fb_cmd2 *mode_cmd) 45 { 46 struct drm_framebuffer *fb = &kfb->base; 47 const struct drm_format_info *info = fb->format; 48 struct drm_gem_object *obj; 49 u32 alignment_w = 0, alignment_h = 0, alignment_header, n_blocks, bpp; 50 u64 min_size; 51 52 obj = drm_gem_object_lookup(file, mode_cmd->handles[0]); 53 if (!obj) { 54 DRM_DEBUG_KMS("Failed to lookup GEM object\n"); 55 return -ENOENT; 56 } 57 58 switch (fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) { 59 case AFBC_FORMAT_MOD_BLOCK_SIZE_32x8: 60 alignment_w = 32; 61 alignment_h = 8; 62 break; 63 case AFBC_FORMAT_MOD_BLOCK_SIZE_16x16: 64 alignment_w = 16; 65 alignment_h = 16; 66 break; 67 default: 68 WARN(1, "Invalid AFBC_FORMAT_MOD_BLOCK_SIZE: %lld.\n", 69 fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK); 70 break; 71 } 72 73 /* tiled header afbc */ 74 if (fb->modifier & AFBC_FORMAT_MOD_TILED) { 75 alignment_w *= AFBC_TH_LAYOUT_ALIGNMENT; 76 alignment_h *= AFBC_TH_LAYOUT_ALIGNMENT; 77 alignment_header = AFBC_TH_BODY_START_ALIGNMENT; 78 } else { 79 alignment_header = AFBC_BODY_START_ALIGNMENT; 80 } 81 82 kfb->aligned_w = ALIGN(fb->width, alignment_w); 83 kfb->aligned_h = ALIGN(fb->height, alignment_h); 84 85 if (fb->offsets[0] % alignment_header) { 86 DRM_DEBUG_KMS("afbc offset alignment check failed.\n"); 87 goto check_failed; 88 } 89 90 n_blocks = (kfb->aligned_w * kfb->aligned_h) / AFBC_SUPERBLK_PIXELS; 91 kfb->offset_payload = ALIGN(n_blocks * AFBC_HEADER_SIZE, 92 alignment_header); 93 94 bpp = komeda_get_afbc_format_bpp(info, fb->modifier); 95 kfb->afbc_size = kfb->offset_payload + n_blocks * 96 ALIGN(bpp * AFBC_SUPERBLK_PIXELS / 8, 97 AFBC_SUPERBLK_ALIGNMENT); 98 if (check_add_overflow(kfb->afbc_size, fb->offsets[0], &min_size)) { 99 goto check_failed; 100 } 101 if (min_size > obj->size) { 102 DRM_DEBUG_KMS("afbc size check failed, obj_size: 0x%zx. min_size 0x%llx.\n", 103 obj->size, min_size); 104 goto check_failed; 105 } 106 107 fb->obj[0] = obj; 108 return 0; 109 110 check_failed: 111 drm_gem_object_put(obj); 112 return -EINVAL; 113 } 114 115 static int 116 komeda_fb_none_afbc_size_check(struct komeda_dev *mdev, struct komeda_fb *kfb, 117 struct drm_file *file, 118 const struct drm_mode_fb_cmd2 *mode_cmd) 119 { 120 struct drm_framebuffer *fb = &kfb->base; 121 const struct drm_format_info *info = fb->format; 122 struct drm_gem_object *obj; 123 u32 i, block_h; 124 u64 min_size; 125 126 if (komeda_fb_check_src_coords(kfb, 0, 0, fb->width, fb->height)) 127 return -EINVAL; 128 129 for (i = 0; i < info->num_planes; i++) { 130 obj = drm_gem_object_lookup(file, mode_cmd->handles[i]); 131 if (!obj) { 132 DRM_DEBUG_KMS("Failed to lookup GEM object\n"); 133 return -ENOENT; 134 } 135 fb->obj[i] = obj; 136 137 block_h = drm_format_info_block_height(info, i); 138 if ((fb->pitches[i] * block_h) % mdev->chip.bus_width) { 139 DRM_DEBUG_KMS("Pitch[%d]: 0x%x doesn't align to 0x%x\n", 140 i, fb->pitches[i], mdev->chip.bus_width); 141 return -EINVAL; 142 } 143 144 min_size = komeda_fb_get_pixel_addr(kfb, 0, fb->height, i) 145 - to_drm_gem_dma_obj(obj)->dma_addr; 146 if (obj->size < min_size) { 147 DRM_DEBUG_KMS("The fb->obj[%d] size: 0x%zx lower than the minimum requirement: 0x%llx.\n", 148 i, obj->size, min_size); 149 return -EINVAL; 150 } 151 } 152 153 if (fb->format->num_planes == 3) { 154 if (fb->pitches[1] != fb->pitches[2]) { 155 DRM_DEBUG_KMS("The pitch[1] and [2] are not same\n"); 156 return -EINVAL; 157 } 158 } 159 160 return 0; 161 } 162 163 struct drm_framebuffer * 164 komeda_fb_create(struct drm_device *dev, struct drm_file *file, 165 const struct drm_format_info *info, 166 const struct drm_mode_fb_cmd2 *mode_cmd) 167 { 168 struct komeda_dev *mdev = dev->dev_private; 169 struct komeda_fb *kfb; 170 int ret = 0, i; 171 172 kfb = kzalloc_obj(*kfb); 173 if (!kfb) 174 return ERR_PTR(-ENOMEM); 175 176 kfb->format_caps = komeda_get_format_caps(&mdev->fmt_tbl, 177 mode_cmd->pixel_format, 178 mode_cmd->modifier[0]); 179 if (!kfb->format_caps) { 180 DRM_DEBUG_KMS("FMT %x is not supported.\n", 181 mode_cmd->pixel_format); 182 kfree(kfb); 183 return ERR_PTR(-EINVAL); 184 } 185 186 drm_helper_mode_fill_fb_struct(dev, &kfb->base, info, mode_cmd); 187 188 if (kfb->base.modifier) 189 ret = komeda_fb_afbc_size_check(kfb, file, mode_cmd); 190 else 191 ret = komeda_fb_none_afbc_size_check(mdev, kfb, file, mode_cmd); 192 if (ret < 0) 193 goto err_cleanup; 194 195 ret = drm_framebuffer_init(dev, &kfb->base, &komeda_fb_funcs); 196 if (ret < 0) { 197 DRM_DEBUG_KMS("failed to initialize fb\n"); 198 199 goto err_cleanup; 200 } 201 202 kfb->is_va = mdev->iommu ? true : false; 203 204 return &kfb->base; 205 206 err_cleanup: 207 for (i = 0; i < kfb->base.format->num_planes; i++) 208 drm_gem_object_put(kfb->base.obj[i]); 209 210 kfree(kfb); 211 return ERR_PTR(ret); 212 } 213 214 int komeda_fb_check_src_coords(const struct komeda_fb *kfb, 215 u32 src_x, u32 src_y, u32 src_w, u32 src_h) 216 { 217 const struct drm_framebuffer *fb = &kfb->base; 218 const struct drm_format_info *info = fb->format; 219 u32 block_w = drm_format_info_block_width(fb->format, 0); 220 u32 block_h = drm_format_info_block_height(fb->format, 0); 221 222 if ((src_x + src_w > fb->width) || (src_y + src_h > fb->height)) { 223 DRM_DEBUG_ATOMIC("Invalid source coordinate.\n"); 224 return -EINVAL; 225 } 226 227 if ((src_x % info->hsub) || (src_w % info->hsub) || 228 (src_y % info->vsub) || (src_h % info->vsub)) { 229 DRM_DEBUG_ATOMIC("Wrong subsampling dimension x:%d, y:%d, w:%d, h:%d for format: %x.\n", 230 src_x, src_y, src_w, src_h, info->format); 231 return -EINVAL; 232 } 233 234 if ((src_x % block_w) || (src_w % block_w) || 235 (src_y % block_h) || (src_h % block_h)) { 236 DRM_DEBUG_ATOMIC("x:%d, y:%d, w:%d, h:%d should be multiple of block_w/h for format: %x.\n", 237 src_x, src_y, src_w, src_h, info->format); 238 return -EINVAL; 239 } 240 241 return 0; 242 } 243 244 dma_addr_t 245 komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane) 246 { 247 struct drm_framebuffer *fb = &kfb->base; 248 const struct drm_gem_dma_object *obj; 249 u32 offset, plane_x, plane_y, block_w, block_sz; 250 251 if (plane >= fb->format->num_planes) { 252 DRM_DEBUG_KMS("Out of max plane num.\n"); 253 return -EINVAL; 254 } 255 256 obj = drm_fb_dma_get_gem_obj(fb, plane); 257 258 offset = fb->offsets[plane]; 259 if (!fb->modifier) { 260 block_w = drm_format_info_block_width(fb->format, plane); 261 block_sz = fb->format->char_per_block[plane]; 262 plane_x = x / (plane ? fb->format->hsub : 1); 263 plane_y = y / (plane ? fb->format->vsub : 1); 264 265 offset += (plane_x / block_w) * block_sz 266 + plane_y * fb->pitches[plane]; 267 } 268 269 return obj->dma_addr + offset; 270 } 271 272 /* if the fb can be supported by a specific layer */ 273 bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type, 274 u32 rot) 275 { 276 struct drm_framebuffer *fb = &kfb->base; 277 struct komeda_dev *mdev = fb->dev->dev_private; 278 u32 fourcc = fb->format->format; 279 u64 modifier = fb->modifier; 280 bool supported; 281 282 supported = komeda_format_mod_supported(&mdev->fmt_tbl, layer_type, 283 fourcc, modifier, rot); 284 if (!supported) 285 DRM_DEBUG_ATOMIC("Layer TYPE: %d doesn't support fb FMT: %p4cc with modifier: 0x%llx.\n", 286 layer_type, &fourcc, modifier); 287 288 return supported; 289 } 290