/*
 * Copyright (C) 2013 Google, Inc.
 * adf_modeinfo_{set_name,set_vrefresh} modified from
 * drivers/gpu/drm/drm_modes.c
 * adf_format_validate_yuv modified from framebuffer_check in
 * drivers/gpu/drm/drm_crtc.c
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/device.h>
#include <linux/idr.h>
#include <linux/highmem.h>
#include <linux/memblock.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include <video/adf_format.h>

#include "sw_sync.h"
#include "sync.h"

#include "adf.h"
#include "adf_fops.h"
#include "adf_sysfs.h"

#define CREATE_TRACE_POINTS
#include "adf_trace.h"

#define ADF_SHORT_FENCE_TIMEOUT (1 * MSEC_PER_SEC)
#define ADF_LONG_FENCE_TIMEOUT (10 * MSEC_PER_SEC)

static DEFINE_IDR(adf_devices);

static void adf_fence_wait(struct adf_device *dev, struct sync_fence *fence)
{
	/* sync_fence_wait() dumps debug information on timeout.  Experience
	   has shown that if the pipeline gets stuck, a short timeout followed
	   by a longer one provides useful information for debugging. */
	int err = sync_fence_wait(fence, ADF_SHORT_FENCE_TIMEOUT);
	if (err >= 0)
		return;

	if (err == -ETIME)
		err = sync_fence_wait(fence, ADF_LONG_FENCE_TIMEOUT);

	if (err < 0)
		dev_warn(&dev->base.dev, "error waiting on fence: %d\n", err);
}

void adf_buffer_cleanup(struct adf_buffer *buf)
{
	size_t i;
	for (i = 0; i < ARRAY_SIZE(buf->dma_bufs); i++)
		if (buf->dma_bufs[i])
			dma_buf_put(buf->dma_bufs[i]);

	if (buf->acquire_fence)
		sync_fence_put(buf->acquire_fence);
}

void adf_buffer_mapping_cleanup(struct adf_buffer_mapping *mapping,
		struct adf_buffer *buf)
{
	/* calling adf_buffer_mapping_cleanup() is safe even if mapping is
	   uninitialized or partially-initialized, as long as it was
	   zeroed on allocation */
	size_t i;
	for (i = 0; i < ARRAY_SIZE(mapping->sg_tables); i++) {
		if (mapping->sg_tables[i])
			dma_buf_unmap_attachment(mapping->attachments[i],
					mapping->sg_tables[i], DMA_TO_DEVICE);
		if (mapping->attachments[i])
			dma_buf_detach(buf->dma_bufs[i],
					mapping->attachments[i]);
	}
}

void adf_post_cleanup(struct adf_device *dev, struct adf_pending_post *post)
{
	size_t i;

	if (post->state)
		dev->ops->state_free(dev, post->state);

	for (i = 0; i < post->config.n_bufs; i++) {
		adf_buffer_mapping_cleanup(&post->config.mappings[i],
				&post->config.bufs[i]);
		adf_buffer_cleanup(&post->config.bufs[i]);
	}

	kfree(post->config.custom_data);
	kfree(post->config.mappings);
	kfree(post->config.bufs);
	kfree(post);
}

static void adf_sw_advance_timeline(struct adf_device *dev)
{
#ifdef CONFIG_SW_SYNC
	sw_sync_timeline_inc(dev->timeline, 1);
#else
	BUG();
#endif
}

static void adf_post_work_func(struct kthread_work *work)
{
	struct adf_device *dev =
			container_of(work, struct adf_device, post_work);
	struct adf_pending_post *post, *next;
	struct list_head saved_list;

	mutex_lock(&dev->post_lock);
	memcpy(&saved_list, &dev->post_list, sizeof(saved_list));
	list_replace_init(&dev->post_list, &saved_list);
	mutex_unlock(&dev->post_lock);

	list_for_each_entry_safe(post, next, &saved_list, head) {
		int i;

		for (i = 0; i < post->config.n_bufs; i++) {
			struct sync_fence *fence =
					post->config.bufs[i].acquire_fence;
			if (fence)
				adf_fence_wait(dev, fence);
		}

		dev->ops->post(dev, &post->config, post->state);

		if (dev->ops->advance_timeline)
			dev->ops->advance_timeline(dev, &post->config,
					post->state);
		else
			adf_sw_advance_timeline(dev);

		list_del(&post->head);
		if (dev->onscreen)
			adf_post_cleanup(dev, dev->onscreen);
		dev->onscreen = post;
	}
}

void adf_attachment_free(struct adf_attachment_list *attachment)
{
	list_del(&attachment->head);
	kfree(attachment);
}

