/* -*- mode: c; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* vi: set ts=8 sw=8 sts=8: */
/*************************************************************************/ /*!
@File
@Codingstyle    LinuxKernel
@Copyright      Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@License        Dual MIT/GPLv2

The contents of this file are subject to the MIT license as set out below.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.

If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.

This License is also included in this distribution in the file called
"MIT-COPYING".

EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ /**************************************************************************/

#include <linux/module.h>
#include <linux/reservation.h>
#include <linux/version.h>
#include <linux/component.h>
#include <linux/of_platform.h>

#include <drm/drmP.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0))
#include <drm/drm_gem.h>
#endif

#include "tc_drv.h"
#include "pvrversion.h"

#include "drm_pdp_drv.h"
#include "drm_pdp_gem.h"
#include "pdp_drm.h"

#include "odin_defs.h"

#if defined(SUPPORT_PLATO_DISPLAY)
#include "plato_drv.h"
#include "pdp2_regs.h"
#include "pdp2_mmu_regs.h"
#endif

#define DRIVER_NAME "pdp"
#define DRIVER_DESC "Imagination Technologies PDP DRM Display Driver"
#define DRIVER_DATE "20150612"

#if defined(PDP_USE_ATOMIC)
#include <drm/drm_atomic_helper.h>

#define PVR_DRIVER_ATOMIC DRIVER_ATOMIC
#else
#define PVR_DRIVER_ATOMIC 0
#endif

static bool display_enable = true;

module_param(display_enable, bool, 0444);
MODULE_PARM_DESC(display_enable, "Enable all displays (default: Y)");


static void pdp_irq_handler(void *data)
{
	struct drm_device *dev = data;
	struct drm_crtc *crtc;

	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
		pdp_crtc_irq_handler(crtc);
}

static int pdp_early_load(struct drm_device *dev)
{
	struct pdp_drm_private *dev_priv;
	int err;

	DRM_DEBUG("loading %s device\n", to_platform_device(dev->dev)->name);

	platform_set_drvdata(to_platform_device(dev->dev), dev);

	dev_priv = kzalloc(sizeof(*dev_priv), GFP_KERNEL);
	if (!dev_priv)
		return -ENOMEM;

	dev->dev_private = dev_priv;
	dev_priv->dev = dev;
	dev_priv->version = (enum pdp_version)
		to_platform_device(dev->dev)->id_entry->driver_data;
	dev_priv->display_enabled = display_enable;

	if (dev_priv->version == PDP_VERSION_APOLLO ||
		dev_priv->version == PDP_VERSION_ODIN) {
#if !defined(SUPPORT_PLATO_DISPLAY)
		err = tc_enable(dev->dev->parent);
		if (err) {
			DRM_ERROR("failed to enable parent device (err=%d)\n", err);
			goto err_dev_priv_free;
		}
#endif
	}
#if defined(SUPPORT_PLATO_DISPLAY)
	else if (dev_priv->version == PDP_VERSION_PLATO) {
// XXX do we we need to do this? Plato driver has already enabled device.
		err = plato_enable(dev->dev->parent);
		if (err) {
			DRM_ERROR("failed to enable parent device (err=%d)\n", err);
			goto err_dev_priv_free;
		}
	}
#endif

	dev_priv->gem_priv = pdp_gem_init(dev);
	if (!dev_priv->gem_priv) {
		DRM_ERROR("gem initialisation failed\n");
		err = -ENOMEM;
		goto err_disable_parent_device;
	}

	err = pdp_modeset_early_init(dev_priv);
	if (err) {
		DRM_ERROR("early modeset initialisation failed (err=%d)\n",
			  err);
		goto err_gem_cleanup;
	}

	err = drm_vblank_init(dev_priv->dev, 1);
	if (err) {
		DRM_ERROR("failed to complete vblank init (err=%d)\n", err);
		goto err_modeset_late_cleanup;
	}

	if (dev_priv->version == PDP_VERSION_APOLLO ||
		dev_priv->version == PDP_VERSION_ODIN) {
#if !defined(SUPPORT_PLATO_DISPLAY)
		err = tc_set_interrupt_handler(dev->dev->parent,
					   TC_INTERRUPT_PDP,
					   pdp_irq_handler,
					   dev);
		if (err) {
			DRM_ERROR("failed to set interrupt handler (err=%d)\n",
				  err);
			goto err_vblank_cleanup;
		}

		err = tc_enable_interrupt(dev->dev->parent, TC_INTERRUPT_PDP);
		if (err) {
			DRM_ERROR("failed to enable pdp interrupts (err=%d)\n",
				  err);
			goto err_uninstall_interrupt_handle;
		}
#endif
	}
#if defined(SUPPORT_PLATO_DISPLAY)
	else if (dev_priv->version == PDP_VERSION_PLATO) {
		err = plato_set_interrupt_handler(dev->dev->parent,
							PLATO_INTERRUPT_PDP,
							pdp_irq_handler,
							dev);
		if (err) {
			DRM_ERROR("failed to set interrupt handler (err=%d)\n",
				  err);
			goto err_vblank_cleanup;
		}

		err = plato_enable_interrupt(dev->dev->parent, PLATO_INTERRUPT_PDP);
		if (err) {
			DRM_ERROR("failed to enable pdp interrupts (err=%d)\n",
				  err);
			goto err_uninstall_interrupt_handle;
		}
	}
#endif

	dev->irq_enabled = true;

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0))
	dev->vblank_disable_allowed = 1;
