/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include "mtk_drm_drv.h" #include "mtk_drm_fb.h" #include "mtk_drm_gem.h" #include "mtk_drm_fbdev.h" #include "mtk_drm_assert.h" #include "mtk_log.h" #include "mtk_drm_crtc.h" #include "mtk_drm_helper.h" #include "mtk_log.h" #include "mtk_drm_mmp.h" #include #define to_drm_private(x) container_of(x, struct mtk_drm_private, fb_helper) #define ALIGN_TO_32(x) ALIGN_TO(x, 32) struct fb_info *debug_info; static inline int try_use_fb_buf(struct drm_device *dev) { struct mtk_drm_private *priv = dev->dev_private; if (priv == NULL) { DDPPR_ERR("%s: priv is NULL\n", __func__); return 0; } return kref_get_unless_zero(&priv->kref_fb_buf); } unsigned int mtk_drm_fb_fm_auto_test(struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct drm_device *drm_dev = fb_helper->dev; struct drm_crtc *crtc; struct mtk_drm_crtc *mtk_crtc; struct mtk_drm_private *private; int ret = 0; /* this debug cmd only for crtc0 */ crtc = list_first_entry(&(drm_dev)->mode_config.crtc_list, typeof(*crtc), head); if (!crtc) { DDPPR_ERR("find crtc fail\n"); return -1; } mtk_crtc = to_mtk_crtc(crtc); DDP_MUTEX_LOCK(&mtk_crtc->lock, __func__, __LINE__); if (!mtk_crtc->enabled || mtk_crtc->ddp_mode == DDP_NO_USE) { DDPINFO("crtc 0 is already sleep, skip\n"); DDP_MUTEX_UNLOCK(&mtk_crtc->lock, __func__, __LINE__); return 0; } private = drm_dev->dev_private; if (mtk_drm_helper_get_opt(private->helper_opt, MTK_DRM_OPT_IDLE_MGR)) { mtk_drm_set_idlemgr(crtc, 0, 0); } ret = mtk_crtc_lcm_ATA(crtc); if (mtk_drm_helper_get_opt(private->helper_opt, MTK_DRM_OPT_IDLE_MGR)) mtk_drm_set_idlemgr(crtc, 1, 0); DDP_MUTEX_UNLOCK(&mtk_crtc->lock, __func__, __LINE__); if (ret == 0) DDPPR_ERR("ATA LCM failed\n"); else DDPPR_ERR("ATA LCM passed\n"); return ret; } static int mtk_drm_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { switch (cmd) { case MTKFB_FACTORY_AUTO_TEST: { unsigned int result = 0; void __user *argp = (void __user *)arg; DDPMSG("factory mode: lcm auto test\n"); result = mtk_drm_fb_fm_auto_test(info); return copy_to_user(argp, &result, sizeof(result)) ? -EFAULT : 0; } default: DDPINFO("%s: Not support:info=0x%p, cmd=0x%08x, arg=0x%08lx\n", __func__, info, (unsigned int)cmd, arg); break; } return 0; } static int mtk_drm_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct drm_device *drm_dev = fb_helper->dev; struct drm_crtc *crtc; struct mtk_drm_crtc *mtk_crtc; struct cmdq_pkt *cmdq_handle; int ret; ret = drm_fb_helper_pan_display(var, info); crtc = list_first_entry(&(drm_dev)->mode_config.crtc_list, typeof(*crtc), head); if (!crtc) { DDPPR_ERR("find crtc fail\n"); return ret; } mtk_crtc = to_mtk_crtc(crtc); DDP_MUTEX_LOCK(&mtk_crtc->lock, __func__, __LINE__); mtk_crtc_pkt_create(&cmdq_handle, &mtk_crtc->base, mtk_crtc->gce_obj.client[CLIENT_CFG]); if (mtk_crtc_with_sub_path(crtc, mtk_crtc->ddp_mode)) mtk_crtc_wait_frame_done(mtk_crtc, cmdq_handle, DDP_SECOND_PATH, 0); else mtk_crtc_wait_frame_done(mtk_crtc, cmdq_handle, DDP_FIRST_PATH, 0); cmdq_pkt_flush(cmdq_handle); cmdq_pkt_destroy(cmdq_handle); DDP_MUTEX_UNLOCK(&mtk_crtc->lock, __func__, __LINE__); return ret; } /* used when early porting, test pan display*/ void disp_get_fb_address(unsigned long *fbVirAddr) { *fbVirAddr = (unsigned long)debug_info->screen_base; pr_info( "%s fbdev->fb_va_base = 0x%p\n", __func__, debug_info->screen_base); } int pan_display_test(int frame_num, int bpp) { int i, j; int Bpp = bpp / 8; unsigned char *fb_va; unsigned int fb_size; int w, h, fb_h; int yoffset_max; int yoffset; debug_info->var.yoffset = 0; disp_get_fb_address((unsigned long *)&fb_va); if (!fb_va) return 0; if (!mtk_crtc_frame_buffer_existed()) return 0; fb_size = debug_info->fix.smem_len; w = debug_info->var.xres; h = debug_info->var.yres; fb_h = fb_size / (ALIGN_TO(w, 32) * Bpp) - 10; pr_info("%s: frame_num=%d,bpp=%d, w=%d,h=%d,fb_h=%d\n", __func__, frame_num, bpp, w, h, fb_h); for (i = 0; i < fb_h; i++) for (j = 0; j < w; j++) { int x = (i * ALIGN_TO(w, 32) + j) * Bpp; fb_va[x++] = (i + j) % 256; fb_va[x++] = (i + j) % 256; fb_va[x++] = (i + j) % 256; if (Bpp == 4) fb_va[x++] = 255; } debug_info->var.bits_per_pixel = bpp; yoffset_max = fb_h - h; yoffset = 0; for (i = 0; i < frame_num; i++, yoffset += 10) { if (yoffset >= yoffset_max) yoffset = 0; debug_info->var.xoffset = 0; debug_info->var.yoffset = yoffset; mtk_drm_fb_pan_display(&debug_info->var, debug_info); } DDPMSG("%s, %d--\n", __func__, __LINE__); return 0; } #define MTK_LEGACY_FB_MAP #ifndef MTK_LEGACY_FB_MAP static int mtk_drm_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct drm_fb_helper *helper = info->par; struct mtk_drm_private *private = helper->dev->dev_private; debug_info = info; return mtk_drm_gem_mmap_buf(private->fbdev_bo, vma); } #else static void mtk_drm_fbdev_vm_open(struct vm_area_struct *vma) { struct fb_info *info = vma->vm_file->private_data; struct drm_fb_helper *fb_helper = info->par; struct drm_device *drm_dev = fb_helper->dev; struct mtk_drm_private *priv = drm_dev->dev_private; if (priv == NULL) { DDPPR_ERR("%s: priv is NULL\n", __func__); return; } kref_get(&priv->kref_fb_buf); } static void mtk_drm_fbdev_vm_close(struct vm_area_struct *vma) { struct fb_info *info = vma->vm_file->private_data; struct drm_fb_helper *fb_helper = info->par; struct drm_device *drm_dev = fb_helper->dev; DDPMSG("%s: munmap done\n", __func__); try_free_fb_buf(drm_dev); } static int mtk_drm_fbdev_vm_split(struct vm_area_struct *area, unsigned long addr) { /* don't support split */ DDPPR_ERR("split not support\n"); return -EFAULT; } static const struct vm_operations_struct mtk_drm_fbdev_vm_ops = { .split = mtk_drm_fbdev_vm_split, .close = mtk_drm_fbdev_vm_close, .open = mtk_drm_fbdev_vm_open, }; static int mtk_drm_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) { /* copy from fbmem.c */ unsigned long mmio_pgoff; unsigned long start; u32 len; int ret; struct drm_fb_helper *fb_helper = info->par; struct drm_device *drm_dev = fb_helper->dev; if (!try_use_fb_buf(drm_dev)) { DDPPR_ERR("%s: fb has been freed!\n", __func__); return -ENOMEM; } /* * Ugh. This can be either the frame buffer mapping, or * if pgoff points past it, the mmio mapping. */ start = info->fix.smem_start; len = info->fix.smem_len; mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT; if (vma->vm_pgoff >= mmio_pgoff) { if (info->var.accel_flags) { return -EINVAL; } vma->vm_pgoff -= mmio_pgoff; start = info->fix.mmio_start; len = info->fix.mmio_len; } vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); /* * The framebuffer needs to be accessed decrypted, be sure * SME protection is removed */ vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); ret = vm_iomap_memory(vma, start, len); if (ret) return ret; /* blow is our action */ vma->vm_ops = &mtk_drm_fbdev_vm_ops; return ret; } #endif static struct fb_ops mtk_fbdev_ops = { .owner = THIS_MODULE, .fb_fillrect = drm_fb_helper_cfb_fillrect, .fb_copyarea = drm_fb_helper_cfb_copyarea, .fb_imageblit = drm_fb_helper_cfb_imageblit, .fb_check_var = drm_fb_helper_check_var, .fb_set_par = drm_fb_helper_set_par, .fb_blank = drm_fb_helper_blank, .fb_pan_display = mtk_drm_fb_pan_display, .fb_setcmap = drm_fb_helper_setcmap, .fb_mmap = mtk_drm_fbdev_mmap, .fb_ioctl = mtk_drm_fb_ioctl, }; bool mtk_drm_lcm_is_connect(void) { struct device_node *chosen_node; chosen_node = of_find_node_by_path("/chosen"); if (chosen_node) { struct tag_videolfb *videolfb_tag = NULL; unsigned long size = 0; videolfb_tag = (struct tag_videolfb *)of_get_property( chosen_node, "atag,videolfb", (int *)&size); if (videolfb_tag) return videolfb_tag->islcmfound; DDPINFO("[DT][videolfb] videolfb_tag not found\n"); } else { DDPINFO("[DT][videolfb] of_chosen not found\n"); } return false; } int _parse_tag_videolfb(unsigned int *vramsize, phys_addr_t *fb_base, unsigned int *fps) { #ifdef CONFIG_MTK_DISP_NO_LK return -1; #else struct device_node *chosen_node; *fps = 6000; chosen_node = of_find_node_by_path("/chosen"); if (chosen_node) { struct tag_videolfb *videolfb_tag = NULL; unsigned long size = 0; videolfb_tag = (struct tag_videolfb *)of_get_property( chosen_node, "atag,videolfb", (int *)&size); if (videolfb_tag) { *vramsize = videolfb_tag->vram; *fb_base = videolfb_tag->fb_base; *fps = videolfb_tag->fps; if (*fps == 0) *fps = 6000; return 0; } DDPINFO("[DT][videolfb] videolfb_tag not found\n"); goto found; } else { DDPINFO("[DT][videolfb] of_chosen not found\n"); } return -1; found: DDPINFO("[DT][videolfb] fb_base = 0x%lx\n", (unsigned long)*fb_base); DDPINFO("[DT][videolfb] vram = 0x%x (%d)\n", *vramsize, *vramsize); DDPINFO("[DT][videolfb] fps = %d\n", *fps); return 0; #endif } static void free_fb_buf(struct kref *kref) { unsigned long va_start = 0; unsigned long va_end = 0; phys_addr_t fb_base; unsigned int vramsize, fps; _parse_tag_videolfb(&vramsize, &fb_base, &fps); if (!fb_base) { DDPPR_ERR("%s:get fb pa error\n", __func__); return; } va_start = (unsigned long)__va(fb_base); va_end = (unsigned long)__va(fb_base + (unsigned long)vramsize); if (va_start) free_reserved_area((void *)va_start, (void *)va_end, 0xff, "fbmem"); else DDPINFO("%s:va invalid\n", __func__); } int try_free_fb_buf(struct drm_device *dev) { struct mtk_drm_private *priv = dev->dev_private; DDPFUNC(); if (priv == NULL) { DDPPR_ERR("%s: priv is NULL\n", __func__); return -1; } return kref_put(&priv->kref_fb_buf, free_fb_buf); } static int mtk_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { struct drm_device *dev = helper->dev; struct mtk_drm_private *private = helper->dev->dev_private; struct drm_mode_fb_cmd2 mode = {0}; struct mtk_drm_gem_obj *mtk_gem; struct fb_info *info; struct drm_framebuffer *fb; unsigned int bytes_per_pixel, vramsize = 0, fps = 0; size_t size; int err; phys_addr_t fb_base = 0; DDPMSG("%s+\n", __func__); bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); if (_parse_tag_videolfb(&vramsize, &fb_base, &fps) < 0) { DDPINFO("[DT][videolfb] fb_base = 0x%lx\n", (unsigned long)fb_base); DDPINFO("[DT][videolfb] vram = 0x%x (%d)\n", vramsize, vramsize); DDPINFO("[DT][videolfb] fps = %d\n", fps); mode.width = sizes->surface_width; mode.height = sizes->surface_height; mode.pitches[0] = sizes->surface_width * bytes_per_pixel; size = mode.pitches[0] * mode.height; mtk_gem = mtk_drm_gem_create(dev, size, true); if (IS_ERR(mtk_gem)) return PTR_ERR(mtk_gem); } else { mode.width = ALIGN_TO_32(sizes->surface_width); /* LK pre-allocate triple buffer */ mode.height = ALIGN_TO_32(sizes->surface_height) * 3; mode.pitches[0] = ALIGN_TO_32(sizes->surface_width) * bytes_per_pixel; size = mode.pitches[0] * mode.height; mtk_gem = mtk_drm_fb_gem_insert(dev, size, fb_base, vramsize); if (IS_ERR(mtk_gem)) return PTR_ERR(mtk_gem); kmemleak_ignore(mtk_gem); } private->fbdev_bo = &mtk_gem->base; info = drm_fb_helper_alloc_fbi(helper); if (IS_ERR(info)) { err = PTR_ERR(info); dev_err(dev->dev, "failed to allocate framebuffer info, %d\n", err); goto err_gem_free_object; } fb = mtk_drm_framebuffer_create(dev, &mode, private->fbdev_bo); if (IS_ERR(fb)) { err = PTR_ERR(fb); dev_err(dev->dev, "failed to allocate DRM framebuffer, %d\n", err); goto err_release_fbi; } helper->fb = fb; info->par = helper; info->flags = FBINFO_FLAG_DEFAULT; info->fbops = &mtk_fbdev_ops; drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth); drm_fb_helper_fill_var(info, helper, sizes->fb_width, sizes->fb_height); dev->mode_config.fb_base = fb_base; // not need to read and write for fbdev info->screen_base = NULL;//mtk_gem->kvaddr; info->screen_size = 0;//size; info->fix.smem_len = size; info->fix.smem_start = fb_base; debug_info = info; #if !defined(CONFIG_DRM_MTK_DISABLE_AEE_LAYER) mtk_drm_assert_fb_init(dev, sizes->surface_width, sizes->surface_height); #endif DRM_DEBUG_KMS("FB [%ux%u]-%u size=%zd\n", fb->width, fb->height, fb->format->depth, size); info->skip_vt_switch = true; DDPMSG("%s-\n", __func__); return 0; err_release_fbi: err_gem_free_object: mtk_drm_gem_free_object(&mtk_gem->base); return err; } static const struct drm_fb_helper_funcs mtk_drm_fb_helper_funcs = { .fb_probe = mtk_fbdev_probe, }; static int mtk_drm_fb_add_one_connector(struct drm_device *dev, struct drm_fb_helper *helper) { struct drm_connector *connector; struct drm_connector_list_iter conn_iter; struct drm_encoder *encoder; const struct drm_connector_helper_funcs *helper_private; int ret = 0; drm_connector_list_iter_begin(dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { helper_private = connector->helper_private; if (helper_private->best_encoder) encoder = helper_private->best_encoder(connector); else encoder = drm_atomic_helper_best_encoder(connector); if (encoder && (encoder->possible_crtcs & 0x1)) { ret = drm_fb_helper_add_one_connector( helper, connector); break; } } drm_connector_list_iter_end(&conn_iter); return ret; } int mtk_fbdev_init(struct drm_device *dev) { struct mtk_drm_private *priv = dev->dev_private; struct drm_fb_helper *helper = &priv->fb_helper; int ret; DDPMSG("%s+\n", __func__); if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector || (priv == NULL)) return -EINVAL; kref_init(&priv->kref_fb_buf); drm_fb_helper_prepare(dev, helper, &mtk_drm_fb_helper_funcs); ret = drm_fb_helper_init(dev, helper, 1); if (ret) { dev_err(dev->dev, "failed to initialize DRM FB helper, %d\n", ret); goto fini; } ret = mtk_drm_fb_add_one_connector(dev, helper); if (ret) { dev_err(dev->dev, "failed to add connectors, %d\n", ret); goto fini; } ret = drm_fb_helper_initial_config(helper, 32); if (ret) { dev_err(dev->dev, "failed to set initial configuration, %d\n", ret); goto fini; } DDPMSG("%s-\n", __func__); return 0; fini: drm_fb_helper_fini(helper); return ret; } void mtk_fbdev_fini(struct drm_device *dev) { struct mtk_drm_private *priv = dev->dev_private; struct drm_fb_helper *helper = &priv->fb_helper; drm_fb_helper_unregister_fbi(helper); if (helper->fb) { drm_framebuffer_unregister_private(helper->fb); drm_framebuffer_remove(helper->fb); } drm_fb_helper_fini(helper); }