/**
 * adf_fbdev_init - initialize helper to wrap ADF device in fbdev API
 *
 * @fbdev: the fbdev helper
 * @interface: the ADF interface that will display the framebuffer
 * @eng: the ADF overlay engine that will scan out the framebuffer
 * @xres_virtual: the virtual width of the framebuffer
 * @yres_virtual: the virtual height of the framebuffer
 * @format: the format of the framebuffer
 * @fbops: the device's fbdev ops
 * @fmt: formatting for the framebuffer identification string
 * @...: variable arguments
 *
 * @format must be a standard, non-indexed RGB format, i.e.,
 * adf_format_is_rgb(@format) && @format != @DRM_FORMAT_C8.
 *
 * Returns 0 on success or -errno on failure.
 */
int adf_fbdev_init(struct adf_fbdev *fbdev, struct adf_interface *interface,
		struct adf_overlay_engine *eng,
		u16 xres_virtual, u16 yres_virtual, u32 format,
		struct fb_ops *fbops, const char *fmt, ...)
{
	struct adf_device *parent = adf_interface_parent(interface);
	struct device *dev = &parent->base.dev;
	u16 width_mm, height_mm;
	va_list args;
	int ret;

	if (!adf_format_is_rgb(format) ||
			format == DRM_FORMAT_C8) {
		dev_err(dev, "fbdev helper does not support format %u\n",
				format);
		return -EINVAL;
	}

	memset(fbdev, 0, sizeof(*fbdev));
	fbdev->intf = interface;
	fbdev->eng = eng;
	fbdev->info = framebuffer_alloc(0, dev);
	if (!fbdev->info) {
		dev_err(dev, "allocating framebuffer device failed\n");
		return -ENOMEM;
	}
	mutex_init(&fbdev->refcount_lock);
	fbdev->default_xres_virtual = xres_virtual;
	fbdev->default_yres_virtual = yres_virtual;
	fbdev->default_format = format;

	fbdev->info->flags = FBINFO_FLAG_DEFAULT;
	ret = adf_interface_get_screen_size(interface, &width_mm, &height_mm);
	if (ret < 0) {
		width_mm = 0;
		height_mm = 0;
	}
	fbdev->info->var.width = width_mm;
	fbdev->info->var.height = height_mm;
	fbdev->info->var.activate = FB_ACTIVATE_VBL;
	va_start(args, fmt);
	vsnprintf(fbdev->info->fix.id, sizeof(fbdev->info->fix.id), fmt, args);
	va_end(args);
	fbdev->info->fix.type = FB_TYPE_PACKED_PIXELS;
	fbdev->info->fix.visual = FB_VISUAL_TRUECOLOR;
	fbdev->info->fix.xpanstep = 1;
	fbdev->info->fix.ypanstep = 1;
	INIT_LIST_HEAD(&fbdev->info->modelist);
	fbdev->info->fbops = fbops;
	fbdev->info->pseudo_palette = fbdev->pseudo_palette;
	fbdev->info->par = fbdev;

	ret = register_framebuffer(fbdev->info);
	if (ret < 0) {
		dev_err(dev, "registering framebuffer failed: %d\n", ret);
		return ret;
	}

	return 0;
}
/**
 * adf_interface_simple_post - flip to a single buffer
 *
 * @intf: interface targeted by the flip
 * @buf: buffer to display
 *
 * adf_interface_simple_post() can be used generically for simple display
 * configurations, since the client does not need to provide any driver-private
 * configuration data.
 *
 * adf_interface_simple_post() has the same copying semantics as
 * adf_device_post().
 *
 * On success, returns a sync fence which signals when the buffer is removed
 * from the screen.  On failure, returns ERR_PTR(-errno).
 */
struct sync_fence *adf_interface_simple_post(struct adf_interface *intf,
		struct adf_buffer *buf)
{
	size_t custom_data_size = 0;
	void *custom_data = NULL;
	struct sync_fence *ret;