#endif

	return 0;

err_uninstall_interrupt_handle:
	if (dev_priv->version == PDP_VERSION_APOLLO ||
		dev_priv->version == PDP_VERSION_ODIN) {
#if !defined(SUPPORT_PLATO_DISPLAY)
		tc_set_interrupt_handler(dev->dev->parent,
					     TC_INTERRUPT_PDP,
					     NULL,
					     NULL);
#endif
	}
#if defined(SUPPORT_PLATO_DISPLAY)
	else if (dev_priv->version == PDP_VERSION_PLATO) {
		plato_set_interrupt_handler(dev->dev->parent,
				PLATO_INTERRUPT_PDP,
				NULL,
				NULL);
	}
#endif
err_vblank_cleanup:
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0))
	/* Called by drm_dev_fini in Linux 4.11.0 and later */
	drm_vblank_cleanup(dev_priv->dev);
#endif
err_modeset_late_cleanup:
	pdp_modeset_late_cleanup(dev_priv);
err_gem_cleanup:
	pdp_gem_cleanup(dev_priv->gem_priv);
err_disable_parent_device:
	if (dev_priv->version == PDP_VERSION_APOLLO ||
		dev_priv->version == PDP_VERSION_ODIN) {
#if !defined(SUPPORT_PLATO_DISPLAY)
		tc_disable(dev->dev->parent);
#endif
	}
#if defined(SUPPORT_PLATO_DISPLAY)
	else if (dev_priv->version == PDP_VERSION_PLATO)
		plato_disable(dev->dev->parent);
#endif
err_dev_priv_free:
	kfree(dev_priv);
	return err;
}

static int pdp_late_load(struct drm_device *dev)
{
	struct pdp_drm_private *dev_priv = dev->dev_private;
	int err;

	err = pdp_modeset_late_init(dev_priv);
	if (err) {
		DRM_ERROR("late modeset initialisation failed (err=%d)\n",
			  err);
		return err;
	}

	return 0;
}

static void pdp_early_unload(struct drm_device *dev)
{
	struct pdp_drm_private *dev_priv = dev->dev_private;

	pdp_modeset_early_cleanup(dev_priv);
}

static void pdp_late_unload(struct drm_device *dev)
{
	struct pdp_drm_private *dev_priv = dev->dev_private;

	DRM_INFO("unloading %s device.\n", to_platform_device(dev->dev)->name);
	if (dev_priv->version == PDP_VERSION_APOLLO ||
		dev_priv->version == PDP_VERSION_ODIN) {
#if !defined(SUPPORT_PLATO_DISPLAY)
		tc_disable_interrupt(dev->dev->parent, TC_INTERRUPT_PDP);
		tc_set_interrupt_handler(dev->dev->parent,
					     TC_INTERRUPT_PDP,
					     NULL,
					     NULL);
#endif
	}
#if defined(SUPPORT_PLATO_DISPLAY)
	else if (dev_priv->version == PDP_VERSION_PLATO) {
		plato_disable_interrupt(dev->dev->parent, PLATO_INTERRUPT_PDP);
		plato_set_interrupt_handler(dev->dev->parent,
						PLATO_INTERRUPT_PDP,
						NULL,
						NULL);
	}
#endif

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0))
	/* Called by drm_dev_fini in Linux 4.11.0 and later */
	drm_vblank_cleanup(dev_priv->dev);
#endif
	pdp_modeset_late_cleanup(dev_priv);
	pdp_gem_cleanup(dev_priv->gem_priv);

	if (dev_priv->version == PDP_VERSION_APOLLO ||
		dev_priv->version == PDP_VERSION_ODIN) {
#if !defined(SUPPORT_PLATO_DISPLAY)
		tc_disable(dev->dev->parent);
#endif
	}
