/* * Copyright (C) 2011 Samsung Electronics Co.Ltd * Authors: * Inki Dae <inki.dae@samsung.com> * Seung-Woo Kim <sw0312.kim@samsung.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include "drmP.h" #include <linux/kernel.h> #include <linux/wait.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <drm/exynos_drm.h> #include "exynos_drm_drv.h" #include "exynos_drm_hdmi.h" #define to_context(dev) platform_get_drvdata(to_platform_device(dev)) #define to_subdrv(dev) to_context(dev) #define get_ctx_from_subdrv(subdrv) container_of(subdrv,\ struct drm_hdmi_context, subdrv); /* these callback points shoud be set by specific drivers. */ static struct exynos_hdmi_display_ops *hdmi_display_ops; static struct exynos_hdmi_manager_ops *hdmi_manager_ops; static struct exynos_hdmi_overlay_ops *hdmi_overlay_ops; struct drm_hdmi_context { struct exynos_drm_subdrv subdrv; struct exynos_drm_hdmi_context *hdmi_ctx; struct exynos_drm_hdmi_context *mixer_ctx; struct work_struct work; }; void exynos_drm_display_ops_register(struct exynos_hdmi_display_ops *display_ops) { DRM_DEBUG_KMS("%s\n", __FILE__); if (display_ops) hdmi_display_ops = display_ops; } EXPORT_SYMBOL(exynos_drm_display_ops_register); void exynos_drm_manager_ops_register(struct exynos_hdmi_manager_ops *manager_ops) { DRM_DEBUG_KMS("%s\n", __FILE__); if (manager_ops) hdmi_manager_ops = manager_ops; } EXPORT_SYMBOL(exynos_drm_manager_ops_register); void exynos_drm_overlay_ops_register(struct exynos_hdmi_overlay_ops *overlay_ops) { DRM_DEBUG_KMS("%s\n", __FILE__); if (overlay_ops) hdmi_overlay_ops = overlay_ops; } EXPORT_SYMBOL(exynos_drm_overlay_ops_register); static bool drm_hdmi_is_connected(struct device *dev) { struct drm_hdmi_context *ctx = to_context(dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_display_ops && hdmi_display_ops->is_connected) return hdmi_display_ops->is_connected(ctx->hdmi_ctx->ctx); return false; } static int drm_hdmi_get_edid(struct device *dev, struct drm_connector *connector, u8 *edid, int len) { struct drm_hdmi_context *ctx = to_context(dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_display_ops && hdmi_display_ops->get_edid) return hdmi_display_ops->get_edid(ctx->hdmi_ctx->ctx, connector, edid, len); return 0; } static int drm_hdmi_check_timing(struct device *dev, void *timing) { struct drm_hdmi_context *ctx = to_context(dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_display_ops && hdmi_display_ops->check_timing) return hdmi_display_ops->check_timing(ctx->hdmi_ctx->ctx, timing); return 0; } static int drm_hdmi_power_on(struct device *dev, int mode) { struct drm_hdmi_context *ctx = to_context(dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_display_ops && hdmi_display_ops->power_on) return hdmi_display_ops->power_on(ctx->hdmi_ctx->ctx, mode); return 0; } static struct exynos_drm_display_ops drm_hdmi_display_ops = { .type = EXYNOS_DISPLAY_TYPE_HDMI, .is_connected = drm_hdmi_is_connected, .get_edid = drm_hdmi_get_edid, .check_timing = drm_hdmi_check_timing, .power_on = drm_hdmi_power_on, }; static int drm_hdmi_enable_vblank(struct device *subdrv_dev) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); struct exynos_drm_subdrv *subdrv = &ctx->subdrv; struct exynos_drm_manager *manager = &subdrv->manager; DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_overlay_ops && hdmi_overlay_ops->enable_vblank) return hdmi_overlay_ops->enable_vblank(ctx->mixer_ctx->ctx, manager->pipe); return 0; } static void drm_hdmi_disable_vblank(struct device *subdrv_dev) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_overlay_ops && hdmi_overlay_ops->disable_vblank) return hdmi_overlay_ops->disable_vblank(ctx->mixer_ctx->ctx); } static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_manager_ops && hdmi_manager_ops->mode_set) hdmi_manager_ops->mode_set(ctx->hdmi_ctx->ctx, mode); } static void drm_hdmi_commit(struct device *subdrv_dev) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_manager_ops && hdmi_manager_ops->commit) hdmi_manager_ops->commit(ctx->hdmi_ctx->ctx); } static void drm_hdmi_dpms(struct device *subdrv_dev, int mode) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); switch (mode) { case DRM_MODE_DPMS_ON: break; case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_OFF: if (hdmi_manager_ops && hdmi_manager_ops->disable) hdmi_manager_ops->disable(ctx->hdmi_ctx->ctx); break; default: DRM_DEBUG_KMS("unkown dps mode: %d\n", mode); break; } } static struct exynos_drm_manager_ops drm_hdmi_manager_ops = { .dpms = drm_hdmi_dpms, .enable_vblank = drm_hdmi_enable_vblank, .disable_vblank = drm_hdmi_disable_vblank, .mode_set = drm_hdmi_mode_set, .commit = drm_hdmi_commit, }; static void drm_mixer_mode_set(struct device *subdrv_dev, struct exynos_drm_overlay *overlay) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_overlay_ops && hdmi_overlay_ops->win_mode_set) hdmi_overlay_ops->win_mode_set(ctx->mixer_ctx->ctx, overlay); } static void drm_mixer_commit(struct device *subdrv_dev, int zpos) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_overlay_ops && hdmi_overlay_ops->win_commit) hdmi_overlay_ops->win_commit(ctx->mixer_ctx->ctx, zpos); } static void drm_mixer_disable(struct device *subdrv_dev, int zpos) { struct drm_hdmi_context *ctx = to_context(subdrv_dev); DRM_DEBUG_KMS("%s\n", __FILE__); if (hdmi_overlay_ops && hdmi_overlay_ops->win_disable) hdmi_overlay_ops->win_disable(ctx->mixer_ctx->ctx, zpos); } static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = { .mode_set = drm_mixer_mode_set, .commit = drm_mixer_commit, .disable = drm_mixer_disable, }; static int hdmi_subdrv_probe(struct drm_device *drm_dev, struct device *dev) { struct exynos_drm_subdrv *subdrv = to_subdrv(dev); struct drm_hdmi_context *ctx; struct platform_device *pdev = to_platform_device(dev); struct exynos_drm_common_hdmi_pd *pd; int ret; DRM_DEBUG_KMS("%s\n", __FILE__); pd = pdev->dev.platform_data; if (!pd) { DRM_DEBUG_KMS("platform data is null.\n"); return -EFAULT; } if (!pd->hdmi_dev) { DRM_DEBUG_KMS("hdmi device is null.\n"); return -EFAULT; } if (!pd->mixer_dev) { DRM_DEBUG_KMS("mixer device is null.\n"); return -EFAULT; } ret = platform_driver_register(&hdmi_driver); if (ret) { DRM_DEBUG_KMS("failed to register hdmi driver.\n"); return ret; } ret = platform_driver_register(&mixer_driver); if (ret) { DRM_DEBUG_KMS("failed to register mixer driver.\n"); goto err_hdmidrv; } ctx = get_ctx_from_subdrv(subdrv); ctx->hdmi_ctx = (struct exynos_drm_hdmi_context *) to_context(pd->hdmi_dev); if (!ctx->hdmi_ctx) { DRM_DEBUG_KMS("hdmi context is null.\n"); ret = -EFAULT; goto err_mixerdrv; } ctx->hdmi_ctx->drm_dev = drm_dev; ctx->mixer_ctx = (struct exynos_drm_hdmi_context *) to_context(pd->mixer_dev); if (!ctx->mixer_ctx) { DRM_DEBUG_KMS("mixer context is null.\n"); ret = -EFAULT; goto err_mixerdrv; } ctx->mixer_ctx->drm_dev = drm_dev; return 0; err_mixerdrv: platform_driver_unregister(&mixer_driver); err_hdmidrv: platform_driver_unregister(&hdmi_driver); return ret; } static void hdmi_subdrv_remove(struct drm_device *drm_dev) { DRM_DEBUG_KMS("%s\n", __FILE__); platform_driver_unregister(&hdmi_driver); platform_driver_unregister(&mixer_driver); } static void exynos_drm_hdmi_late_probe(struct work_struct *work) { struct drm_hdmi_context *ctx = container_of(work, struct drm_hdmi_context, work); /* * this function calls subdrv->probe() so this must be called * after probe context. * * PS. subdrv->probe() will call platform_driver_register() to probe * hdmi and mixer driver. */ exynos_drm_subdrv_register(&ctx->subdrv); } static int __devinit exynos_drm_hdmi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct exynos_drm_subdrv *subdrv; struct drm_hdmi_context *ctx; DRM_DEBUG_KMS("%s\n", __FILE__); ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) { DRM_LOG_KMS("failed to alloc common hdmi context.\n"); return -ENOMEM; } subdrv = &ctx->subdrv; subdrv->probe = hdmi_subdrv_probe; subdrv->remove = hdmi_subdrv_remove; subdrv->manager.pipe = -1; subdrv->manager.ops = &drm_hdmi_manager_ops; subdrv->manager.overlay_ops = &drm_hdmi_overlay_ops; subdrv->manager.display_ops = &drm_hdmi_display_ops; subdrv->manager.dev = dev; platform_set_drvdata(pdev, subdrv); INIT_WORK(&ctx->work, exynos_drm_hdmi_late_probe); schedule_work(&ctx->work); return 0; } static int hdmi_runtime_suspend(struct device *dev) { DRM_DEBUG_KMS("%s\n", __FILE__); return 0; } static int hdmi_runtime_resume(struct device *dev) { DRM_DEBUG_KMS("%s\n", __FILE__); return 0; } static const struct dev_pm_ops hdmi_pm_ops = { .runtime_suspend = hdmi_runtime_suspend, .runtime_resume = hdmi_runtime_resume, }; static int __devexit exynos_drm_hdmi_remove(struct platform_device *pdev) { struct drm_hdmi_context *ctx = platform_get_drvdata(pdev); DRM_DEBUG_KMS("%s\n", __FILE__); exynos_drm_subdrv_unregister(&ctx->subdrv); kfree(ctx); return 0; } static struct platform_driver exynos_drm_common_hdmi_driver = { .probe = exynos_drm_hdmi_probe, .remove = __devexit_p(exynos_drm_hdmi_remove), .driver = { .name = "exynos-drm-hdmi", .owner = THIS_MODULE, .pm = &hdmi_pm_ops, }, }; static int __init exynos_drm_hdmi_init(void) { int ret; DRM_DEBUG_KMS("%s\n", __FILE__); ret = platform_driver_register(&exynos_drm_common_hdmi_driver); if (ret) { DRM_DEBUG_KMS("failed to register hdmi common driver.\n"); return ret; } return ret; } static void __exit exynos_drm_hdmi_exit(void) { platform_driver_unregister(&exynos_drm_common_hdmi_driver); } module_init(exynos_drm_hdmi_init); module_exit(exynos_drm_hdmi_exit); MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); MODULE_AUTHOR("Seung-Woo Kim, <sw0312.kim@samsung.com>"); MODULE_DESCRIPTION("Samsung SoC DRM HDMI Driver"); MODULE_LICENSE("GPL");