	if (intf->ops && intf->ops->describe_simple_post) {
		int err;

		custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL);
		if (!custom_data) {
			ret = ERR_PTR(-ENOMEM);
			goto done;
		}

		err = intf->ops->describe_simple_post(intf, buf, custom_data,
				&custom_data_size);
		if (err < 0) {
			ret = ERR_PTR(err);
			goto done;
		}
	}

	ret = adf_device_post(adf_interface_parent(intf), &intf, 1, buf, 1,
			custom_data, custom_data_size);
done:
	kfree(custom_data);
	return ret;
}
/**
 * adf_interface_current_mode - get interface's current display mode
 *
 * @intf: the interface
 * @mode: returns the current mode
 */
void adf_interface_current_mode(struct adf_interface *intf,
		struct drm_mode_modeinfo *mode)
{
	struct adf_device *dev = adf_interface_parent(intf);

	mutex_lock(&dev->client_lock);
	memcpy(mode, &intf->current_mode, sizeof(*mode));
	mutex_unlock(&dev->client_lock);
}
/**
 * adf_interface_blank - get interface's current DPMS state
 *
 * @intf: the interface
 *
 * Returns one of %DRM_MODE_DPMS_*.
 */
u8 adf_interface_dpms_state(struct adf_interface *intf)
{
	struct adf_device *dev = adf_interface_parent(intf);
	u8 dpms_state;

	mutex_lock(&dev->client_lock);
	dpms_state = intf->dpms_state;
	mutex_unlock(&dev->client_lock);

	return dpms_state;
}
/**
 * adf_fbdev_open - default implementation of fbdev open op
 */
int adf_fbdev_open(struct fb_info *info, int user)
{
	struct adf_fbdev *fbdev = info->par;
	int ret;

	mutex_lock(&fbdev->refcount_lock);

	if (unlikely(fbdev->refcount == UINT_MAX)) {
		ret = -EMFILE;
		goto done;
	}

	if (!fbdev->refcount) {
		struct drm_mode_modeinfo mode;
		struct fb_videomode fbmode;
		struct adf_device *dev = adf_interface_parent(fbdev->intf);

		ret = adf_device_attach(dev, fbdev->eng, fbdev->intf);
		if (ret < 0 && ret != -EALREADY)
			goto done;

		ret = adf_fb_alloc(fbdev);
		if (ret < 0)
			goto done;

		adf_interface_current_mode(fbdev->intf, &mode);
		adf_modeinfo_to_fb_videomode(&mode, &fbmode);
		fb_videomode_to_var(&fbdev->info->var, &fbmode);

		adf_fbdev_set_format(fbdev, fbdev->default_format);
		adf_fbdev_fill_modelist(fbdev);
	}

	if (!fbdev_opened_once) {
		fbdev_opened_once = true;
	} else {
		ret = adf_fbdev_post(fbdev);
		if (ret < 0) {
			if (!fbdev->refcount)
				adf_fb_destroy(fbdev);
			goto done;
		}
	}

	fbdev->refcount++;
done:
	mutex_unlock(&fbdev->refcount_lock);
	return ret;
}
/**
 * adf_interface_screen_size - get size of screen connected to interface
 *
 * @intf: the interface
 * @width_mm: returns the screen width in mm
 * @height_mm: returns the screen width in mm
 *
 * Returns 0 on success or -errno on failure.
 */
int adf_interface_get_screen_size(struct adf_interface *intf, u16 *width_mm,
		u16 *height_mm)
{
	struct adf_device *dev = adf_interface_parent(intf);
	int ret;

	if (!intf->ops || !intf->ops->screen_size)
		return -EOPNOTSUPP;

	mutex_lock(&dev->client_lock);
	ret = intf->ops->screen_size(intf, width_mm, height_mm);
	mutex_unlock(&dev->client_lock);

	return ret;
}
/**
 * adf_interface_set_mode - set interface's display mode
 *
 * @intf: the interface
 * @mode: the new mode
 *
 * Returns 0 on success or -errno on failure.
 */