#if defined(SUPPORT_PLATO_DISPLAY)
	else if (dev_priv->version == PDP_VERSION_PLATO)
		plato_disable(dev->dev->parent);
#endif

	kfree(dev_priv);
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0))
static int pdp_load(struct drm_device *dev, unsigned long flags)
{
	int err;

	err = pdp_early_load(dev);
	if (err)
		return err;

	err = pdp_late_load(dev);
	if (err) {
		pdp_late_unload(dev);
		return err;
	}

	return 0;
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0))
static int pdp_unload(struct drm_device *dev)
#else
static void pdp_unload(struct drm_device *dev)
#endif
{
	pdp_early_unload(dev);
	pdp_late_unload(dev);

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0))
	return 0;
#endif
}
#endif

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0))
static void pdp_preclose(struct drm_device *dev, struct drm_file *file)
{
	struct drm_crtc *crtc;

	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
		pdp_crtc_flip_event_cancel(crtc, file);
}
#endif

static void pdp_lastclose(struct drm_device *dev)
{
#if defined(PDP_USE_ATOMIC)
	drm_atomic_helper_shutdown(dev);
#else
	struct drm_crtc *crtc;

	DRM_INFO("%s: %s device\n", __func__, to_platform_device(dev->dev)->name);
	drm_modeset_lock_all(dev);
	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
		if (crtc->primary->fb) {
			struct drm_mode_set mode_set = { .crtc = crtc };
			int err;

			err = drm_mode_set_config_internal(&mode_set);
			if (err)
				DRM_ERROR("failed to disable crtc %p (err=%d)\n",
					  crtc, err);
		}
	}
	drm_modeset_unlock_all(dev);
#endif
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0))
static int pdp_enable_vblank(struct drm_device *dev, unsigned int crtc)
#else
static int pdp_enable_vblank(struct drm_device *dev, int crtc)
#endif
{
	struct pdp_drm_private *dev_priv = dev->dev_private;

	switch (crtc) {
	case 0:
		pdp_crtc_set_vblank_enabled(dev_priv->crtc, true);
		break;
	default:
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0))
		DRM_ERROR("invalid crtc %u\n", crtc);
#else
		DRM_ERROR("invalid crtc %d\n", crtc);
#endif
		return -EINVAL;
	}

	DRM_DEBUG("vblank interrupts enabled for crtc %d\n", crtc);

	return 0;
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0))
static void pdp_disable_vblank(struct drm_device *dev, unsigned int crtc)
#else
static void pdp_disable_vblank(struct drm_device *dev, int crtc)
#endif
{
	struct pdp_drm_private *dev_priv = dev->dev_private;

	switch (crtc) {
	case 0:
		pdp_crtc_set_vblank_enabled(dev_priv->crtc, false);
		break;
	default:
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0))
		DRM_ERROR("invalid crtc %u\n", crtc);
#else
		DRM_ERROR("invalid crtc %d\n", crtc);
#endif
		return;
	}

	DRM_DEBUG("vblank interrupts disabled for crtc %d\n", crtc);
}

static int pdp_gem_object_create_ioctl(struct drm_device *dev,
				       void *data,
				       struct drm_file *file)
{
	struct pdp_drm_private *dev_priv = dev->dev_private;

	return pdp_gem_object_create_ioctl_priv(dev,
						dev_priv->gem_priv,
						data,
						file);
}

static int pdp_gem_dumb_create(struct drm_file *file,
			       struct drm_device *dev,
			       struct drm_mode_create_dumb *args)
{
	struct pdp_drm_private *dev_priv = dev->dev_private;

	return pdp_gem_dumb_create_priv(file,
					dev,
					dev_priv->gem_priv,
					args);
}

static void pdp_gem_object_free(struct drm_gem_object *obj)
{
	struct pdp_drm_private *dev_priv = obj->dev->dev_private;

	pdp_gem_object_free_priv(dev_priv->gem_priv, obj);
}

static const struct vm_operations_struct pdp_gem_vm_ops = {
	.fault	= pdp_gem_object_vm_fault,
	.open	= drm_gem_vm_open,
	.close	= drm_gem_vm_close,
};

