CHROMIUM: drm/exynos: Fixup init failure cleanup path
[cascardo/linux.git] / drivers / gpu / drm / exynos / exynos_drm_drv.c
index 2c53254..9462a85 100644 (file)
 #include "exynos_drm_plane.h"
 #include "exynos_drm_vidi.h"
 #include "exynos_drm_dmabuf.h"
+#include "exynos_drm_display.h"
+
+#include <linux/i2c.h>
+#include <linux/of_i2c.h>
+
+#include <drm/bridge/ptn3460.h>
+#include <drm/bridge/ps8622.h>
 
 #define DRIVER_NAME    "exynos"
 #define DRIVER_DESC    "Samsung SoC DRM"
 
 #define VBLANK_OFF_DELAY       50000
 
+/* TODO (seanpaul): Once we remove platform drivers, we'll be calling the
+ * various panel/controller init functions directly. These init functions will
+ * return to us the ops and context, so we can get rid of these attach
+ * functions. Once the attach functions are gone, we can move this array of
+ * display pointers into the drm device's platform data.
+ *
+ * For now, we'll use a global to keep track of things.
+ */
+static struct exynos_drm_display *displays[EXYNOS_DRM_DISPLAY_NUM_DISPLAYS];
+
+struct bridge_init {
+       struct i2c_client *client;
+       struct device_node *node;
+       bool valid;
+};
+
+static int find_bridge(const char *name, struct bridge_init *bridge)
+{
+       bridge->valid = false;
+       bridge->client = NULL;
+       bridge->node = of_find_node_by_name(NULL, name);
+       if (!bridge->node)
+               return 0;
+
+       bridge->client = of_find_i2c_device_by_node(bridge->node);
+       if (!bridge->client)
+               return -ENODEV;
+
+       bridge->valid = true;
+       return 0;
+}
+
 static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
 {
        struct exynos_drm_private *private;
        int ret;
        int nr;
+       struct bridge_init bridge;
 
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       DRM_DEBUG("[DEV:%s] flags: 0x%lx\n", dev->devname, flags);
 
        private = kzalloc(sizeof(struct exynos_drm_private), GFP_KERNEL);
        if (!private) {
@@ -63,7 +103,17 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
                return -ENOMEM;
        }
 
-       INIT_LIST_HEAD(&private->pageflip_event_list);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+       if (kds_callback_init(&private->kds_cb, 1,
+                             exynos_drm_kds_callback) < 0) {
+               DRM_ERROR("kds alloc queue failed.\n");
+               ret = -ENOMEM;
+               goto err_kds;
+       }
+#endif
+
+       DRM_INIT_WAITQUEUE(&private->wait_vsync_queue);
+
        dev->dev_private = (void *)private;
 
        drm_mode_config_init(dev);
@@ -77,22 +127,54 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
         * EXYNOS4 is enough to have two CRTCs and each crtc would be used
         * without dependency of hardware.
         */
-       for (nr = 0; nr < MAX_CRTC; nr++) {
-               ret = exynos_drm_crtc_create(dev, nr);
+       for (nr = 0; nr < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; nr++) {
+               ret = exynos_drm_crtc_create(dev, nr, displays[nr]);
                if (ret)
                        goto err_crtc;
        }
 
        for (nr = 0; nr < MAX_PLANE; nr++) {
-               ret = exynos_plane_init(dev, nr);
-               if (ret)
+               struct drm_plane *plane;
+               unsigned int possible_crtcs =
+                       (1 << EXYNOS_DRM_DISPLAY_NUM_DISPLAYS) - 1;
+
+               plane = exynos_plane_init(dev, possible_crtcs, false);
+               if (!plane)
                        goto err_crtc;
        }
 
-       ret = drm_vblank_init(dev, MAX_CRTC);
+       ret = drm_vblank_init(dev, EXYNOS_DRM_DISPLAY_NUM_DISPLAYS);
        if (ret)
                goto err_crtc;
 
+       ret = find_bridge("ptn3460-bridge", &bridge);
+       if (ret) {
+               DRM_ERROR("Could not get PTN3460 bridge %d\n", ret);
+               goto err_vblank;
+       }
+       if (bridge.valid) {
+               ret = ptn3460_init(dev, bridge.client, bridge.node);
+               if (ret) {
+                       DRM_ERROR("Failed to initialize the ptn bridge\n");
+                       goto err_vblank;
+               }
+       } else {
+               ret = find_bridge("ps8622-bridge", &bridge);
+               if (ret) {
+                       DRM_ERROR("Could not get PS8622 bridge %d\n", ret);
+                       goto err_vblank;
+               }
+
+               if (bridge.valid) {
+                       ret = ps8622_init(dev, bridge.client, bridge.node);
+                       if (ret) {
+                               DRM_ERROR("Failed to initialize the Parade "
+                                         "bridge\n");
+                               goto err_vblank;
+                       }
+               }
+       }
+
        /*
         * probe sub drivers such as display controller and hdmi driver,
         * that were registered at probe() of platform driver
@@ -125,6 +207,10 @@ err_vblank:
        drm_vblank_cleanup(dev);
 err_crtc:
        drm_mode_config_cleanup(dev);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+       kds_callback_term(&private->kds_cb);
+err_kds:
+#endif
        kfree(private);
 
        return ret;
@@ -132,14 +218,19 @@ err_crtc:
 
 static int exynos_drm_unload(struct drm_device *dev)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       struct exynos_drm_private *private = dev->dev_private;
+
+       DRM_DEBUG("[DEV:%s]\n", dev->devname);
 
        exynos_drm_fbdev_fini(dev);
        exynos_drm_device_unregister(dev);
        drm_vblank_cleanup(dev);
        drm_kms_helper_poll_fini(dev);
        drm_mode_config_cleanup(dev);
-       kfree(dev->dev_private);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+       kds_callback_term(&private->kds_cb);
+#endif
+       kfree(private);
 
        dev->dev_private = NULL;
 
@@ -148,9 +239,18 @@ static int exynos_drm_unload(struct drm_device *dev)
 
 static int exynos_drm_open(struct drm_device *dev, struct drm_file *file)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       struct exynos_drm_file_private *file_private;
+
+       DRM_DEBUG("[DEV:%s]\n", dev->devname);
+
+       file_private = kzalloc(sizeof(*file_private), GFP_KERNEL);
+       if (!file_private) {
+               DRM_ERROR("failed to allocate exynos_drm_file_private\n");
+               return -ENOMEM;
+       }
+       INIT_LIST_HEAD(&file_private->gem_cpu_acquire_list);
 
-       drm_prime_init_file_private(&file->prime);
+       file->driver_priv = file_private;
 
        return exynos_drm_subdrv_open(dev, file);
 }
@@ -158,30 +258,31 @@ static int exynos_drm_open(struct drm_device *dev, struct drm_file *file)
 static void exynos_drm_preclose(struct drm_device *dev,
                                        struct drm_file *file)
 {
-       struct exynos_drm_private *private = dev->dev_private;
-       struct drm_pending_vblank_event *e, *t;
-       unsigned long flags;
-
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
-
-       /* release events of current file */
-       spin_lock_irqsave(&dev->event_lock, flags);
-       list_for_each_entry_safe(e, t, &private->pageflip_event_list,
-                       base.link) {
-               if (e->base.file_priv == file) {
-                       list_del(&e->base.link);
-                       e->base.destroy(&e->base);
-               }
+       struct exynos_drm_file_private *file_private = file->driver_priv;
+       struct exynos_drm_gem_obj_node *cur, *d;
+
+       DRM_DEBUG("[DEV:%s]\n", dev->devname);
+
+       mutex_lock(&dev->struct_mutex);
+       /* release kds resource sets for outstanding GEM object acquires */
+       list_for_each_entry_safe(cur, d,
+                       &file_private->gem_cpu_acquire_list, list) {
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+               BUG_ON(cur->exynos_gem_obj->resource_set == NULL);
+               kds_resource_set_release(&cur->exynos_gem_obj->resource_set);
+#endif
+               drm_gem_object_unreference(&cur->exynos_gem_obj->base);
+               kfree(cur);
        }
-       drm_prime_destroy_file_private(&file->prime);
-       spin_unlock_irqrestore(&dev->event_lock, flags);
+       mutex_unlock(&dev->struct_mutex);
+       INIT_LIST_HEAD(&file_private->gem_cpu_acquire_list);
 
        exynos_drm_subdrv_close(dev, file);
 }
 
 static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       DRM_DEBUG("[DEV:%s]\n", dev->devname);
 
        if (!file->driver_priv)
                return;
@@ -192,7 +293,7 @@ static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file)
 
 static void exynos_drm_lastclose(struct drm_device *dev)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       DRM_DEBUG("[DEV:%s]\n", dev->devname);
 
        exynos_drm_fbdev_restore_mode(dev);
 }