struct adf_event_refcount *adf_obj_find_event_refcount(struct adf_obj *obj,
		enum adf_event_type type)
{
	struct rb_root *root = &obj->event_refcount;
	struct rb_node **new = &(root->rb_node);
	struct rb_node *parent = NULL;
	struct adf_event_refcount *refcount;

	while (*new) {
		refcount = container_of(*new, struct adf_event_refcount, node);
		parent = *new;

		if (refcount->type > type)
			new = &(*new)->rb_left;
		else if (refcount->type < type)
			new = &(*new)->rb_right;
		else
			return refcount;
	}

	refcount = kzalloc(sizeof(*refcount), GFP_KERNEL);
	if (!refcount)
		return NULL;
	refcount->type = type;

	rb_link_node(&refcount->node, parent, new);
	rb_insert_color(&refcount->node, root);
	return refcount;
}

/**
 * adf_event_get - increase the refcount for an event
 *
 * @obj: the object that produces the event
 * @type: the event type
 *
 * ADF will call the object's set_event() op if needed.  ops are allowed
 * to sleep, so adf_event_get() must NOT be called from an atomic context.
 *
 * Returns 0 if successful, or -%EINVAL if the object does not support the
 * requested event type.
 */
int adf_event_get(struct adf_obj *obj, enum adf_event_type type)
{
	struct adf_event_refcount *refcount;
	int old_refcount;
	int ret;

	ret = adf_obj_check_supports_event(obj, type);
	if (ret < 0)
		return ret;

	mutex_lock(&obj->event_lock);

	refcount = adf_obj_find_event_refcount(obj, type);
	if (!refcount) {
		ret = -ENOMEM;
		goto done;
	}

	old_refcount = refcount->refcount++;

	if (old_refcount == 0) {
		obj->ops->set_event(obj, type, true);
		trace_adf_event_enable(obj, type);
	}

done:
	mutex_unlock(&obj->event_lock);
	return ret;
}
EXPORT_SYMBOL(adf_event_get);

/**
 * adf_event_put - decrease the refcount for an event
 *
 * @obj: the object that produces the event
 * @type: the event type
 *
 * ADF will call the object's set_event() op if needed.  ops are allowed
 * to sleep, so adf_event_put() must NOT be called from an atomic context.
 *
 * Returns 0 if successful, -%EINVAL if the object does not support the
 * requested event type, or -%EALREADY if the refcount is already 0.
 */
int adf_event_put(struct adf_obj *obj, enum adf_event_type type)
{
	struct adf_event_refcount *refcount;
	int old_refcount;
	int ret;

	ret = adf_obj_check_supports_event(obj, type);
	if (ret < 0)
		return ret;


	mutex_lock(&obj->event_lock);

	refcount = adf_obj_find_event_refcount(obj, type);
	if (!refcount) {
		ret = -ENOMEM;
		goto done;
	}

	old_refcount = refcount->refcount--;

	if (WARN_ON(old_refcount == 0)) {
		refcount->refcount++;
		ret = -EALREADY;
	} else if (old_refcount == 1) {
		obj->ops->set_event(obj, type, false);
		trace_adf_event_disable(obj, type);
	}

done:
	mutex_unlock(&obj->event_lock);
	return ret;
}
EXPORT_SYMBOL(adf_event_put);

/**
 * adf_vsync_wait - wait for a vsync event on a display interface
 *
 * @intf: the display interface
 * @timeout: timeout in jiffies (0 = wait indefinitely)
 *
 * adf_vsync_wait() may sleep, so it must NOT be called from an atomic context.
 *
 * This function returns -%ERESTARTSYS if it is interrupted by a signal.
 * If @timeout == 0 then this function returns 0 on vsync. If @timeout > 0 then
 * this function returns the number of remaining jiffies or -%ETIMEDOUT on
 * timeout.
 */
int adf_vsync_wait(struct adf_interface *intf, long timeout)
{
	ktime_t timestamp;
	int ret;
	unsigned long flags;

	read_lock_irqsave(&intf->vsync_lock, flags);
	timestamp = intf->vsync_timestamp;
	read_unlock_irqrestore(&intf->vsync_lock, flags);

	adf_vsync_get(intf);
	if (timeout) {
		ret = wait_event_interruptible_timeout(intf->vsync_wait,
				!ktime_equal(timestamp,
						intf->vsync_timestamp),
				msecs_to_jiffies(timeout));
		if (ret == 0 && ktime_equal(timestamp, intf->vsync_timestamp))
			ret = -ETIMEDOUT;
	} else {
		ret = wait_event_interruptible(intf->vsync_wait,
				!ktime_equal(timestamp,
						intf->vsync_timestamp));
	}
	adf_vsync_put(intf);

	return ret;
}
EXPORT_SYMBOL(adf_vsync_wait);