static const struct drm_ioctl_desc pdp_ioctls[] = {
	DRM_IOCTL_DEF_DRV(PDP_GEM_CREATE, pdp_gem_object_create_ioctl,
				DRM_AUTH | DRM_UNLOCKED | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(PDP_GEM_MMAP, pdp_gem_object_mmap_ioctl,
				DRM_AUTH | DRM_UNLOCKED),
	DRM_IOCTL_DEF_DRV(PDP_GEM_CPU_PREP, pdp_gem_object_cpu_prep_ioctl,
				DRM_AUTH | DRM_UNLOCKED),
	DRM_IOCTL_DEF_DRV(PDP_GEM_CPU_FINI, pdp_gem_object_cpu_fini_ioctl,
				DRM_AUTH | DRM_UNLOCKED),
};

static const struct file_operations pdp_driver_fops = {
	.owner		= THIS_MODULE,
	.open		= drm_open,
	.release	= drm_release,
	.unlocked_ioctl	= drm_ioctl,
	.mmap		= drm_gem_mmap,
	.poll		= drm_poll,
	.read		= drm_read,
	.llseek		= noop_llseek,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= drm_compat_ioctl,
#endif
};

static struct drm_driver pdp_drm_driver = {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0))
	.load				= NULL,
	.unload				= NULL,
#else
	.load				= pdp_load,
	.unload				= pdp_unload,
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0))
	.preclose			= pdp_preclose,
#endif
	.lastclose			= pdp_lastclose,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)) && \
	(LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
	.set_busid			= drm_platform_set_busid,
#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0))
	.get_vblank_counter		= NULL,
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0))
	.get_vblank_counter		= drm_vblank_no_hw_counter,
#else
	.get_vblank_counter		= drm_vblank_count,
#endif
	.enable_vblank			= pdp_enable_vblank,
	.disable_vblank			= pdp_disable_vblank,

	.debugfs_init			= pdp_debugfs_init,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0))
	.debugfs_cleanup		= pdp_debugfs_cleanup,
#endif

	.gem_free_object		= pdp_gem_object_free,

	.prime_handle_to_fd		= drm_gem_prime_handle_to_fd,
	.prime_fd_to_handle		= drm_gem_prime_fd_to_handle,
	.gem_prime_export		= pdp_gem_prime_export,
	.gem_prime_import		= pdp_gem_prime_import,
	.gem_prime_import_sg_table	= pdp_gem_prime_import_sg_table,

    // Set dumb_create to NULL to avoid xorg owning the display (if xorg is running).
	.dumb_create			= pdp_gem_dumb_create,
	.dumb_map_offset		= pdp_gem_dumb_map_offset,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0))
	.dumb_destroy			= drm_gem_dumb_destroy,
#endif

	.gem_vm_ops			= &pdp_gem_vm_ops,

	.name				= DRIVER_NAME,
	.desc				= DRIVER_DESC,
	.date				= DRIVER_DATE,
	.major				= PVRVERSION_MAJ,
	.minor				= PVRVERSION_MIN,
	.patchlevel			= PVRVERSION_BUILD,

	.driver_features		= DRIVER_GEM |
					  DRIVER_MODESET |
					  DRIVER_PRIME |
					  PVR_DRIVER_ATOMIC,
	.ioctls				= pdp_ioctls,
	.num_ioctls			= ARRAY_SIZE(pdp_ioctls),
	.fops				= &pdp_driver_fops,
};

#if defined(SUPPORT_PLATO_DISPLAY)

static int compare_parent_dev(struct device *dev, void *data)
{
	struct device *pdp_dev = data;

	return dev->parent && dev->parent == pdp_dev->parent;
}

static int pdp_component_bind(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct drm_device *ddev;
	int ret;

	dev_info(dev, "Loading platform device\n");
	ddev = drm_dev_alloc(&pdp_drm_driver, &pdev->dev);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0))
	if (IS_ERR(ddev))
		return PTR_ERR(ddev);
#else
	if (!ddev)
		return -ENOMEM;
#endif

	// XXX no need to do this as happens in pdp_early_load
	platform_set_drvdata(pdev, ddev);

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
	/* Needed by drm_platform_set_busid */
	ddev->platformdev = pdev;
#endif
	BUG_ON(pdp_drm_driver.load != NULL);

	ret = pdp_early_load(ddev);
	if (ret)
		goto err_drm_dev_unref;

	DRM_DEBUG_DRIVER("Binding other components\n");
	/* Bind other components, including HDMI encoder/connector */
	ret = component_bind_all(dev, ddev);
	if (ret) {
		DRM_ERROR("Failed to bind other components (ret=%d)\n", ret);
		goto err_drm_dev_late_unload;
	}

	ret = drm_dev_register(ddev, 0);
	if (ret)
		goto err_drm_dev_late_unload;

	ret = pdp_late_load(ddev);
	if (ret)
		goto err_drm_dev_unregister;

	return 0;

err_drm_dev_unregister:
	drm_dev_unregister(ddev);