@@ -211,10 +312,14 @@ static struct drm_ioctl_desc exynos_ioctls[] = {
                        DRM_AUTH),
        DRM_IOCTL_DEF_DRV(EXYNOS_GEM_MMAP,
                        exynos_drm_gem_mmap_ioctl, DRM_UNLOCKED | DRM_AUTH),
-       DRM_IOCTL_DEF_DRV(EXYNOS_PLANE_SET_ZPOS, exynos_plane_set_zpos_ioctl,
-                       DRM_UNLOCKED | DRM_AUTH),
        DRM_IOCTL_DEF_DRV(EXYNOS_VIDI_CONNECTION,
                        vidi_connection_ioctl, DRM_UNLOCKED | DRM_AUTH),
+       DRM_IOCTL_DEF_DRV(EXYNOS_GEM_CPU_ACQUIRE,
+                       exynos_drm_gem_cpu_acquire_ioctl,
+                       DRM_UNLOCKED | DRM_AUTH),
+       DRM_IOCTL_DEF_DRV(EXYNOS_GEM_CPU_RELEASE,
+                       exynos_drm_gem_cpu_release_ioctl,
+                       DRM_UNLOCKED | DRM_AUTH),
 };
 
 static const struct file_operations exynos_drm_driver_fops = {
@@ -239,7 +344,10 @@ static struct drm_driver exynos_drm_driver = {
        .get_vblank_counter     = drm_vblank_count,
        .enable_vblank          = exynos_drm_crtc_enable_vblank,
        .disable_vblank         = exynos_drm_crtc_disable_vblank,
-       .gem_init_object        = exynos_drm_gem_init_object,
+#if defined(CONFIG_DEBUG_FS)
+       .debugfs_init           = exynos_drm_debugfs_init,
+       .debugfs_cleanup        = exynos_drm_debugfs_cleanup,
+#endif
        .gem_free_object        = exynos_drm_gem_free_object,
        .gem_vm_ops             = &exynos_drm_gem_vm_ops,
        .dumb_create            = exynos_drm_gem_dumb_create,
@@ -270,6 +378,12 @@ static int iommu_init(struct platform_device *pdev)
                return -1;
        }
 
+       /*
+        * The ordering in Makefile warrants that this is initialized after
+        * FIMD, so only just ensure that it works as expected and we are
+        * reusing the mapping originally created in exynos_drm_fimd.c.
+        */
+       WARN_ON(!exynos_drm_common_mapping);
        if (!s5p_create_iommu_mapping(&pdev->dev, 0,
                                0, 0, exynos_drm_common_mapping)) {
                printk(KERN_ERR "failed to create IOMMU mapping\n");
@@ -278,11 +392,23 @@ static int iommu_init(struct platform_device *pdev)
 
        return 0;
 }
+
+static void iommu_deinit(struct platform_device *pdev)
+{
+       /* detach the device and mapping */
+       s5p_destroy_iommu_mapping(&pdev->dev);
+       DRM_DEBUG("released the IOMMU mapping\n");
+
+       return;
+}
 #endif
 
 static int exynos_drm_platform_probe(struct platform_device *pdev)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       struct device *dev = &pdev->dev;
+       int ret;
+
+       DRM_DEBUG("[PDEV:%s]\n", pdev->name);
 
 #ifdef CONFIG_EXYNOS_IOMMU
        if (iommu_init(pdev)) {
@@ -293,32 +419,242 @@ static int exynos_drm_platform_probe(struct platform_device *pdev)
 
        exynos_drm_driver.num_ioctls = DRM_ARRAY_SIZE(exynos_ioctls);
 
-       return drm_platform_init(&exynos_drm_driver, pdev);
+       pm_vt_switch_required(dev, false);
+       pm_runtime_enable(dev);
+       pm_runtime_get_sync(dev);
+
+       ret = drm_platform_init(&exynos_drm_driver, pdev);
+#ifdef CONFIG_EXYNOS_IOMMU
+       if (ret)
+               iommu_deinit(pdev);
+#endif
+
+       return ret;
 }
 
-static int exynos_drm_platform_remove(struct platform_device *pdev)
+static int __devexit exynos_drm_platform_remove(struct platform_device *pdev)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       struct device *dev = &pdev->dev;
+
+       DRM_DEBUG("[PDEV:%s]\n", pdev->name);
+
+       pm_vt_switch_unregister(dev);
+       pm_runtime_disable(dev);
 
        drm_platform_exit(&exynos_drm_driver, pdev);
 
+#ifdef CONFIG_EXYNOS_IOMMU
+       iommu_deinit(pdev);
+#endif
+       return 0;
+}
+
+const char *exynos_display_type_name(const struct exynos_drm_display *display)
+{
+       switch (display->display_type) {
+       case EXYNOS_DRM_DISPLAY_TYPE_FIMD:
+               return "FIMD";
+       case EXYNOS_DRM_DISPLAY_TYPE_MIXER:
+               return "MIXER";
+       case EXYNOS_DRM_DISPLAY_TYPE_VIDI:
+               return "VIDI";
+       default:
+               return "?";
+       }
+}
+
+void exynos_display_attach_panel(enum exynos_drm_display_type type,
+               struct exynos_panel_ops *ops, void *ctx)
+{
+       int i;
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               if (displays[i]->display_type == type) {
+                       displays[i]->panel_ctx = ctx;
+                       displays[i]->panel_ops = ops;
+                       return;
+               }
+       }
+}
+
+void exynos_display_attach_controller(enum exynos_drm_display_type type,
+               struct exynos_controller_ops *ops, void *ctx)
+{
+       int i;
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               if (displays[i]->display_type == type) {
+                       displays[i]->controller_ctx = ctx;
+                       displays[i]->controller_ops = ops;
+                       return;
+               }
+       }
+}
+
+static int display_subdrv_probe(struct drm_device *drm_dev,
+               struct exynos_drm_subdrv *subdrv)
+{
+       struct exynos_drm_display *display = subdrv->display;
+       int ret;
+
+       if (!display->controller_ops || !display->panel_ops)
+               return -EINVAL;
+
+       if (display->controller_ops->subdrv_probe) {
+               ret = display->controller_ops->subdrv_probe(
+                               display->controller_ctx, drm_dev);
+               if (ret)
+                       return ret;
+       }
+
+       if (display->panel_ops->subdrv_probe) {
+               ret = display->panel_ops->subdrv_probe(display->panel_ctx,
+                               drm_dev);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+int exynos_display_init(struct exynos_drm_display *display,
+               enum exynos_drm_display_type type)
+{
+       struct exynos_drm_subdrv *subdrv;
+
+       subdrv = kzalloc(sizeof(*subdrv), GFP_KERNEL);
+       if (!subdrv) {
+               DRM_ERROR("Failed to allocate display subdrv\n");
+               return -ENOMEM;
+       }
+
+       display->display_type = type;
+       display->pipe = -1;
+       display->subdrv = subdrv;
+
+       subdrv->probe = display_subdrv_probe;
+       subdrv->display = display;
+       exynos_drm_subdrv_register(subdrv);
+
        return 0;
 }
 
+void exynos_display_remove(struct exynos_drm_display *display)
+{
+       if (display->subdrv) {
+               exynos_drm_subdrv_unregister(display->subdrv);
+               kfree(display->subdrv);
+       }
+}
+
+static int exynos_drm_resume_displays(struct drm_device *dev)
+{
+       int i;
+
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               struct exynos_drm_display *display = displays[i];
+               struct drm_connector *connector = display->subdrv->connector;
+
+               if (!connector || !connector->funcs->dpms)
+                       continue;
+
+               connector->funcs->dpms(connector, display->suspend_dpms);
+       }
+
+       drm_helper_resume_force_mode(dev);
+
+       return 0;
+}
+
+static int exynos_drm_suspend_displays(void)
+{
+       int i;
+
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               struct exynos_drm_display *display = displays[i];
+               struct drm_connector *connector = display->subdrv->connector;
+
+               if (!connector)
+                       continue;
+
+               display->suspend_dpms = connector->dpms;
+               if (connector->funcs->dpms)
+                       connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
+       }
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos_drm_suspend(struct device *dev)
+{
+       if (pm_runtime_suspended(dev))
+               return 0;
+
+       return exynos_drm_suspend_displays();
+}
+
+static int exynos_drm_resume(struct device *dev)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct drm_device *drm_dev = platform_get_drvdata(pdev);
+
+       if (pm_runtime_suspended(dev))
+               return 0;
+
+       return exynos_drm_resume_displays(drm_dev);
+}
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int exynos_drm_runtime_resume(struct device *dev)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct drm_device *drm_dev = platform_get_drvdata(pdev);
+
+       /* Check drm_dev here since this function is called from probe */
+       if (!drm_dev)
+               return 0;
+
+       return exynos_drm_resume_displays(drm_dev);
+}
+
+static int exynos_drm_runtime_suspend(struct device *dev)
+{
+       return exynos_drm_suspend_displays();
+}
+#endif
+
+static const struct dev_pm_ops drm_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(exynos_drm_suspend, exynos_drm_resume)
+       SET_RUNTIME_PM_OPS(exynos_drm_runtime_suspend,
+                       exynos_drm_runtime_resume, NULL)
+};
+
 static struct platform_driver exynos_drm_platform_driver = {
        .probe          = exynos_drm_platform_probe,
        .remove         = __devexit_p(exynos_drm_platform_remove),
        .driver         = {
                .owner  = THIS_MODULE,
                .name   = "exynos-drm",
+               .pm     = &drm_pm_ops,
        },
 };
 
 static int __init exynos_drm_init(void)
 {
-       int ret;
+       int ret, i;
+
+       DRM_DEBUG_DRIVER("\n");
 
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               displays[i] = kzalloc(sizeof(*displays[i]), GFP_KERNEL);
+               if (!displays[i]) {
+                       ret = -ENOMEM;
+                       goto out_display;
+               }
+
+               ret = exynos_display_init(displays[i], i);
+               if (ret)
+                       goto out_display;
+       }
 
 #ifdef CONFIG_DRM_EXYNOS_FIMD
        ret = platform_driver_register(&fimd_driver);
