452 lines
11 KiB
C
452 lines
11 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
|
|
#include "meson_fb.h"
|
|
#include "meson_vpu.h"
|
|
|
|
static const struct drm_format_info meson_formats[] = {
|
|
{ .format = DRM_FORMAT_ABGR10101010, .depth = 40, .num_planes = 1,
|
|
.cpp = { 5, 0, 0 }, .hsub = 1, .vsub = 1, .has_alpha = true },
|
|
{ .format = DRM_FORMAT_YUVX1010102, .depth = 32, .num_planes = 1,
|
|
.cpp = { 4, 0, 0 }, .hsub = 1, .vsub = 1, .is_yuv = true },
|
|
};
|
|
|
|
void am_meson_fb_destroy(struct drm_framebuffer *fb)
|
|
{
|
|
struct am_meson_fb *meson_fb = to_am_meson_fb(fb);
|
|
int i;
|
|
|
|
for (i = 0; i < AM_MESON_GEM_OBJECT_NUM && meson_fb->bufp[i]; i++)
|
|
drm_gem_object_put(&meson_fb->bufp[i]->base);
|
|
drm_framebuffer_cleanup(fb);
|
|
if (meson_fb->logo && meson_fb->logo->alloc_flag)
|
|
am_meson_free_logo_memory();
|
|
|
|
DRM_DEBUG("meson_fb=0x%p,\n", meson_fb);
|
|
kfree(meson_fb);
|
|
}
|
|
|
|
int am_meson_fb_create_handle(struct drm_framebuffer *fb,
|
|
struct drm_file *file_priv,
|
|
unsigned int *handle)
|
|
{
|
|
struct am_meson_fb *meson_fb = to_am_meson_fb(fb);
|
|
|
|
if (meson_fb->bufp[0])
|
|
return drm_gem_handle_create(file_priv,
|
|
&meson_fb->bufp[0]->base, handle);
|
|
else
|
|
return -EFAULT;
|
|
}
|
|
|
|
struct drm_framebuffer_funcs am_meson_fb_funcs = {
|
|
.create_handle = am_meson_fb_create_handle, //must for fbdev emulate
|
|
.destroy = am_meson_fb_destroy,
|
|
};
|
|
|
|
struct drm_framebuffer *
|
|
am_meson_fb_alloc(struct drm_device *dev,
|
|
struct drm_mode_fb_cmd2 *mode_cmd,
|
|
struct drm_gem_object *obj)
|
|
{
|
|
struct am_meson_fb *meson_fb;
|
|
struct am_meson_gem_object *meson_gem;
|
|
int ret = 0;
|
|
|
|
meson_fb = kzalloc(sizeof(*meson_fb), GFP_KERNEL);
|
|
if (!meson_fb)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (obj) {
|
|
meson_gem = container_of(obj, struct am_meson_gem_object, base);
|
|
meson_fb->bufp[0] = meson_gem;
|
|
} else {
|
|
meson_fb->bufp[0] = NULL;
|
|
}
|
|
drm_helper_mode_fill_fb_struct(dev, &meson_fb->base, mode_cmd);
|
|
|
|
ret = drm_framebuffer_init(dev, &meson_fb->base,
|
|
&am_meson_fb_funcs);
|
|
if (ret) {
|
|
dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
|
|
ret);
|
|
goto err_free_fb;
|
|
}
|
|
DRM_DEBUG("meson_fb[id:%d,ref:%d]=0x%p,meson_fb->bufp[0]=0x%p\n",
|
|
meson_fb->base.base.id,
|
|
kref_read(&meson_fb->base.base.refcount),
|
|
meson_fb, meson_fb->bufp[0]);
|
|
|
|
return &meson_fb->base;
|
|
|
|
err_free_fb:
|
|
kfree(meson_fb);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
struct drm_framebuffer *am_meson_fb_create(struct drm_device *dev,
|
|
struct drm_file *file_priv,
|
|
const struct drm_mode_fb_cmd2 *mode_cmd)
|
|
{
|
|
struct am_meson_fb *meson_fb = 0;
|
|
struct drm_gem_object *obj = 0;
|
|
struct am_meson_gem_object *meson_gem;
|
|
int ret, i;
|
|
|
|
meson_fb = kzalloc(sizeof(*meson_fb), GFP_KERNEL);
|
|
if (!meson_fb)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* support multi handle .*/
|
|
for (i = 0; i < AM_MESON_GEM_OBJECT_NUM && mode_cmd->handles[i]; i++) {
|
|
obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]);
|
|
if (!obj) {
|
|
dev_err(dev->dev, "Failed to lookup GEM handle\n");
|
|
kfree(meson_fb);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
meson_gem = container_of(obj, struct am_meson_gem_object, base);
|
|
meson_fb->bufp[i] = meson_gem;
|
|
}
|
|
|
|
drm_helper_mode_fill_fb_struct(dev, &meson_fb->base, mode_cmd);
|
|
|
|
ret = drm_framebuffer_init(dev, &meson_fb->base, &am_meson_fb_funcs);
|
|
if (ret) {
|
|
dev_err(dev->dev,
|
|
"Failed to initialize framebuffer: %d\n",
|
|
ret);
|
|
drm_gem_object_put(obj);
|
|
kfree(meson_fb);
|
|
return ERR_PTR(ret);
|
|
}
|
|
DRM_DEBUG("meson_fb[id:%d,ref:%d]=0x%px,meson_fb->bufp[%d-0]=0x%p\n",
|
|
meson_fb->base.base.id,
|
|
kref_read(&meson_fb->base.base.refcount),
|
|
meson_fb, i, meson_fb->bufp[0]);
|
|
|
|
return &meson_fb->base;
|
|
}
|
|
|
|
struct drm_framebuffer *
|
|
am_meson_drm_framebuffer_init(struct drm_device *dev,
|
|
struct drm_mode_fb_cmd2 *mode_cmd,
|
|
struct drm_gem_object *obj)
|
|
{
|
|
struct drm_framebuffer *fb;
|
|
|
|
fb = am_meson_fb_alloc(dev, mode_cmd, obj);
|
|
if (IS_ERR(fb))
|
|
return NULL;
|
|
|
|
return fb;
|
|
}
|
|
|
|
int am_meson_mode_rmfb(struct drm_device *dev, u32 fb_id,
|
|
struct drm_file *file_priv)
|
|
{
|
|
struct drm_framebuffer *fb = NULL;
|
|
struct drm_framebuffer *fbl = NULL;
|
|
int found = 0;
|
|
|
|
if (!drm_core_check_feature(dev, DRIVER_MODESET))
|
|
return -EOPNOTSUPP;
|
|
|
|
fb = drm_framebuffer_lookup(dev, file_priv, fb_id);
|
|
if (!fb)
|
|
return -ENOENT;
|
|
|
|
mutex_lock(&file_priv->fbs_lock);
|
|
list_for_each_entry(fbl, &file_priv->fbs, filp_head)
|
|
if (fb == fbl)
|
|
found = 1;
|
|
if (!found) {
|
|
mutex_unlock(&file_priv->fbs_lock);
|
|
goto fail_unref;
|
|
}
|
|
|
|
list_del_init(&fb->filp_head);
|
|
mutex_unlock(&file_priv->fbs_lock);
|
|
|
|
/* drop the reference we picked up in framebuffer lookup */
|
|
drm_framebuffer_put(fb);
|
|
|
|
/*
|
|
* we now own the reference that was stored in the fbs list
|
|
*
|
|
* drm_framebuffer_remove may fail with -EINTR on pending signals,
|
|
* so run this in a separate stack as there's no way to correctly
|
|
* handle this after the fb is already removed from the lookup table.
|
|
*/
|
|
DRM_DEBUG("fb=%px, fbid=%d, ref=%d\n", fb,
|
|
fb->base.id, drm_framebuffer_read_refcount(fb));
|
|
|
|
drm_framebuffer_put(fb);
|
|
|
|
return 0;
|
|
|
|
fail_unref:
|
|
drm_framebuffer_put(fb);
|
|
return -ENOENT;
|
|
}
|
|
|
|
int am_meson_mode_rmfb_ioctl(struct drm_device *dev,
|
|
void *data, struct drm_file *file_priv)
|
|
{
|
|
u32 *fb_id = data;
|
|
|
|
return am_meson_mode_rmfb(dev, *fb_id, file_priv);
|
|
}
|
|
|
|
static const struct drm_format_info *
|
|
lookup_format_info(const struct drm_format_info formats[],
|
|
int num_formats, u32 format)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < num_formats; i++) {
|
|
if (formats[i].format == format)
|
|
return &formats[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const struct drm_format_info *
|
|
am_meson_get_format_info(const struct drm_mode_fb_cmd2 *cmd)
|
|
{
|
|
return lookup_format_info(meson_formats,
|
|
ARRAY_SIZE(meson_formats),
|
|
cmd->pixel_format);
|
|
}
|
|
|
|
static int fb_plane_width(int width,
|
|
const struct drm_format_info *format, int plane)
|
|
{
|
|
if (plane == 0)
|
|
return width;
|
|
|
|
return DIV_ROUND_UP(width, format->hsub);
|
|
}
|
|
|
|
static int fb_plane_height(int height,
|
|
const struct drm_format_info *format, int plane)
|
|
{
|
|
if (plane == 0)
|
|
return height;
|
|
|
|
return DIV_ROUND_UP(height, format->vsub);
|
|
}
|
|
|
|
static int meson_framebuffer_check(struct drm_device *dev,
|
|
const struct drm_mode_fb_cmd2 *r)
|
|
{
|
|
const struct drm_format_info *info;
|
|
int i;
|
|
|
|
/* check if the format is supported at all */
|
|
if (!drm_get_format_info(dev, r)) {
|
|
drm_dbg_kms(dev, "bad framebuffer format %d\n",
|
|
r->pixel_format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (r->width == 0) {
|
|
drm_dbg_kms(dev, "bad framebuffer width %u\n", r->width);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (r->height == 0) {
|
|
drm_dbg_kms(dev, "bad framebuffer height %u\n", r->height);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* now let the driver pick its own format info */
|
|
info = drm_get_format_info(dev, r);
|
|
|
|
for (i = 0; i < info->num_planes; i++) {
|
|
unsigned int width = fb_plane_width(r->width, info, i);
|
|
unsigned int height = fb_plane_height(r->height, info, i);
|
|
unsigned int block_size = info->char_per_block[i];
|
|
u64 min_pitch = drm_format_info_min_pitch(info, i, width);
|
|
|
|
if (!block_size && r->modifier[i] == DRM_FORMAT_MOD_LINEAR) {
|
|
drm_dbg_kms(dev, "Format requires non-linear modifier for plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!r->handles[i]) {
|
|
drm_dbg_kms(dev, "no buffer object handle for plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (min_pitch > UINT_MAX)
|
|
return -ERANGE;
|
|
|
|
if ((uint64_t)height * r->pitches[i] + r->offsets[i] > UINT_MAX)
|
|
return -ERANGE;
|
|
|
|
if (block_size && r->pitches[i] < min_pitch) {
|
|
drm_dbg_kms(dev, "bad pitch %u for plane %d %lld\n",
|
|
r->pitches[i], i, min_pitch);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (r->modifier[i] && !(r->flags & DRM_MODE_FB_MODIFIERS)) {
|
|
drm_dbg_kms(dev, "bad fb modifier %llu for plane %d\n",
|
|
r->modifier[i], i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (r->flags & DRM_MODE_FB_MODIFIERS &&
|
|
r->modifier[i] != r->modifier[0]) {
|
|
drm_dbg_kms(dev, "bad fb modifier %llu for plane %d\n",
|
|
r->modifier[i], i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* modifier specific checks: */
|
|
switch (r->modifier[i]) {
|
|
case DRM_FORMAT_MOD_SAMSUNG_64_32_TILE:
|
|
/* NOTE: the pitch restriction may be lifted later if it turns
|
|
* out that no hw has this restriction:
|
|
*/
|
|
if (r->pixel_format != DRM_FORMAT_NV12 ||
|
|
width % 128 || height % 32 ||
|
|
r->pitches[i] % 128) {
|
|
drm_dbg_kms(dev, "bad modifier data for plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = info->num_planes; i < 4; i++) {
|
|
if (r->modifier[i]) {
|
|
drm_dbg_kms(dev, "non-zero modifier for unused plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Pre-FB_MODIFIERS userspace didn't clear the structs properly. */
|
|
if (!(r->flags & DRM_MODE_FB_MODIFIERS))
|
|
continue;
|
|
|
|
if (r->handles[i]) {
|
|
drm_dbg_kms(dev, "buffer object handle for unused plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (r->pitches[i]) {
|
|
drm_dbg_kms(dev, "non-zero pitch for unused plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (r->offsets[i]) {
|
|
drm_dbg_kms(dev, "non-zero offset for unused plane %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct drm_framebuffer *
|
|
meson_internal_framebuffer_create(struct drm_device *dev,
|
|
const struct drm_mode_fb_cmd2 *r,
|
|
struct drm_file *file_priv)
|
|
{
|
|
struct drm_mode_config *config = &dev->mode_config;
|
|
struct drm_framebuffer *fb;
|
|
int ret;
|
|
|
|
if (r->flags & ~(DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS)) {
|
|
drm_dbg_kms(dev, "bad framebuffer flags 0x%08x\n", r->flags);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (config->min_width > r->width || r->width > config->max_width) {
|
|
drm_dbg_kms(dev, "bad framebuffer width %d, should be >= %d && <= %d\n",
|
|
r->width, config->min_width, config->max_width);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
if (config->min_height > r->height || r->height > config->max_height) {
|
|
drm_dbg_kms(dev, "bad framebuffer height %d, should be >= %d && <= %d\n",
|
|
r->height, config->min_height, config->max_height);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (r->flags & DRM_MODE_FB_MODIFIERS &&
|
|
dev->mode_config.fb_modifiers_not_supported) {
|
|
drm_dbg_kms(dev, "driver does not support fb modifiers\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
ret = meson_framebuffer_check(dev, r);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
fb = dev->mode_config.funcs->fb_create(dev, file_priv, r);
|
|
if (IS_ERR(fb)) {
|
|
drm_dbg_kms(dev, "could not create framebuffer\n");
|
|
return fb;
|
|
}
|
|
|
|
return fb;
|
|
}
|
|
|
|
static int meson_mode_addfb2(struct drm_device *dev,
|
|
void *data, struct drm_file *file_priv)
|
|
{
|
|
struct drm_mode_fb_cmd2 *r = data;
|
|
struct drm_framebuffer *fb;
|
|
|
|
if (!drm_core_check_feature(dev, DRIVER_MODESET))
|
|
return -EOPNOTSUPP;
|
|
|
|
fb = meson_internal_framebuffer_create(dev, r, file_priv);
|
|
if (IS_ERR(fb))
|
|
return PTR_ERR(fb);
|
|
|
|
drm_dbg_kms(dev, "[FB:%d]\n", fb->base.id);
|
|
r->fb_id = fb->base.id;
|
|
|
|
/* Transfer ownership to the filp for reaping on close */
|
|
mutex_lock(&file_priv->fbs_lock);
|
|
list_add(&fb->filp_head, &file_priv->fbs);
|
|
mutex_unlock(&file_priv->fbs_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int am_meson_mode_addfb2_ioctl(struct drm_device *dev,
|
|
void *data, struct drm_file *file_priv)
|
|
{
|
|
#ifdef __BIG_ENDIAN
|
|
if (!dev->mode_config.quirk_addfb_prefer_host_byte_order) {
|
|
/*
|
|
* Drivers must set the
|
|
* quirk_addfb_prefer_host_byte_order quirk to make
|
|
* the drm_mode_addfb() compat code work correctly on
|
|
* bigendian machines.
|
|
*
|
|
* If they don't they interpret pixel_format values
|
|
* incorrectly for bug compatibility, which in turn
|
|
* implies the ADDFB2 ioctl does not work correctly
|
|
* then. So block it to make userspace fallback to
|
|
* ADDFB.
|
|
*/
|
|
DRM_DEBUG_KMS("addfb2 broken on bigendian");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
#endif
|
|
|
|
return meson_mode_addfb2(dev, data, file_priv);
|
|
}
|