int adf_interface_set_mode(struct adf_interface *intf,
		struct drm_mode_modeinfo *mode)
{
	struct adf_device *dev = adf_interface_parent(intf);
	int ret = 0;

	if (!intf->ops || !intf->ops->modeset)
		return -EOPNOTSUPP;

	mutex_lock(&dev->client_lock);
	flush_kthread_worker(&dev->post_worker);

	ret = intf->ops->modeset(intf, mode);
	if (ret < 0)
		goto done;

	memcpy(&intf->current_mode, mode, sizeof(*mode));
done:
	mutex_unlock(&dev->client_lock);
	return ret;
}
/**
 * adf_fbdev_open - default implementation of fbdev open op
 */
int adf_fbdev_open(struct fb_info *info, int user)
{
	struct adf_fbdev *fbdev = info->par;
	int ret;

	if (!fbdev->open) {
		struct drm_mode_modeinfo mode;
		struct fb_videomode fbmode;
		struct adf_device *dev = adf_interface_parent(fbdev->intf);

		ret = adf_device_attach(dev, fbdev->eng, fbdev->intf);
		if (ret < 0 && ret != -EALREADY)
			return ret;

		ret = adf_fb_alloc(fbdev);
		if (ret < 0)
			return ret;

		adf_interface_current_mode(fbdev->intf, &mode);
		adf_modeinfo_to_fb_videomode(&mode, &fbmode);
		fb_videomode_to_var(&fbdev->info->var, &fbmode);

		adf_fbdev_set_format(fbdev, fbdev->default_format);
		adf_fbdev_fill_modelist(fbdev);
	}

	ret = adf_fbdev_post(fbdev);
	if (ret < 0) {
		if (!fbdev->open)
			adf_fb_destroy(fbdev);
		return ret;
	}

	fbdev->open = true;
	return 0;
}
/**
 * adf_interface_blank - set interface's DPMS state
 *
 * @intf: the interface
 * @state: one of %DRM_MODE_DPMS_*
 *
 * Returns 0 on success or -errno on failure.
 */
int adf_interface_blank(struct adf_interface *intf, u8 state)
{
	struct adf_device *dev = adf_interface_parent(intf);
	u8 prev_state;
	bool disable_vsync;
	bool enable_vsync;
	int ret = 0;
	struct adf_event_refcount *vsync_refcount;

	if (!intf->ops || !intf->ops->blank)
		return -EOPNOTSUPP;

	if (state > DRM_MODE_DPMS_OFF)
		return -EINVAL;

	mutex_lock(&dev->client_lock);
	if (state != DRM_MODE_DPMS_ON)
		flush_kthread_worker(&dev->post_worker);
	mutex_lock(&intf->base.event_lock);

	vsync_refcount = adf_obj_find_event_refcount(&intf->base,
			ADF_EVENT_VSYNC);
	if (!vsync_refcount) {
		ret = -ENOMEM;
		goto done;
	}

	prev_state = intf->dpms_state;
	if (prev_state == state) {
		ret = -EBUSY;
		goto done;
	}

	disable_vsync = vsync_active(prev_state) &&
			!vsync_active(state) &&
			vsync_refcount->refcount;
	enable_vsync = !vsync_active(prev_state) &&
			vsync_active(state) &&
			vsync_refcount->refcount;

	if (disable_vsync)
		intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC,
				false);

	ret = intf->ops->blank(intf, state);
	if (ret < 0) {
		if (disable_vsync)
			intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC,
					true);
		goto done;
	}

	if (enable_vsync)
		intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC,
				true);

	intf->dpms_state = state;
done:
	mutex_unlock(&intf->base.event_lock);
	mutex_unlock(&dev->client_lock);
	return ret;
}