static void adf_event_queue(struct adf_obj *obj, struct adf_event *event)
{
	struct adf_file *file;
	unsigned long flags;

	trace_adf_event(obj, event->type);

	spin_lock_irqsave(&obj->file_lock, flags);

	list_for_each_entry(file, &obj->file_list, head)
		if (test_bit(event->type, file->event_subscriptions))
			adf_file_queue_event(file, event);

	spin_unlock_irqrestore(&obj->file_lock, flags);
}

/**
 * adf_event_notify - notify userspace of a driver-private event
 *
 * @obj: the ADF object that produced the event
 * @event: the event
 *
 * adf_event_notify() may be called safely from an atomic context.  It will
 * copy @event if needed, so @event may point to a variable on the stack.
 *
 * Drivers must NOT call adf_event_notify() for vsync and hotplug events.
 * ADF provides adf_vsync_notify() and
 * adf_hotplug_notify_{connected,disconnected}() for these events.
 */
int adf_event_notify(struct adf_obj *obj, struct adf_event *event)
{
	if (WARN_ON(event->type == ADF_EVENT_VSYNC ||
			event->type == ADF_EVENT_HOTPLUG))
		return -EINVAL;

	adf_event_queue(obj, event);
	return 0;
}
EXPORT_SYMBOL(adf_event_notify);

/**
 * adf_vsync_notify - notify ADF of a display interface's vsync event
 *
 * @intf: the display interface
 * @timestamp: the time the vsync occurred
 *
 * adf_vsync_notify() may be called safely from an atomic context.
 */
void adf_vsync_notify(struct adf_interface *intf, ktime_t timestamp)
{
	unsigned long flags;
	struct adf_vsync_event event;

	write_lock_irqsave(&intf->vsync_lock, flags);
	intf->vsync_timestamp = timestamp;
	write_unlock_irqrestore(&intf->vsync_lock, flags);

	wake_up_interruptible_all(&intf->vsync_wait);

	event.base.type = ADF_EVENT_VSYNC;
	event.base.length = sizeof(event);
	event.timestamp = ktime_to_ns(timestamp);
	adf_event_queue(&intf->base, &event.base);
}
EXPORT_SYMBOL(adf_vsync_notify);

void adf_hotplug_notify(struct adf_interface *intf, bool connected,
		struct drm_mode_modeinfo *modelist, size_t n_modes)
{
	unsigned long flags;
	struct adf_hotplug_event event;
	struct drm_mode_modeinfo *old_modelist;

	write_lock_irqsave(&intf->hotplug_modelist_lock, flags);
	old_modelist = intf->modelist;
	intf->hotplug_detect = connected;
	intf->modelist = modelist;
	intf->n_modes = n_modes;
	write_unlock_irqrestore(&intf->hotplug_modelist_lock, flags);

	kfree(old_modelist);

	event.base.length = sizeof(event);
	event.base.type = ADF_EVENT_HOTPLUG;
	event.connected = connected;
	adf_event_queue(&intf->base, &event.base);
}

/**
 * adf_hotplug_notify_connected - notify ADF of a display interface being
 * connected to a display
 *
 * @intf: the display interface
 * @modelist: hardware modes supported by display
 * @n_modes: length of modelist
 *
 * @modelist is copied as needed, so it may point to a variable on the stack.
 *
 * adf_hotplug_notify_connected() may NOT be called safely from an atomic
 * context.
 *
 * Returns 0 on success or error code (<0) on error.
 */
int adf_hotplug_notify_connected(struct adf_interface *intf,
		struct drm_mode_modeinfo *modelist, size_t n_modes)
{
	struct drm_mode_modeinfo *modelist_copy;

	if (n_modes > ADF_MAX_MODES)
		return -ENOMEM;

	modelist_copy = kzalloc(sizeof(modelist_copy[0]) * n_modes,
			GFP_KERNEL);
	if (!modelist_copy)
		return -ENOMEM;
	memcpy(modelist_copy, modelist, sizeof(modelist_copy[0]) * n_modes);

	adf_hotplug_notify(intf, true, modelist_copy, n_modes);
	return 0;
}
EXPORT_SYMBOL(adf_hotplug_notify_connected);

/**
 * adf_hotplug_notify_disconnected - notify ADF of a display interface being
 * disconnected from a display
 *
 * @intf: the display interface
 *
 * adf_hotplug_notify_disconnected() may be called safely from an atomic
 * context.
 */
