// 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"

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);
}
