Files

1197 lines
33 KiB
C
Raw Permalink Normal View History

2026-08-13 11:28:40 +08:00
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <drm/drm.h>
#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <linux/dma-buf.h>
#include <linux/fb.h>
#include <linux/amlogic/ion.h>
#if IS_ENABLED(CONFIG_AMLOGIC_DEBUG_ATRACE)
#define KERNEL_ATRACE_TAG KERNEL_ATRACE_TAG_DRM
#include <trace/events/meson_atrace.h>
#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);
}
}