void adf_hotplug_notify_disconnected(struct adf_interface *intf)
{
	adf_hotplug_notify(intf, false, NULL, 0);
}
EXPORT_SYMBOL(adf_hotplug_notify_disconnected);

static int adf_obj_init(struct adf_obj *obj, enum adf_obj_type type,
		struct idr *idr, struct adf_device *parent,
		const struct adf_obj_ops *ops, const char *fmt, va_list args)
{
	int ret;

	if (ops && ops->supports_event && !ops->set_event) {
		pr_err("%s: %s implements supports_event but not set_event\n",
				__func__, adf_obj_type_str(type));
		return -EINVAL;
	}

	ret = idr_alloc(idr, obj, 0, 0, GFP_KERNEL);
	if (ret < 0) {
		pr_err("%s: allocating object id failed: %d\n", __func__, ret);
		return ret;
	}
	obj->id = ret;

	vscnprintf(obj->name, sizeof(obj->name), fmt, args);

	obj->type = type;
	obj->ops = ops;
	obj->parent = parent;
	mutex_init(&obj->event_lock);
	obj->event_refcount = RB_ROOT;
	spin_lock_init(&obj->file_lock);
	INIT_LIST_HEAD(&obj->file_list);
	return 0;
}

static void adf_obj_destroy(struct adf_obj *obj, struct idr *idr)
{
	struct rb_node *node = rb_first(&obj->event_refcount);

	while (node) {
		struct adf_event_refcount *refcount =
				container_of(node, struct adf_event_refcount,
						node);
		rb_erase(&refcount->node, &obj->event_refcount);
		kfree(refcount);
		node = rb_first(&obj->event_refcount);
	}

	mutex_destroy(&obj->event_lock);
	idr_remove(idr, obj->id);
}

/**
 * adf_device_init - initialize ADF-internal data for a display device
 * and create sysfs entries
 *
 * @dev: the display device
 * : the device's parent device
 * @ops: the device's associated ops
 * @fmt: formatting string for the display device's name
 *
 * @fmt specifies the device's sysfs filename and the name returned to
 * userspace through the %ADF_GET_DEVICE_DATA ioctl.
 *
 * Returns 0 on success or error code (<0) on failure.
 */
int adf_device_init(struct adf_device *dev, struct device *parent,
		const struct adf_device_ops *ops, const char *fmt, ...)
{
	int ret;
	va_list args;

	if (!ops->validate || !ops->post) {
		pr_err("%s: device must implement validate and post\n",
				__func__);
		return -EINVAL;
	}

	if (!ops->complete_fence && !ops->advance_timeline) {
		if (!IS_ENABLED(CONFIG_SW_SYNC)) {
			pr_err("%s: device requires sw_sync but it is not enabled in the kernel\n",
					__func__);
			return -EINVAL;
		}
	} else if (!(ops->complete_fence && ops->advance_timeline)) {
		pr_err("%s: device must implement both complete_fence and advance_timeline, or implement neither\n",
				__func__);
		return -EINVAL;
	}

	memset(dev, 0, sizeof(*dev));

	va_start(args, fmt);
	ret = adf_obj_init(&dev->base, ADF_OBJ_DEVICE, &adf_devices, dev,
			&ops->base, fmt, args);
	va_end(args);
	if (ret < 0)
		return ret;

	dev->dev = parent;
	dev->ops = ops;
	idr_init(&dev->overlay_engines);
	idr_init(&dev->interfaces);
	mutex_init(&dev->client_lock);
	INIT_LIST_HEAD(&dev->post_list);
	mutex_init(&dev->post_lock);
	init_kthread_worker(&dev->post_worker);
	INIT_LIST_HEAD(&dev->attached);
	INIT_LIST_HEAD(&dev->attach_allowed);

	dev->post_thread = kthread_run(kthread_worker_fn,
			&dev->post_worker, dev->base.name);
	if (IS_ERR(dev->post_thread)) {
		ret = PTR_ERR(dev->post_thread);
		dev->post_thread = NULL;

		pr_err("%s: failed to run config posting thread: %d\n",
				__func__, ret);
		goto err;
	}
	init_kthread_work(&dev->post_work, adf_post_work_func);

	ret = adf_device_sysfs_init(dev);
	if (ret < 0)
		goto err;

	return 0;

err:
	adf_device_destroy(dev);
	return ret;
}
EXPORT_SYMBOL(adf_device_init);

/**
 * adf_device_destroy - clean up ADF-internal data for a display device
 *
 * @dev: the display device
 */
