| /* | |
| * Copyright (c) 2015 MediaTek Inc. | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| */ | |
| #include <drm/drmP.h> | |
| #include <drm/drm_crtc_helper.h> | |
| #include <drm/drm_fb_helper.h> | |
| #include <drm/drm_gem.h> | |
| #include <linux/dma-buf.h> | |
| #include <linux/reservation.h> | |
| #include "mtk_drm_drv.h" | |
| #include "mtk_drm_fb.h" | |
| #include "mtk_drm_gem.h" | |
| /* | |
| * mtk specific framebuffer structure. | |
| * | |
| * @fb: drm framebuffer object. | |
| * @gem_obj: array of gem objects. | |
| */ | |
| struct mtk_drm_fb { | |
| struct drm_framebuffer base; | |
| /* For now we only support a single plane */ | |
| struct drm_gem_object *gem_obj; | |
| }; | |
| #define to_mtk_fb(x) container_of(x, struct mtk_drm_fb, base) | |
| struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb) | |
| { | |
| struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); | |
| return mtk_fb->gem_obj; | |
| } | |
| static int mtk_drm_fb_create_handle(struct drm_framebuffer *fb, | |
| struct drm_file *file_priv, | |
| unsigned int *handle) | |
| { | |
| struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); | |
| return drm_gem_handle_create(file_priv, mtk_fb->gem_obj, handle); | |
| } | |
| static void mtk_drm_fb_destroy(struct drm_framebuffer *fb) | |
| { | |
| struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); | |
| drm_framebuffer_cleanup(fb); | |
| drm_gem_object_put_unlocked(mtk_fb->gem_obj); | |
| kfree(mtk_fb); | |
| } | |
| static const struct drm_framebuffer_funcs mtk_drm_fb_funcs = { | |
| .create_handle = mtk_drm_fb_create_handle, | |
| .destroy = mtk_drm_fb_destroy, | |
| }; | |
| static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev, | |
| const struct drm_mode_fb_cmd2 *mode, | |
| struct drm_gem_object *obj) | |
| { | |
| struct mtk_drm_fb *mtk_fb; | |
| int ret; | |
| if (drm_format_num_planes(mode->pixel_format) != 1) | |
| return ERR_PTR(-EINVAL); | |
| mtk_fb = kzalloc(sizeof(*mtk_fb), GFP_KERNEL); | |
| if (!mtk_fb) | |
| return ERR_PTR(-ENOMEM); | |
| drm_helper_mode_fill_fb_struct(dev, &mtk_fb->base, mode); | |
| mtk_fb->gem_obj = obj; | |
| ret = drm_framebuffer_init(dev, &mtk_fb->base, &mtk_drm_fb_funcs); | |
| if (ret) { | |
| DRM_ERROR("failed to initialize framebuffer\n"); | |
| kfree(mtk_fb); | |
| return ERR_PTR(ret); | |
| } | |
| return mtk_fb; | |
| } | |
| /* | |
| * Wait for any exclusive fence in fb's gem object's reservation object. | |
| * | |
| * Returns -ERESTARTSYS if interrupted, else 0. | |
| */ | |
| int mtk_fb_wait(struct drm_framebuffer *fb) | |
| { | |
| struct drm_gem_object *gem; | |
| struct reservation_object *resv; | |
| long ret; | |
| if (!fb) | |
| return 0; | |
| gem = mtk_fb_get_gem_obj(fb); | |
| if (!gem || !gem->dma_buf || !gem->dma_buf->resv) | |
| return 0; | |
| resv = gem->dma_buf->resv; | |
| ret = reservation_object_wait_timeout_rcu(resv, false, true, | |
| MAX_SCHEDULE_TIMEOUT); | |
| /* MAX_SCHEDULE_TIMEOUT on success, -ERESTARTSYS if interrupted */ | |
| if (WARN_ON(ret < 0)) | |
| return ret; | |
| return 0; | |
| } | |
| struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, | |
| struct drm_file *file, | |
| const struct drm_mode_fb_cmd2 *cmd) | |
| { | |
| struct mtk_drm_fb *mtk_fb; | |
| struct drm_gem_object *gem; | |
| unsigned int width = cmd->width; | |
| unsigned int height = cmd->height; | |
| unsigned int size, bpp; | |
| int ret; | |
| if (drm_format_num_planes(cmd->pixel_format) != 1) | |
| return ERR_PTR(-EINVAL); | |
| gem = drm_gem_object_lookup(file, cmd->handles[0]); | |
| if (!gem) | |
| return ERR_PTR(-ENOENT); | |
| bpp = drm_format_plane_cpp(cmd->pixel_format, 0); | |
| size = (height - 1) * cmd->pitches[0] + width * bpp; | |
| size += cmd->offsets[0]; | |
| if (gem->size < size) { | |
| ret = -EINVAL; | |
| goto unreference; | |
| } | |
| mtk_fb = mtk_drm_framebuffer_init(dev, cmd, gem); | |
| if (IS_ERR(mtk_fb)) { | |
| ret = PTR_ERR(mtk_fb); | |
| goto unreference; | |
| } | |
| return &mtk_fb->base; | |
| unreference: | |
| drm_gem_object_put_unlocked(gem); | |
| return ERR_PTR(ret); | |
| } |