// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_ATRACE) #define KERNEL_ATRACE_TAG KERNEL_ATRACE_TAG_DRM #include #endif #include "meson_drv.h" #include "meson_gem.h" #include "meson_fb.h" #include "meson_fbdev.h" #include "meson_plane.h" #include "meson_vpu_pipeline.h" #define PREFERRED_BPP 32 #define PREFERRED_DEPTH 32 #define MESON_DRM_MAX_CONNECTOR 2 #define MAX_RETRY_CNT 20 static u32 rdma_chk_addr[] = { VIU_OSD1_TCOLOR_AG3, VIU_OSD2_TCOLOR_AG3, VIU_OSD3_TCOLOR_AG3, }; static size_t cal_fbdev_afbc_size(struct fb_info *info) { size_t size; u32 aligned_width, aligned_height, pixel_size; u32 stride, buf_num, buf_size, block_count; aligned_width = ALIGN(info->var.xres, AFBC_TILED_HEADERS_WIDEBLK_WIDTH_ALIGN); aligned_height = ALIGN(info->var.yres, AFBC_TILED_HEADERS_WIDEBLK_HEIGHT_ALIGN); pixel_size = info->var.bits_per_pixel / 8; stride = aligned_width * pixel_size; stride = ALIGN(stride, 64); block_count = aligned_width / AFBC_PIXELS_PER_BLOCK * aligned_height / AFBC_PIXELS_PER_BLOCK; buf_size = stride * aligned_height + ALIGN(block_count * AFBC_HEADER_BYTES_PER_BLOCKENTRY, AFBC_BODY_BYTE_ALIGNMENT); buf_num = info->var.yres_virtual / info->var.yres; buf_size = roundup(buf_size, BIT(CONFIG_CMA_ALIGNMENT) * PAGE_SIZE); size = buf_size * buf_num; return size; } static int am_meson_fbdev_alloc_fb_gem(struct fb_info *info) { struct am_meson_fb *meson_fb; struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct drm_framebuffer *fb = helper->fb; struct drm_device *dev = helper->dev; struct am_meson_gem_object *meson_gem; void *vaddr = NULL; struct page **pages; struct page **tmp; struct sg_page_iter piter; pgprot_t pgprot; int npages; struct meson_drm *priv = dev->dev_private; struct meson_of_conf *conf = &priv->of_conf; size_t size = cal_fbdev_afbc_size(info); if (!conf->afbc_aligned_size) size = info->screen_size; if (!fbdev->fb_gem) { meson_gem = am_meson_gem_object_create(dev, 0, size); if (IS_ERR(meson_gem)) { DRM_ERROR("alloc memory %d fail\n", (u32)size); return -ENOMEM; } #ifndef CONFIG_AMLOGIC_ION_DEV if (!meson_gem->is_dma) { DRM_WARN("ion is not supported anymore!\n"); meson_gem_object_free(&meson_gem->base); return -EINVAL; } #endif fbdev->fb_gem = &meson_gem->base; fb = helper->fb; meson_fb = container_of(fb, struct am_meson_fb, base); if (!meson_fb) { DRM_INFO("meson_fb is NULL!\n"); return -EINVAL; } meson_fb->bufp[0] = meson_gem; if (meson_gem->is_dma) { npages = PAGE_ALIGN(meson_gem->base.size) / PAGE_SIZE; pages = vmalloc(array_size(npages, sizeof(struct page *))); tmp = pages; if (!pages) return -ENOMEM; pgprot = pgprot_writecombine(PAGE_KERNEL); for_each_sgtable_page(meson_gem->sg, &piter, 0) { WARN_ON(tmp - pages >= npages); *tmp++ = sg_page_iter_page(&piter); } vaddr = vmap(pages, npages, VM_MAP, pgprot); vfree(pages); } else { #ifdef CONFIG_AMLOGIC_ION_DEV vaddr = ion_heap_map_kernel(meson_gem->ionbuffer->heap, meson_gem->ionbuffer); #endif } info->screen_base = (char __iomem *)vaddr; info->fix.smem_start = meson_gem->addr; MESON_DRM_FBDEV("alloc memory %d done\n", (u32)size); } else { MESON_DRM_FBDEV("no need repeate alloc memory %d\n", (u32)size); } return 0; } static void am_meson_fbdev_free_fb_gem(struct fb_info *info) { struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev; struct drm_framebuffer *fb; struct am_meson_fb *meson_fb; if (!helper) { DRM_ERROR("fb helper is NULL!\n"); return; } fbdev = container_of(helper, struct meson_drm_fbdev, base); if (fbdev && fbdev->fb_gem) { #ifdef CONFIG_AMLOGIC_ION_DEV struct drm_gem_object *gem_obj = fbdev->fb_gem; struct am_meson_gem_object *meson_gem = container_of(gem_obj, struct am_meson_gem_object, base); if (!meson_gem->is_dma) ion_heap_unmap_kernel(meson_gem->ionbuffer->heap, meson_gem->ionbuffer); #endif info->screen_base = NULL; meson_gem_object_free(fbdev->fb_gem); fbdev->fb_gem = NULL; fb = helper->fb; if (fb) { meson_fb = container_of(fb, struct am_meson_fb, base); if (meson_fb) meson_fb->bufp[0] = NULL; else DRM_ERROR("meson_fb is NULL!\n"); } else { DRM_ERROR("drm framebuffer is NULL!\n"); } MESON_DRM_FBDEV("free memory done\n"); } else { MESON_DRM_FBDEV("memory already free before\n"); } } static int am_meson_fbdev_open(struct fb_info *info, int arg) { int ret = 0; struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct am_osd_plane *osdplane = container_of(fbdev->plane, struct am_osd_plane, base); MESON_DRM_FBDEV("IN,pid[%s %d],plane_id[%d],screen_size[%ld],res_virtual[%d %d]\n", current->comm, current->pid, osdplane->plane_index, info->screen_size, info->var.xres_virtual, info->var.yres_virtual); ret = am_meson_fbdev_alloc_fb_gem(info); MESON_DRM_FBDEV("OUT, ret=%d\n", ret); return ret; } static int am_meson_fbdev_release(struct fb_info *info, int arg) { struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct am_osd_plane *osdplane = container_of(fbdev->plane, struct am_osd_plane, base); MESON_DRM_FBDEV("IN, pid[%s %d], plane_id[%d]\n", current->comm, current->pid, osdplane->plane_index); MESON_DRM_FBDEV("may no need to release memory\n"); MESON_DRM_FBDEV("OUT\n"); return 0; } static int am_meson_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct am_meson_gem_object *meson_gem; meson_gem = container_of(fbdev->fb_gem, struct am_meson_gem_object, base); return meson_gem_prime_mmap(fbdev->fb_gem, vma); } static int am_meson_drm_fbdev_sync(struct fb_info *info) { return 0; } static int am_meson_drm_fbdev_setcmap(struct fb_cmap *cmap, struct fb_info *info) { int count, index; int r = 0; u16 *red, *green, *blue, *transp; u16 trans = 0xffff; red = cmap->red; green = cmap->green; blue = cmap->blue; transp = cmap->transp; index = cmap->start; MESON_DRM_FBDEV("visual = %d, len = %d\n", info->fix.visual, cmap->len); if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR) { for (count = 0; count < cmap->len; count++) { if (transp) trans = *transp++; if (info->fbops->fb_setcolreg) r = info->fbops->fb_setcolreg(index++, *red++, *green++, *blue++, trans, info); if (r != 0) return r; } } else { r = drm_fb_helper_setcmap(cmap, info); return r; } return 0; } static int am_meson_drm_fbdev_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info) { struct drm_fb_helper *helper = info->par; struct fb_var_screeninfo *var = &info->var; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct drm_device *dev = helper->dev; struct am_osd_plane *osd_plane = to_am_osd_plane(fbdev->plane); MESON_DRM_FBDEV("pixel is %d bits, palette index-%d\n", var->bits_per_pixel, regno); if (var->bits_per_pixel == 8 && regno < 256) { drm_modeset_lock_all(dev); fbdev->fbdev_rec_palette[regno] = ((red & 0xff) << 24) | ((green & 0xff) << 16) | ((blue & 0xff) << 8) | (transp & 0xff); osd_plane->receive_palette = &fbdev->fbdev_rec_palette[0]; MESON_DRM_FBDEV("palette index-%d value-0x%x.\n", regno, osd_plane->receive_palette[regno]); drm_modeset_unlock_all(dev); } return 0; } /** * am_meson_drm_fb_helper_check_var - implementation for ->fb_check_var * @var: screeninfo to check * @info: fbdev registered by the helper */ static int am_meson_drm_fb_helper_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct drm_framebuffer *fb = fb_helper->fb; int depth; if (var->pixclock != 0 || in_dbg_master()) { DRM_ERROR("FAILED.\n"); return -EINVAL; } /* * Changes struct fb_var_screeninfo are currently not pushed back * to KMS, hence fail if different settings are requested. */ MESON_DRM_FBDEV("fb requested w/h/bpp %dx%d-%d (virtual %dx%d)\n", var->xres, var->yres, var->bits_per_pixel, var->xres_virtual, var->yres_virtual); MESON_DRM_FBDEV("current fb w/h/bpp %dx%d-%d\n", fb->width, fb->height, fb->format->depth); if (var->bits_per_pixel != fb->format->depth || var->xres_virtual != fb->width || var->yres_virtual != fb->height) MESON_DRM_FBDEV("need realloc buffer\n"); switch (var->bits_per_pixel) { case 16: depth = (var->green.length == 6) ? 16 : 15; break; case 32: depth = (var->transp.length > 0) ? 32 : 24; break; default: depth = var->bits_per_pixel; break; } switch (depth) { case 8: var->red.offset = 0; var->green.offset = 0; var->blue.offset = 0; var->red.length = 8; var->green.length = 8; var->blue.length = 8; var->transp.length = 0; var->transp.offset = 0; break; case 15: var->red.offset = 10; var->green.offset = 5; var->blue.offset = 0; var->red.length = 5; var->green.length = 5; var->blue.length = 5; var->transp.length = 1; var->transp.offset = 15; break; case 16: var->red.offset = 11; var->green.offset = 5; var->blue.offset = 0; var->red.length = 5; var->green.length = 6; var->blue.length = 5; var->transp.length = 0; var->transp.offset = 0; break; case 24: var->red.offset = 16; var->green.offset = 8; var->blue.offset = 0; var->red.length = 8; var->green.length = 8; var->blue.length = 8; var->transp.length = 0; var->transp.offset = 0; break; case 32: var->red.offset = 16; var->green.offset = 8; var->blue.offset = 0; var->red.length = 8; var->green.length = 8; var->blue.length = 8; var->transp.length = 8; var->transp.offset = 24; break; default: return -EINVAL; } return 0; } static void am_meson_drm_wait_one_vblank(struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct meson_drm_fbdev *fbdev = container_of(fb_helper, struct meson_drm_fbdev, base); int crtc_index; struct drm_vblank_crtc *vblank; if (fbdev->plane->crtc) crtc_index = fbdev->plane->crtc->index; else if (fbdev->modeset.crtc) crtc_index = fbdev->modeset.crtc->index; else crtc_index = 0; vblank = &fb_helper->dev->vblank[crtc_index]; if (vblank->enabled) drm_wait_one_vblank(fb_helper->dev, crtc_index); else MESON_DRM_FBDEV("crtc-%d is disabled, can not wait vblank.\n", crtc_index); } /** * drm_fb_helper_set_par - implementation for ->fb_set_par * @info: fbdev registered by the helper * * This will let fbcon do the mode init and is called at initialization time by * the fbdev core when registering the driver, and later on through the hotplug * callback. */ int am_meson_drm_fb_helper_set_par(struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct fb_var_screeninfo *var = &info->var; struct drm_framebuffer *fb = fb_helper->fb; struct meson_drm_fbdev *fbdev = container_of(fb_helper, struct meson_drm_fbdev, base); struct drm_gem_object *fb_gem = fbdev->fb_gem; struct drm_plane *plane = fbdev->plane; struct drm_fb_helper_surface_size sizes; struct drm_device *dev = fb_helper->dev; struct drm_mode_fb_cmd2 mode_cmd = { 0 }; unsigned int bytes_per_pixel, pitches; u32 xres_set = var->xres; u32 yres_set = var->yres; int depth; unsigned long screen_size; unsigned long old_screen_size = info->screen_size; if (oops_in_progress) return -EBUSY; if (var->pixclock != 0) { DRM_ERROR("PIXEL CLOCK SET\n"); return -EINVAL; } DRM_INFO("pid[%s %d], plane_id[%d], fb_gem[%px]\n" "bpp[%d %d], fb[%d %d], res[%d %d], res_virtual[%d %d]\n", current->comm, current->pid, plane->index, fb_gem, var->bits_per_pixel, fb->format->depth, fb->width, fb->height, var->xres, var->yres, var->xres_virtual, var->yres_virtual); if (var->bits_per_pixel != fb->format->depth || var->xres_virtual != fb->width || var->yres_virtual != fb->height || !fb_gem) { fbdev->vscreen_info_changed = true; /*realloc framebuffer, free old then alloc new gem*/ sizes.fb_height = var->yres_virtual; sizes.fb_width = var->xres_virtual; sizes.surface_width = sizes.fb_width; sizes.surface_height = sizes.fb_height; sizes.surface_bpp = var->bits_per_pixel; sizes.surface_depth = PREFERRED_DEPTH; switch (var->bits_per_pixel) { case 16: depth = (var->green.length == 6) ? 16 : 15; break; case 32: depth = (var->transp.length > 0) ? 32 : 24; break; default: depth = var->bits_per_pixel; break; } bytes_per_pixel = DIV_ROUND_UP(sizes.surface_bpp, 8); mode_cmd.width = sizes.surface_width; mode_cmd.height = sizes.surface_height; mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes.surface_bpp, depth); mode_cmd.pitches[0] = ALIGN(mode_cmd.width * bytes_per_pixel, 64); MESON_DRM_FBDEV("length[%d %d %d %d]offset[%d %d %d %d]format:%c%c%c%c\n", var->red.length, var->green.length, var->blue.length, var->transp.length, var->red.offset, var->green.offset, var->blue.offset, var->transp.offset, (mode_cmd.pixel_format & 0xFF), ((mode_cmd.pixel_format >> 8) & 0xFF), ((mode_cmd.pixel_format >> 16) & 0xFF), ((mode_cmd.pixel_format >> 24) & 0xFF)); pitches = ALIGN(sizes.fb_width * bytes_per_pixel, 64); screen_size = pitches * sizes.fb_height; if (fb_gem && fb_gem->size < info->screen_size) { DRM_INFO("%s, GEM SIZE is not enough: %ld < %ld\n", __func__, (unsigned long)fb_gem->size, screen_size); am_meson_fbdev_free_fb_gem(info); fb_gem = NULL; } if (!fb_gem) { info->screen_size = screen_size; if (am_meson_fbdev_alloc_fb_gem(info)) { DRM_ERROR("realloc fb fail, screen_size[%ld->%ld]\n", screen_size, old_screen_size); info->screen_size = old_screen_size; return -ENOMEM; } } fb->width = sizes.fb_width; fb->height = sizes.fb_height; fb->pitches[0] = ALIGN(fb->width * bytes_per_pixel, 64); fb->format = drm_get_format_info(dev, &mode_cmd); info->screen_size = fb->pitches[0] * fb->height; //info->fix.smem_len = info->screen_size; drm_fb_helper_fill_info(info, fb_helper, &sizes); /* fix some bug in drm_fb_helper_full_info */ var->xres = xres_set; var->yres = yres_set; } am_meson_drm_wait_one_vblank(info); return 0; } /* sync of drm_client_modeset_commit_atomic(), * 1. add fbdev for non-primary plane. * 2. remove crtc set. */ static int am_meson_drm_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { struct drm_fb_helper *fb_helper = info->par; struct meson_drm_fbdev *fbdev = container_of(fb_helper, struct meson_drm_fbdev, base); struct drm_device *dev = fb_helper->dev; struct drm_atomic_state *state; struct drm_plane_state *plane_state; struct drm_plane *plane = fbdev->plane; struct drm_mode_set *mode_set; int hdisplay, vdisplay; ktime_t start1, end1, start2, end2; s64 commit_time_ms, total_time_ms; int ret; if (fbdev->blank) { MESON_DRM_FBDEV("skip blank.\n"); return 0; } if (fbdev->vscreen_info_changed) { fbdev->vscreen_info_changed = false; DRM_INFO("%s, skip set_par's pan display\n", __func__); return 0; } MESON_DRM_FBDEV("IN, pid[%s %d], plane_id[%d], plane_type[%d], fb[%p]\n", current->comm, current->pid, plane->index, plane->type, fb_helper->fb); #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_ATRACE) ATRACE_BEGIN("fbdev_pan_display"); #endif start1 = ktime_get(); drm_modeset_lock_all(dev); state = drm_atomic_state_alloc(dev); if (!state) { ret = -ENOMEM; goto unlock_exit; } state->acquire_ctx = dev->mode_config.acquire_ctx; retry: mode_set = &fbdev->modeset; /*update plane state, refer to drm_atomic_plane_set_property()*/ plane_state = drm_atomic_get_plane_state(state, plane); if (IS_ERR(plane_state)) { ret = PTR_ERR(plane_state); goto fail; } ret = drm_atomic_set_crtc_for_plane(plane_state, mode_set->crtc); if (ret != 0) { DRM_ERROR("set crtc for plane failed.\n"); goto fail; } drm_mode_get_hv_timing(&mode_set->crtc->mode, &hdisplay, &vdisplay); if (fbdev->dst_changed.w && fbdev->dst_changed.h) { plane_state->crtc_x = fbdev->dst_changed.x; plane_state->crtc_y = fbdev->dst_changed.y; plane_state->crtc_w = fbdev->dst_changed.w; plane_state->crtc_h = fbdev->dst_changed.h; } else { plane_state->crtc_x = 0; plane_state->crtc_y = 0; plane_state->crtc_w = hdisplay; plane_state->crtc_h = vdisplay; } drm_atomic_set_fb_for_plane(plane_state, fb_helper->fb); if (fb_helper->fb) { plane_state->src_x = var->xoffset << 16; plane_state->src_y = var->yoffset << 16; plane_state->src_w = var->xres << 16; plane_state->src_h = var->yres << 16; plane_state->zpos = fbdev->zorder; } else { plane_state->src_x = 0; plane_state->src_y = 0; plane_state->src_w = 0; plane_state->src_h = 0; plane_state->zpos = fbdev->zorder; } /* fix alpha */ plane_state->pixel_blend_mode = DRM_MODE_BLEND_PREMULTI; MESON_DRM_FBDEV("update fb [%x-%x, %xx%x]-%d->[%d-%d, %dx%d]\n", plane_state->src_x, plane_state->src_y, plane_state->src_w, plane_state->src_h, plane_state->zpos, plane_state->crtc_x, plane_state->crtc_y, plane_state->crtc_w, plane_state->crtc_h); state->legacy_cursor_update = true; start2 = ktime_get(); ret = drm_atomic_commit(state); end2 = ktime_get(); commit_time_ms = ktime_ms_delta(end2, start2); if (ret != 0) goto fail; info->var.xoffset = var->xoffset; info->var.yoffset = var->yoffset; fail: if (ret == -EDEADLK) goto backoff; drm_atomic_state_put(state); unlock_exit: drm_modeset_unlock_all(dev); if (ret) DRM_ERROR("failed, ret = %d\n", ret); else MESON_DRM_FBDEV("OUT\n"); end1 = ktime_get(); total_time_ms = ktime_ms_delta(end1, start1); #if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_ATRACE) ATRACE_END(); ATRACE_COUNTER("fbdev_pan_display", total_time_ms); #endif if (total_time_ms > am_drm_param.fbdev_pan_display_threshold) DRM_WARN("fbdev pan display cost (%lld %lld) ms, it is too long\n", commit_time_ms, total_time_ms); return ret; backoff: drm_atomic_state_clear(state); drm_modeset_backoff(state->acquire_ctx); goto retry; } static void meson_drm_modeset_lock_crtc(struct drm_crtc *crtc, struct drm_plane *plane, struct drm_modeset_acquire_ctx *ctx) { int ret; drm_modeset_acquire_init(ctx, 0); retry: ret = drm_modeset_lock(&crtc->mutex, ctx); if (ret) goto fail; if (plane) { ret = drm_modeset_lock(&plane->mutex, ctx); if (ret) goto fail; if (plane->crtc) { ret = drm_modeset_lock(&plane->crtc->mutex, ctx); if (ret) goto fail; } } return; fail: if (ret == -EDEADLK) { drm_modeset_backoff(ctx); goto retry; } } void meson_drm_modeset_unlock_crtc(struct drm_crtc *crtc, struct drm_modeset_acquire_ctx *ctx) { drm_modeset_drop_locks(ctx); drm_modeset_acquire_fini(ctx); } /** * the implement if different from drm_fb_helper. * for plane based fbdev, we only disable corresponding plane * but not the whole crtc. */ int am_meson_drm_fb_blank(int blank, struct fb_info *info) { struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct drm_device *dev = helper->dev; struct meson_drm *priv = dev->dev_private; struct drm_crtc *crtc = NULL; struct drm_modeset_acquire_ctx ctx; int ret = 0; if (blank == 0) { MESON_DRM_FBDEV("IN, pid[%s %d] meson_fbdev[%s]fb[%p] goto unblank.\n", current->comm, current->pid, fbdev->plane->name, fbdev->plane->fb); fbdev->blank = false; ret = am_meson_drm_fb_pan_display(&info->var, info); am_meson_drm_wait_one_vblank(info); } else { MESON_DRM_FBDEV("IN, pid[%s %d] meson_fbdev[%s]fb[%p] goto blank.\n", current->comm, current->pid, fbdev->plane->name, fbdev->plane->fb); if (fbdev->plane->crtc) { crtc = fbdev->plane->crtc; } else if (fbdev->modeset.crtc) { crtc = fbdev->modeset.crtc; } if (!crtc) { DRM_ERROR("crtc is null, skip %s blank\n", fbdev->plane->name); return 0; } meson_drm_modeset_lock_crtc(crtc, fbdev->plane, &ctx); if (priv->pan_async_commit_ran) { DRM_INFO("Force to wait one vblank!\n"); am_meson_drm_wait_one_vblank(info); } drm_atomic_helper_disable_plane(fbdev->plane, &ctx); meson_drm_modeset_unlock_crtc(crtc, &ctx); fbdev->blank = true; } MESON_DRM_FBDEV("OUT, meson_fbdev[%s], ret=%d\n", fbdev->plane->name, ret); return ret; } static int am_meson_drm_fbdev_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { int ret = 0, i = 0; u32 val; void __user *argp = (void __user *)arg; struct fb_dmabuf_export fbdma; struct drm_fb_helper *helper = info->par; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct drm_plane *plane = fbdev->plane; struct am_meson_fb *meson_fb; struct region dst = {0}; u32 zorder = 0; memset(&fbdma, 0, sizeof(fbdma)); MESON_DRM_FBDEV("CMD [%x] - [%d] IN\n", cmd, plane->index); /*amlogic fbdev ioctl, used by gpu fbdev backend.*/ if (cmd == FBIOGET_OSD_DMABUF) { meson_fb = container_of(helper->fb, struct am_meson_fb, base); fbdma.fd = dma_buf_fd(meson_fb->bufp[0]->dmabuf, O_CLOEXEC); fbdma.flags = O_CLOEXEC; dma_buf_get(fbdma.fd); ret = copy_to_user(argp, &fbdma, sizeof(fbdma)) ? -EFAULT : 0; } else if (cmd == FBIO_WAITFORVSYNC) { am_meson_drm_wait_one_vblank(info); val = meson_drm_read_reg(rdma_chk_addr[plane->index]); while (i < MAX_RETRY_CNT && val != frame_seq[plane->index]) { usleep_range(2000, 2500); i++; val = meson_drm_read_reg(rdma_chk_addr[plane->index]); } if (i == MAX_RETRY_CNT) DRM_ERROR("timeout, frame seq %u-%u\n", frame_seq[plane->index], val); MESON_DRM_FBDEV("wait for vsync last for %d ms, %u-%u\n", i * 2, frame_seq[plane->index], val); } else if (cmd == FBIOSET_OSD_DST_RESOLUTION) { ret = copy_from_user(&dst, argp, sizeof(struct region)) ? -EFAULT : 0; if (ret) { DRM_ERROR("set dst region error and not change\n"); memset(&fbdev->dst_changed, 0, sizeof(struct region)); } else if ((dst.w == 0) || (dst.h == 0)) { MESON_DRM_FBDEV("restore mode resolution by clearing w(%d) or h(%d)\n", dst.w, dst.h); memset(&fbdev->dst_changed, 0, sizeof(struct region)); } else { MESON_DRM_FBDEV("set dst region is [%d, %d, %d x %d]\n", dst.x, dst.y, dst.w, dst.h); memcpy(&fbdev->dst_changed, &dst, sizeof(struct region)); } } else if (cmd == FBIOGET_OSD_ZORDER) { zorder = fbdev->zorder; ret = copy_to_user(argp, &zorder, sizeof(zorder)) ? -EFAULT : 0; MESON_DRM_FBDEV("get plane %d zorder and value is%d(%d)\n", plane->index, zorder, fbdev->zorder); } else if (cmd == FBIOSET_OSD_ZORDER) { ret = copy_from_user(&zorder, argp, sizeof(zorder)) ? -EFAULT : 0; if (ret) { DRM_ERROR("set plane %d zorder error and not change\n", plane->index); } else if ((zorder < 65) || (zorder > 128)) { DRM_ERROR("plane %d zorder value %d is out of range\n", plane->index, zorder); ret = -EFAULT; } else { MESON_DRM_FBDEV("set plane %d zorder and value is %d\n", plane->index, zorder); fbdev->zorder = zorder; ret = am_meson_drm_fb_pan_display(&info->var, info); } } else { DRM_ERROR("invalid fbdev ioctl cmd[%x]\n", cmd); ret = -EFAULT; } MESON_DRM_FBDEV("CMD [%x] - [%d] OUT\n", cmd, plane->index); return ret; } static struct fb_ops meson_drm_fbdev_ops = { .owner = THIS_MODULE, .fb_open = am_meson_fbdev_open, .fb_release = am_meson_fbdev_release, .fb_mmap = am_meson_fbdev_mmap, __FB_DEFAULT_DMAMEM_OPS_DRAW, .fb_check_var = am_meson_drm_fb_helper_check_var, .fb_set_par = am_meson_drm_fb_helper_set_par, .fb_blank = am_meson_drm_fb_blank, .fb_pan_display = am_meson_drm_fb_pan_display, .fb_setcmap = am_meson_drm_fbdev_setcmap, .fb_setcolreg = am_meson_drm_fbdev_setcolreg, .fb_sync = am_meson_drm_fbdev_sync, .fb_ioctl = am_meson_drm_fbdev_ioctl, #ifdef CONFIG_COMPAT .fb_compat_ioctl = am_meson_drm_fbdev_ioctl, #endif }; static int am_meson_drm_fbdev_modeset_create(struct drm_fb_helper *helper) { struct drm_device *dev = helper->dev; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct am_osd_plane *amp = to_am_osd_plane(fbdev->plane); struct meson_drm *private = dev->dev_private; int crtc_id = private->of_conf.crtcmask_osd[amp->plane_index]; fbdev->modeset.crtc = drm_crtc_from_index(dev, crtc_id); DRM_INFO("%s in, plane_index = %d, crtc_id = %d\n", __func__, amp->plane_index, crtc_id); return 0; } static int am_meson_drm_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizesxx) { struct drm_device *dev = helper->dev; struct meson_drm *private = dev->dev_private; struct meson_drm_fbdev *fbdev = container_of(helper, struct meson_drm_fbdev, base); struct drm_mode_fb_cmd2 mode_cmd = { 0 }; struct drm_fb_helper_surface_size sizes; struct drm_framebuffer *fb; struct fb_info *fbi; unsigned int bytes_per_pixel; int ret; if (private->ui_config.overlay_flag == 1) { sizes.fb_width = private->ui_config.overlay_ui_w; sizes.fb_height = private->ui_config.overlay_ui_h; sizes.surface_width = private->ui_config.overlay_fb_w; sizes.surface_height = private->ui_config.overlay_fb_h; sizes.surface_bpp = private->ui_config.overlay_fb_bpp; } else { sizes.fb_width = private->ui_config.ui_w; sizes.fb_height = private->ui_config.ui_h; sizes.surface_width = private->ui_config.fb_w; sizes.surface_height = private->ui_config.fb_h; sizes.surface_bpp = private->ui_config.fb_bpp; } sizes.surface_depth = PREFERRED_DEPTH; bytes_per_pixel = DIV_ROUND_UP(sizes.surface_bpp, 8); mode_cmd.width = sizes.surface_width; mode_cmd.height = sizes.surface_height; mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes.surface_bpp, PREFERRED_DEPTH); mode_cmd.pitches[0] = ALIGN(mode_cmd.width * bytes_per_pixel, 64); DRM_INFO("fb=%dx%d, surface=%dx%d, bpp=%d\n", sizes.fb_width, sizes.fb_height, sizes.surface_width, sizes.surface_height, sizes.surface_bpp); DRM_INFO("mode_cmd_size=%dx%d,pixel_format=%d-%d-%d,pitches=%d,fb_gem=%p\n", mode_cmd.width, mode_cmd.height, mode_cmd.pixel_format, DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888, mode_cmd.pitches[0], fbdev->fb_gem); fbi = drm_fb_helper_alloc_info(helper); if (IS_ERR(fbi)) { DRM_ERROR("Failed to create framebuffer info.\n"); ret = PTR_ERR(fbi); return ret; } helper->fb = am_meson_drm_framebuffer_init(dev, &mode_cmd, fbdev->fb_gem); if (IS_ERR(helper->fb)) { dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n"); ret = PTR_ERR(helper->fb); goto err_release_fbi; } fb = helper->fb; fbi->par = helper; fbi->fbops = &meson_drm_fbdev_ops; fbi->skip_vt_switch = true; fbi->screen_size = fb->pitches[0] * fb->height; fbi->fix.smem_len = fbi->screen_size; drm_fb_helper_fill_info(fbi, helper, &sizes); am_meson_drm_fbdev_modeset_create(helper); DRM_INFO("xres=%dx%d, xres_virtual=%dx%d, bpp=%d, screen_size=%ld\n", fbi->var.xres, fbi->var.yres, fbi->var.xres_virtual, fbi->var.yres_virtual, fbi->var.bits_per_pixel, fbi->screen_size); return 0; err_release_fbi: drm_fb_helper_fini(helper); return ret; } static const struct drm_fb_helper_funcs meson_drm_fb_helper_funcs = { .fb_probe = am_meson_drm_fbdev_probe, }; static int am_meson_fbdev_parse_config(struct drm_device *dev) { struct meson_drm *private = dev->dev_private; struct meson_vpu_pipeline *pipeline = private->pipeline; u32 sizes[5], overlay_sizes[5]; int ret, tmp, i; ret = of_property_read_u32_array(dev->dev->of_node, "fbdev_sizes", sizes, 5); tmp = of_property_read_u32_array(dev->dev->of_node, "fbdev_overlay_sizes", overlay_sizes, 5); if (!ret) { private->ui_config.ui_w = sizes[0]; private->ui_config.ui_h = sizes[1]; private->ui_config.fb_w = sizes[2]; private->ui_config.fb_h = sizes[3]; private->ui_config.fb_bpp = sizes[4]; private->ui_config.overlay_ui_w = sizes[0]; private->ui_config.overlay_ui_h = sizes[1]; private->ui_config.overlay_fb_w = sizes[2]; private->ui_config.overlay_fb_h = sizes[3]; private->ui_config.overlay_fb_bpp = sizes[4]; } if (!tmp) { private->ui_config.overlay_ui_w = overlay_sizes[0]; private->ui_config.overlay_ui_h = overlay_sizes[1]; private->ui_config.overlay_fb_w = overlay_sizes[2]; private->ui_config.overlay_fb_h = overlay_sizes[3]; private->ui_config.overlay_fb_bpp = overlay_sizes[4]; } /*initial fbdev zorder*/ for (i = 0; i < MESON_MAX_OSD; i++) private->fbdev_zorder[i] = i; tmp = of_property_read_u32_array(dev->dev->of_node, "fbdev_zorder", private->fbdev_zorder, pipeline->num_osds); if (tmp) MESON_DRM_FBDEV("undefined fbdev_zorder!\n"); return ret; } static ssize_t show_force_free_mem(struct device *device, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "Usage: echo 1 > force_free mem\n"); } static ssize_t store_force_free_mem(struct device *device, struct device_attribute *attr, const char *buf, size_t count) { struct fb_info *fb_info = dev_get_drvdata(device); if (strncmp("1", buf, 1) == 0) { am_meson_fbdev_free_fb_gem(fb_info); DRM_INFO("fb mem is freed !\n"); } return count; } static struct device_attribute fbdev_device_attrs[] = { __ATTR(force_free_mem, 0644, show_force_free_mem, store_force_free_mem), }; struct meson_drm_fbdev *am_meson_create_drm_fbdev(struct drm_device *dev, struct drm_plane *plane) { struct meson_drm *drmdev = dev->dev_private; struct meson_drm_fbdev *fbdev; struct drm_fb_helper *helper; int ret, bpp; struct fb_info *fbinfo; int i = 0; bpp = drmdev->ui_config.fb_bpp; fbdev = devm_kzalloc(dev->dev, sizeof(struct meson_drm_fbdev), GFP_KERNEL); if (!fbdev) return NULL; helper = &fbdev->base; if (plane) fbdev->plane = plane; else return NULL; drm_fb_helper_prepare(dev, helper, bpp, &meson_drm_fb_helper_funcs); ret = drm_fb_helper_init(dev, helper); if (ret < 0) { dev_err(dev->dev, "Failed to initialize drm fb helper - %d.\n", ret); goto err_free; } ret = drm_fb_helper_initial_config(helper); if (ret < 0) { dev_err(dev->dev, "Failed to set initial hw config - %d.\n", ret); goto err_drm_fb_helper_fini; } fbinfo = helper->info; if (fbinfo && fbinfo->dev) { for (i = 0; i < ARRAY_SIZE(fbdev_device_attrs); i++) { ret = device_create_file(fbinfo->dev, &fbdev_device_attrs[i]); if (ret) { DRM_ERROR("Failed to create file - %d.\n", ret); continue; } } fbinfo->flags &= ~FBINFO_HIDE_SMEM_START; } fbdev->blank = false; fbdev->vscreen_info_changed = false; DRM_INFO("create fbdev success.\n"); return fbdev; err_drm_fb_helper_fini: drm_fb_helper_fini(helper); err_free: kfree(fbdev); fbdev = NULL; DRM_INFO("create drm fbdev failed[%d]\n", ret); return NULL; } int am_meson_drm_fbdev_init(struct drm_device *dev) { struct meson_drm *drmdev = dev->dev_private; struct meson_drm_fbdev *fbdev; struct am_osd_plane *osd_plane; int i, fbdev_cnt = 0; int ret = 0; DRM_INFO("%s in\n", __func__); ret = am_meson_fbdev_parse_config(dev); if (ret) { DRM_ERROR("don't find fbdev_sizes, please config it\n"); return ret; } if (drmdev->primary_plane) { drmdev->ui_config.overlay_flag = 0; fbdev = am_meson_create_drm_fbdev(dev, drmdev->primary_plane); if (fbdev) { fbdev->zorder = OSD_PLANE_BEGIN_ZORDER + drmdev->fbdev_zorder[0]; fbdev_cnt++; MESON_DRM_FBDEV("create fbdev(%p) for primary plane, zorder=(%d %d)\n", fbdev, drmdev->fbdev_zorder[i], fbdev->zorder); } else { DRM_ERROR("create fbdev for primary plane failed\n"); } } /*only create fbdev for viu1*/ for (i = 0; i < MESON_MAX_OSD; i++) { osd_plane = drmdev->osd_planes[i]; if (!osd_plane) continue; if (osd_plane->base.type == DRM_PLANE_TYPE_PRIMARY) continue; drmdev->ui_config.overlay_flag = 1; fbdev = am_meson_create_drm_fbdev(dev, &osd_plane->base); if (fbdev) { fbdev->zorder = OSD_PLANE_BEGIN_ZORDER + drmdev->fbdev_zorder[i]; fbdev_cnt++; MESON_DRM_FBDEV("create fbdev(%p) for plane (%d %d), zorder=(%d %d)\n", fbdev, i, osd_plane->plane_index, drmdev->fbdev_zorder[i], fbdev->zorder); } else { DRM_ERROR("create fbdev for plane %d failed\n", i); break; } } DRM_INFO("%s create %d out\n", __func__, fbdev_cnt); return 0; } void am_meson_drm_fbdev_fini(struct drm_device *dev) { struct meson_drm *private = dev->dev_private; struct meson_drm_fbdev *fbdev; struct drm_fb_helper *helper; struct fb_info *fbinfo; int i; for (i = 0; i < MESON_MAX_OSD; i++) { fbdev = private->osd_fbdevs[i]; if (!fbdev) { dev_err(dev->dev, "fbdev is NULL.\n"); continue; } helper = &fbdev->base; if (!helper || !helper->info) { kfree(fbdev); dev_err(dev->dev, "helper or fbinfo is NULL.\n"); continue; } fbinfo = helper->info; if (fbinfo && fbinfo->dev) { for (i = 0; i < ARRAY_SIZE(fbdev_device_attrs); i++) { device_remove_file(fbinfo->dev, &fbdev_device_attrs[i]); } } drm_fb_helper_unregister_info(helper); drm_fb_helper_fini(helper); if (helper->fb) drm_framebuffer_put(helper->fb); fbdev->fb_gem = NULL; drm_fb_helper_fini(helper); kfree(fbdev); } }