@@ -326,6 +662,12 @@ static int __init exynos_drm_init(void)
                goto out_fimd;
 #endif
 
+#ifdef CONFIG_DRM_EXYNOS_DP
+       ret = platform_driver_register(&dp_driver);
+       if (ret < 0)
+               goto out_dp_driver;
+#endif
+
 #ifdef CONFIG_DRM_EXYNOS_HDMI
        ret = platform_driver_register(&hdmi_driver);
        if (ret < 0)
@@ -333,9 +675,6 @@ static int __init exynos_drm_init(void)
        ret = platform_driver_register(&mixer_driver);
        if (ret < 0)
                goto out_mixer;
-       ret = platform_driver_register(&exynos_drm_common_hdmi_driver);
-       if (ret < 0)
-               goto out_common_hdmi;
 #endif
 
 #ifdef CONFIG_DRM_EXYNOS_VIDI
@@ -357,29 +696,38 @@ out_vidi:
 #endif
 
 #ifdef CONFIG_DRM_EXYNOS_HDMI
-       platform_driver_unregister(&exynos_drm_common_hdmi_driver);
-out_common_hdmi:
        platform_driver_unregister(&mixer_driver);
 out_mixer:
        platform_driver_unregister(&hdmi_driver);
 out_hdmi:
 #endif
 