void adf_device_destroy(struct adf_device *dev)
{
	struct adf_attachment_list *entry, *next;

	idr_destroy(&dev->interfaces);
	idr_destroy(&dev->overlay_engines);

	if (dev->post_thread) {
		flush_kthread_worker(&dev->post_worker);
		kthread_stop(dev->post_thread);
	}

	if (dev->onscreen)
		adf_post_cleanup(dev, dev->onscreen);
	adf_device_sysfs_destroy(dev);
	list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) {
		adf_attachment_free(entry);
	}
	list_for_each_entry_safe(entry, next, &dev->attached, head) {
		adf_attachment_free(entry);
	}
	mutex_destroy(&dev->post_lock);
	mutex_destroy(&dev->client_lock);

	if (dev->timeline)
		sync_timeline_destroy(&dev->timeline->obj);

	adf_obj_destroy(&dev->base, &adf_devices);
}
EXPORT_SYMBOL(adf_device_destroy);

/**
 * adf_interface_init - initialize ADF-internal data for a display interface
 * and create sysfs entries
 *
 * @intf: the display interface
 * @dev: the interface's "parent" display device
 * @type: interface type (see enum @adf_interface_type)
 * @idx: which interface of type @type;
 *	e.g. interface DSI.1 -> @type=%ADF_INTF_TYPE_DSI, @idx=1
 * @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values)
 * @ops: the interface's associated ops
 * @fmt: formatting string for the display interface's name
 *
 * @dev must have previously been initialized with adf_device_init().
 *
 * @fmt affects the name returned to userspace through the
 * %ADF_GET_INTERFACE_DATA ioctl.  It does not affect the sysfs filename,
 * which is derived from @dev's name.
 *
 * Returns 0 on success or error code (<0) on failure.
 */
int adf_interface_init(struct adf_interface *intf, struct adf_device *dev,
		enum adf_interface_type type, u32 idx, u32 flags,
		const struct adf_interface_ops *ops, const char *fmt, ...)
{
	int ret;
	va_list args;
	const u32 allowed_flags = ADF_INTF_FLAG_PRIMARY |
			ADF_INTF_FLAG_EXTERNAL;

	if (dev->n_interfaces == ADF_MAX_INTERFACES) {
		pr_err("%s: parent device %s has too many interfaces\n",
				__func__, dev->base.name);
		return -ENOMEM;
	}

	if (type >= ADF_INTF_MEMORY && type <= ADF_INTF_TYPE_DEVICE_CUSTOM) {
		pr_err("%s: invalid interface type %u\n", __func__, type);
		return -EINVAL;
	}

	if (flags & ~allowed_flags) {
		pr_err("%s: invalid interface flags 0x%X\n", __func__,
				flags & ~allowed_flags);
		return -EINVAL;
	}

	memset(intf, 0, sizeof(*intf));

	va_start(args, fmt);
	ret = adf_obj_init(&intf->base, ADF_OBJ_INTERFACE, &dev->interfaces,
			dev, ops ? &ops->base : NULL, fmt, args);
	va_end(args);
	if (ret < 0)
		return ret;

	intf->type = type;
	intf->idx = idx;
	intf->flags = flags;
	intf->ops = ops;
	intf->dpms_state = DRM_MODE_DPMS_OFF;
	init_waitqueue_head(&intf->vsync_wait);
	rwlock_init(&intf->vsync_lock);
	rwlock_init(&intf->hotplug_modelist_lock);

	ret = adf_interface_sysfs_init(intf);
	if (ret < 0)
		goto err;
	dev->n_interfaces++;

	return 0;

err:
	adf_obj_destroy(&intf->base, &dev->interfaces);
	return ret;
}
EXPORT_SYMBOL(adf_interface_init);

/**
 * adf_interface_destroy - clean up ADF-internal data for a display interface
 *
 * @intf: the display interface
 */
void adf_interface_destroy(struct adf_interface *intf)
{
	struct adf_device *dev = adf_interface_parent(intf);
	struct adf_attachment_list *entry, *next;

	mutex_lock(&dev->client_lock);
	list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) {
		if (entry->attachment.interface == intf) {
			adf_attachment_free(entry);
			dev->n_attach_allowed--;
		}
	}
	list_for_each_entry_safe(entry, next, &dev->attached, head) {
		if (entry->attachment.interface == intf) {
			adf_device_detach_op(dev,
					entry->attachment.overlay_engine, intf);
			adf_attachment_free(entry);
			dev->n_attached--;
		}
	}
	kfree(intf->modelist);
	adf_interface_sysfs_destroy(intf);
	adf_obj_destroy(&intf->base, &dev->interfaces);
	dev->n_interfaces--;
	mutex_unlock(&dev->client_lock);
}
EXPORT_SYMBOL(adf_interface_destroy);