err_drm_dev_late_unload:
	pdp_late_unload(ddev);
err_drm_dev_unref:
	drm_dev_unref(ddev);
	return	ret;
}

static void pdp_component_unbind(struct device *dev)
{
	struct drm_device *ddev = dev_get_drvdata(dev);

	dev_info(dev, "Unloading platform device\n");
	BUG_ON(pdp_drm_driver.unload != NULL);
	pdp_early_unload(ddev);
	drm_dev_unregister(ddev);
	pdp_late_unload(ddev);
	component_unbind_all(dev, ddev);
	drm_dev_unref(ddev);
}

static const struct component_master_ops pdp_component_ops = {
	.bind	= pdp_component_bind,
	.unbind = pdp_component_unbind,
};


static int pdp_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct component_match *match = NULL;

	component_match_add(dev, &match, compare_parent_dev, dev);
	return component_master_add_with_match(dev, &pdp_component_ops, match);
}

static int pdp_remove(struct platform_device *pdev)
{
	component_master_del(&pdev->dev, &pdp_component_ops);
	return 0;
}

#else  // !SUPPORT_PLATO_DISPLAY

static int pdp_probe(struct platform_device *pdev)
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0))
	struct drm_device *ddev;
	int ret;

	ddev = drm_dev_alloc(&pdp_drm_driver, &pdev->dev);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0))
	if (IS_ERR(ddev))
		return PTR_ERR(ddev);
#else
	if (!ddev)
		return -ENOMEM;
#endif

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))
	/* Needed by drm_platform_set_busid */
	ddev->platformdev = pdev;
#endif
	/*
	 * The load callback, called from drm_dev_register, is deprecated,
	 * because of potential race conditions.
	 */
	BUG_ON(pdp_drm_driver.load != NULL);

	ret = pdp_early_load(ddev);
	if (ret)
		goto err_drm_dev_unref;

	ret = drm_dev_register(ddev, 0);
	if (ret)
		goto err_drm_dev_late_unload;

	ret = pdp_late_load(ddev);
	if (ret)
		goto err_drm_dev_unregister;

#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0))
	DRM_INFO("Initialized %s %d.%d.%d %s on minor %d\n",
		pdp_drm_driver.name,
		pdp_drm_driver.major,
		pdp_drm_driver.minor,
		pdp_drm_driver.patchlevel,
		pdp_drm_driver.date,
		ddev->primary->index);
#endif
	return 0;

err_drm_dev_unregister:
	drm_dev_unregister(ddev);
err_drm_dev_late_unload:
	pdp_late_unload(ddev);
err_drm_dev_unref:
	drm_dev_unref(ddev);
	return	ret;
#else
	return drm_platform_init(&pdp_drm_driver, pdev);
#endif
}

static int pdp_remove(struct platform_device *pdev)
{
	struct drm_device *ddev = platform_get_drvdata(pdev);

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0))
	/*
	 * The unload callback, called from drm_dev_unregister, is
	 * deprecated.
	 */
	BUG_ON(pdp_drm_driver.unload != NULL);

	pdp_early_unload(ddev);

	drm_dev_unregister(ddev);

	pdp_late_unload(ddev);

	drm_dev_unref(ddev);
#else
	drm_put_dev(ddev);
#endif
	return 0;
}

#endif  // SUPPORT_PLATO_DISPLAY

static void pdp_shutdown(struct platform_device *pdev)
{
}

static struct platform_device_id pdp_platform_device_id_table[] = {
	{ .name = APOLLO_DEVICE_NAME_PDP, .driver_data = PDP_VERSION_APOLLO },
	{ .name = ODN_DEVICE_NAME_PDP, .driver_data = PDP_VERSION_ODIN },
#if defined(SUPPORT_PLATO_DISPLAY)
	{ .name = PLATO_DEVICE_NAME_PDP, .driver_data = PDP_VERSION_PLATO },
#endif  // SUPPORT_PLATO_DISPLAY
	{ },
};

static struct platform_driver pdp_platform_driver = {
	.probe		= pdp_probe,
	.remove		= pdp_remove,
	.shutdown	= pdp_shutdown,
	.driver		= {
		.owner  = THIS_MODULE,
		.name	= DRIVER_NAME,
	},
	.id_table	= pdp_platform_device_id_table,
};

module_platform_driver(pdp_platform_driver);

MODULE_AUTHOR("Imagination Technologies Ltd. <gpl-support@imgtec.com>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_DEVICE_TABLE(platform, pdp_platform_device_id_table);
MODULE_LICENSE("Dual MIT/GPL");