+       platform_driver_unregister(&dp_driver);
+out_dp_driver:
 #ifdef CONFIG_DRM_EXYNOS_FIMD
        platform_driver_unregister(&fimd_driver);
 out_fimd:
 #endif
+out_display:
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               if (!displays[i])
+                       continue;
+
+               exynos_display_remove(displays[i]);
+               kfree(displays[i]);
+       }
        return ret;
 }
 
 static void __exit exynos_drm_exit(void)
 {
-       DRM_DEBUG_DRIVER("%s\n", __FILE__);
+       int i;
+
+       DRM_DEBUG_DRIVER("\n");
 
        platform_driver_unregister(&exynos_drm_platform_driver);
 
 #ifdef CONFIG_DRM_EXYNOS_HDMI
-       platform_driver_unregister(&exynos_drm_common_hdmi_driver);
        platform_driver_unregister(&mixer_driver);
        platform_driver_unregister(&hdmi_driver);
 #endif
@@ -388,9 +736,18 @@ static void __exit exynos_drm_exit(void)
        platform_driver_unregister(&vidi_driver);
 #endif
 
+       platform_driver_unregister(&dp_driver);
 #ifdef CONFIG_DRM_EXYNOS_FIMD
        platform_driver_unregister(&fimd_driver);
 #endif
+
+       for (i = 0; i < EXYNOS_DRM_DISPLAY_NUM_DISPLAYS; i++) {
+               if (!displays[i])
+                       continue;
+
+               exynos_display_remove(displays[i]);
+               kfree(displays[i]);
+       }
 }
 
 module_init(exynos_drm_init);