static bool adf_overlay_engine_has_custom_formats(
		const struct adf_overlay_engine_ops *ops)
{
	size_t i;
	for (i = 0; i < ops->n_supported_formats; i++)
		if (!adf_format_is_standard(ops->supported_formats[i]))
			return true;
	return false;
}

/**
 * adf_overlay_engine_init - initialize ADF-internal data for an
 * overlay engine and create sysfs entries
 *
 * @eng: the overlay engine
 * @dev: the overlay engine's "parent" display device
 * @ops: the overlay engine's associated ops
 * @fmt: formatting string for the overlay engine's name
 *
 * @dev must have previously been initialized with adf_device_init().
 *
 * @fmt affects the name returned to userspace through the
 * %ADF_GET_OVERLAY_ENGINE_DATA ioctl.  It does not affect the sysfs filename,
 * which is derived from @dev's name.
 *
 * Returns 0 on success or error code (<0) on failure.
 */
int adf_overlay_engine_init(struct adf_overlay_engine *eng,
		struct adf_device *dev,
		const struct adf_overlay_engine_ops *ops, const char *fmt, ...)
{
	int ret;
	va_list args;

	if (!ops->supported_formats) {
		pr_err("%s: overlay engine must support at least one format\n",
				__func__);
		return -EINVAL;
	}

	if (ops->n_supported_formats > ADF_MAX_SUPPORTED_FORMATS) {
		pr_err("%s: overlay engine supports too many formats\n",
				__func__);
		return -EINVAL;
	}

	if (adf_overlay_engine_has_custom_formats(ops) &&
			!dev->ops->validate_custom_format) {
		pr_err("%s: overlay engine has custom formats but parent device %s does not implement validate_custom_format\n",
				__func__, dev->base.name);
		return -EINVAL;
	}

	memset(eng, 0, sizeof(*eng));

	va_start(args, fmt);
	ret = adf_obj_init(&eng->base, ADF_OBJ_OVERLAY_ENGINE,
			&dev->overlay_engines, dev, &ops->base, fmt, args);
	va_end(args);
	if (ret < 0)
		return ret;

	eng->ops = ops;

	ret = adf_overlay_engine_sysfs_init(eng);
	if (ret < 0)
		goto err;

	return 0;

err:
	adf_obj_destroy(&eng->base, &dev->overlay_engines);
	return ret;
}
EXPORT_SYMBOL(adf_overlay_engine_init);

/**
 * adf_interface_destroy - clean up ADF-internal data for an overlay engine
 *
 * @eng: the overlay engine
 */
void adf_overlay_engine_destroy(struct adf_overlay_engine *eng)
{
	struct adf_device *dev = adf_overlay_engine_parent(eng);
	struct adf_attachment_list *entry, *next;

	mutex_lock(&dev->client_lock);
	list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) {
		if (entry->attachment.overlay_engine == eng) {
			adf_attachment_free(entry);
			dev->n_attach_allowed--;
		}
	}
	list_for_each_entry_safe(entry, next, &dev->attached, head) {
		if (entry->attachment.overlay_engine == eng) {
			adf_device_detach_op(dev, eng,
					entry->attachment.interface);
			adf_attachment_free(entry);
			dev->n_attached--;
		}
	}
	adf_overlay_engine_sysfs_destroy(eng);
	adf_obj_destroy(&eng->base, &dev->overlay_engines);
	mutex_unlock(&dev->client_lock);
}
EXPORT_SYMBOL(adf_overlay_engine_destroy);

struct adf_attachment_list *adf_attachment_find(struct list_head *list,
		struct adf_overlay_engine *eng, struct adf_interface *intf)
{
	struct adf_attachment_list *entry;
	list_for_each_entry(entry, list, head) {
		if (entry->attachment.interface == intf &&
				entry->attachment.overlay_engine == eng)
			return entry;
	}
	return NULL;
}

int adf_attachment_validate(struct adf_device *dev,
		struct adf_overlay_engine *eng, struct adf_interface *intf)
{
	struct adf_device *intf_dev = adf_interface_parent(intf);
	struct adf_device *eng_dev = adf_overlay_engine_parent(eng);

	if (intf_dev != dev) {
		dev_err(&dev->base.dev, "can't attach interface %s belonging to device %s\n",
				intf->base.name, intf_dev->base.name);
		return -EINVAL;
	}

	if (eng_dev != dev) {
		dev_err(&dev->base.dev, "can't attach overlay engine %s belonging to device %s\n",
				eng->base.name, eng_dev->base.name);
		return -EINVAL;
	}

	return 0;
}

/**
 * adf_attachment_allow - add a new entry to the list of allowed
 * attachments
 *
 * @dev: the parent device
 * @eng: the overlay engine
 * @intf: the interface
 *
 * adf_attachment_allow() indicates that the underlying display hardware allows
 * @intf to scan out @eng's output.  It is intended to be called at
 * driver initialization for each supported overlay engine + interface pair.
 *
 * Returns 0 on success, -%EALREADY if the entry already exists, or -errno on
 * any other failure.
 */
int adf_attachment_allow(struct adf_device *dev,
		struct adf_overlay_engine *eng, struct adf_interface *intf)
{
	int ret;
	struct adf_attachment_list *entry = NULL;

	ret = adf_attachment_validate(dev, eng, intf);
	if (ret < 0)
		return ret;

	mutex_lock(&dev->client_lock);

	if (dev->n_attach_allowed == ADF_MAX_ATTACHMENTS) {
		ret = -ENOMEM;
		goto done;
	}

	if (adf_attachment_find(&dev->attach_allowed, eng, intf)) {
		ret = -EALREADY;
		goto done;
	}

	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
	if (!entry) {
		ret = -ENOMEM;
		goto done;
	}

	entry->attachment.interface = intf;
	entry->attachment.overlay_engine = eng;
	list_add_tail(&entry->head, &dev->attach_allowed);
	dev->n_attach_allowed++;

done:
	mutex_unlock(&dev->client_lock);
	if (ret < 0)
		kfree(entry);

	return ret;
}
EXPORT_SYMBOL(adf_attachment_allow);

/**
 * adf_obj_type_str - string representation of an adf_obj_type
 *
 * @type: the object type
 */
const char *adf_obj_type_str(enum adf_obj_type type)
{
	switch (type) {
	case ADF_OBJ_OVERLAY_ENGINE:
		return "overlay engine";

	case ADF_OBJ_INTERFACE:
		return "interface";

	case ADF_OBJ_DEVICE:
		return "device";

	default:
		return "unknown";
	}
}
EXPORT_SYMBOL(adf_obj_type_str);

/**
 * adf_interface_type_str - string representation of an adf_interface's type
 *
 * @intf: the interface
 */
const char *adf_interface_type_str(struct adf_interface *intf)
{
	switch (intf->type) {
	case ADF_INTF_DSI:
		return "DSI";

	case ADF_INTF_eDP:
		return "eDP";

	case ADF_INTF_DPI:
		return "DPI";

	case ADF_INTF_VGA:
		return "VGA";

	case ADF_INTF_DVI:
		return "DVI";

	case ADF_INTF_HDMI:
		return "HDMI";

	case ADF_INTF_MEMORY:
		return "memory";

	default:
		if (intf->type >= ADF_INTF_TYPE_DEVICE_CUSTOM) {
			if (intf->ops && intf->ops->type_str)
				return intf->ops->type_str(intf);
			return "custom";
		}
		return "unknown";
	}
}
EXPORT_SYMBOL(adf_interface_type_str);

/**
 * adf_event_type_str - string representation of an adf_event_type
 *
 * @obj: ADF object that produced the event
 * @type: event type
 */
const char *adf_event_type_str(struct adf_obj *obj, enum adf_event_type type)
{
	switch (type) {
	case ADF_EVENT_VSYNC:
		return "vsync";

	case ADF_EVENT_HOTPLUG:
		return "hotplug";

	default:
		if (type >= ADF_EVENT_DEVICE_CUSTOM) {
			if (obj->ops && obj->ops->event_type_str)
				return obj->ops->event_type_str(obj, type);
			return "custom";
		}
		return "unknown";
	}
}
EXPORT_SYMBOL(adf_event_type_str);

/**
 * adf_format_str - string representation of an ADF/DRM fourcc format
 *
 * @format: format fourcc
 * @buf: target buffer for the format's string representation
 */
void adf_format_str(u32 format, char buf[ADF_FORMAT_STR_SIZE])
{
	buf[0] = format & 0xFF;
	buf[1] = (format >> 8) & 0xFF;
	buf[2] = (format >> 16) & 0xFF;
	buf[3] = (format >> 24) & 0xFF;
	buf[4] = '\0';
}
EXPORT_SYMBOL(adf_format_str);

/**
 * adf_format_validate_yuv - validate the number and size of planes in buffers
 * with a custom YUV format.
 *
 * @dev: ADF device performing the validation
 * @buf: buffer to validate
 * @num_planes: expected number of planes
 * @hsub: expected horizontal chroma subsampling factor, in pixels
 * @vsub: expected vertical chroma subsampling factor, in pixels
 * @cpp: expected bytes per pixel for each plane (length @num_planes)
 *
 * adf_format_validate_yuv() is intended to be called as a helper from @dev's
 * validate_custom_format() op.
 *
 * Returns 0 if @buf has the expected number of planes and each plane
 * has sufficient size, or -EINVAL otherwise.
 */
int adf_format_validate_yuv(struct adf_device *dev, struct adf_buffer *buf,
		u8 num_planes, u8 hsub, u8 vsub, u8 cpp[])
{
	u8 i;

	if (num_planes != buf->n_planes) {
		char format_str[ADF_FORMAT_STR_SIZE];
		adf_format_str(buf->format, format_str);
		dev_err(&dev->base.dev, "%u planes expected for format %s but %u planes provided\n",
				num_planes, format_str, buf->n_planes);
		return -EINVAL;
	}

	if (buf->w == 0 || buf->w % hsub) {
		dev_err(&dev->base.dev, "bad buffer width %u\n", buf->w);
		return -EINVAL;
	}

	if (buf->h == 0 || buf->h % vsub) {
		dev_err(&dev->base.dev, "bad buffer height %u\n", buf->h);
		return -EINVAL;
	}

	for (i = 0; i < num_planes; i++) {
		u32 width = buf->w / (i != 0 ? hsub : 1);
		u32 height = buf->h / (i != 0 ? vsub : 1);
		u8 cpp = adf_format_plane_cpp(buf->format, i);
		u32 last_line_size;

		if (buf->pitch[i] < (u64) width * cpp) {
			dev_err(&dev->base.dev, "plane %u pitch is shorter than buffer width (pitch = %u, width = %u, bpp = %u)\n",
					i, buf->pitch[i], width, cpp * 8);
			return -EINVAL;
		}

		switch (dev->ops->quirks.buffer_padding) {
		case ADF_BUFFER_PADDED_TO_PITCH:
			last_line_size = buf->pitch[i];
			break;

		case ADF_BUFFER_UNPADDED:
			last_line_size = width * cpp;
			break;

		default:
			BUG();
		}

		if ((u64) (height - 1) * buf->pitch[i] + last_line_size +
				buf->offset[i] > buf->dma_bufs[i]->size) {
			dev_err(&dev->base.dev, "plane %u buffer too small (height = %u, pitch = %u, offset = %u, size = %zu)\n",
					i, height, buf->pitch[i],
					buf->offset[i], buf->dma_bufs[i]->size);
			return -EINVAL;
		}
	}

	return 0;
}
EXPORT_SYMBOL(adf_format_validate_yuv);

/**
 * adf_modeinfo_set_name - sets the name of a mode from its display resolution
 *
 * @mode: mode
 *
 * adf_modeinfo_set_name() fills in @mode->name in the format
 * "[hdisplay]x[vdisplay](i)".  It is intended to help drivers create
 * ADF/DRM-style modelists from other mode formats.
 */
void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode)
{
	bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;

	snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d%s",
		 mode->hdisplay, mode->vdisplay,
		 interlaced ? "i" : "");
}
EXPORT_SYMBOL(adf_modeinfo_set_name);

/**
 * adf_modeinfo_set_vrefresh - sets the vrefresh of a mode from its other
 * timing data
 *
 * @mode: mode
 *
 * adf_modeinfo_set_vrefresh() calculates @mode->vrefresh from
 * @mode->{h,v}display and @mode->flags.  It is intended to help drivers
 * create ADF/DRM-style modelists from other mode formats.
 */
void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode)
{
	int refresh = 0;
	unsigned int calc_val;

	if (mode->vrefresh > 0)
		return;

	if (mode->htotal <= 0 || mode->vtotal <= 0)
		return;

	/* work out vrefresh the value will be x1000 */
	calc_val = (mode->clock * 1000);
	calc_val /= mode->htotal;
	refresh = (calc_val + mode->vtotal / 2) / mode->vtotal;

	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
		refresh *= 2;
	if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
		refresh /= 2;
	if (mode->vscan > 1)
		refresh /= mode->vscan;

	mode->vrefresh = refresh;
}
EXPORT_SYMBOL(adf_modeinfo_set_vrefresh);

static int __init adf_init(void)
{
	int err;

	err = adf_sysfs_init();
	if (err < 0)
		return err;

	return 0;
}

static void __exit adf_exit(void)
{
	adf_sysfs_destroy();
}

module_init(adf_init);
module_exit(adf_exit);