summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/imx/dcss/dcss-crtc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/imx/dcss/dcss-crtc.c')
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-crtc.c501
1 files changed, 501 insertions, 0 deletions
diff --git a/drivers/gpu/drm/imx/dcss/dcss-crtc.c b/drivers/gpu/drm/imx/dcss/dcss-crtc.c
new file mode 100644
index 000000000000..6bd818009381
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-crtc.c
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * 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.
+ *
+ * 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/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/component.h>
+#include <linux/pm_runtime.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <video/imx-dcss.h>
+
+#include "dcss-kms.h"
+#include "dcss-plane.h"
+#include "imx-drm.h"
+#include "dcss-crtc.h"
+
+#define TRACE_FLUSH 0
+#define TRACE_VBLANK 1
+
+struct dcss_crtc {
+ struct device *dev;
+ struct drm_crtc base;
+ struct imx_drm_crtc *imx_crtc;
+
+ struct dcss_plane *plane[3];
+
+ int irq;
+ bool irq_enabled;
+
+ struct drm_property *alpha;
+ struct drm_property *use_global;
+ struct drm_property *dtrc_table_ofs;
+
+ struct completion en_dis_completion;
+
+ enum dcss_hdr10_nonlinearity opipe_nl;
+ enum dcss_hdr10_gamut opipe_g;
+ enum dcss_hdr10_pixel_range opipe_pr;
+ u32 opipe_pix_format;
+};
+
+static void dcss_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+
+ imx_drm_remove_crtc(dcss_crtc->imx_crtc);
+}
+
+static void dcss_crtc_reset(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *state;
+
+ if (crtc->state) {
+ if (crtc->state->mode_blob)
+ drm_property_unreference_blob(crtc->state->mode_blob);
+
+ state = to_imx_crtc_state(crtc->state);
+ memset(state, 0, sizeof(*state));
+ } else {
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return;
+ crtc->state = &state->base;
+ }
+
+ state->base.crtc = crtc;
+}
+
+static struct drm_crtc_state *dcss_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *state;
+
+ if (!crtc->state)
+ return NULL;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+ WARN_ON(state->base.crtc != crtc);
+ state->base.crtc = crtc;
+
+ return &state->base;
+}
+
+static void dcss_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ __drm_atomic_helper_crtc_destroy_state(state);
+ kfree(to_imx_crtc_state(state));
+}
+
+static const struct drm_crtc_funcs dcss_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .destroy = dcss_crtc_destroy,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = dcss_crtc_reset,
+ .atomic_duplicate_state = dcss_crtc_duplicate_state,
+ .atomic_destroy_state = dcss_crtc_destroy_state,
+};
+
+static int dcss_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ /* TODO: other checks? */
+
+ return 0;
+}
+
+static void dcss_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ drm_crtc_vblank_on(crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ drm_crtc_arm_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static void dcss_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+ struct dcss_soc *dcss = dev_get_drvdata(dcss_crtc->dev->parent);
+
+ dcss_trace_module(TRACE_DRM_CRTC, TRACE_FLUSH);
+
+ if (dcss_dtg_is_enabled(dcss))
+ dcss_ctxld_enable(dcss);
+}
+
+void dcss_crtc_setup_opipe(struct drm_crtc *crtc, struct drm_connector *conn,
+ u32 colorimetry, u32 eotf,
+ enum hdmi_quantization_range qr)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+ struct drm_display_info *di = &conn->display_info;
+ int vic;
+
+ if ((colorimetry & BIT(HDMI_EXTENDED_COLORIMETRY_BT2020)) ||
+ (colorimetry & BIT(HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM)))
+ dcss_crtc->opipe_g = G_REC2020;
+ else if (colorimetry & BIT(HDMI_EXTENDED_COLORIMETRY_ADOBE_RGB))
+ dcss_crtc->opipe_g = G_ADOBE_ARGB;
+ else
+ dcss_crtc->opipe_g = G_REC709;
+
+ if ((eotf & (1 << 2)) && dcss_crtc->opipe_g == G_REC2020)
+ dcss_crtc->opipe_nl = NL_REC2084;
+ else
+ dcss_crtc->opipe_nl = NL_REC709;
+
+ if (qr == HDMI_QUANTIZATION_RANGE_FULL)
+ dcss_crtc->opipe_pr = PR_FULL;
+ else
+ dcss_crtc->opipe_pr = PR_LIMITED;
+
+ vic = drm_match_cea_mode(&crtc->state->adjusted_mode);
+
+ /* FIXME: we should get the connector colorspace some other way */
+ if (vic == 97 &&
+ (di->color_formats & DRM_COLOR_FORMAT_YCRCB420) &&
+ (di->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30))
+ dcss_crtc->opipe_pix_format = DRM_FORMAT_P010;
+ else
+ dcss_crtc->opipe_pix_format = DRM_FORMAT_ARGB8888;
+
+ DRM_DEBUG_KMS("OPIPE_CFG: gamut = %d, nl = %d, pr = %d, pix_fmt = %d\n",
+ dcss_crtc->opipe_g, dcss_crtc->opipe_nl,
+ dcss_crtc->opipe_pr, dcss_crtc->opipe_pix_format);
+}
+
+int dcss_crtc_get_opipe_cfg(struct drm_crtc *crtc,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+
+ opipe_cfg->pixel_format = dcss_crtc->opipe_pix_format;
+ opipe_cfg->g = dcss_crtc->opipe_g;
+ opipe_cfg->nl = dcss_crtc->opipe_nl;
+ opipe_cfg->pr = dcss_crtc->opipe_pr;
+
+ return 0;
+}
+
+static int dcss_enable_vblank(struct drm_crtc *crtc);
+
+static void dcss_crtc_enable(struct drm_crtc *crtc)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+ struct dcss_soc *dcss = dev_get_drvdata(dcss_crtc->dev->parent);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ struct videomode vm;
+
+ drm_display_mode_to_videomode(mode, &vm);
+
+ pm_runtime_get_sync(dcss_crtc->dev->parent);
+
+ dcss_enable_vblank(crtc);
+
+ dcss_dtg_sync_set(dcss, &vm);
+
+ dcss_ss_subsam_set(dcss, dcss_crtc->opipe_pix_format);
+ dcss_ss_sync_set(dcss, &vm, mode->flags & DRM_MODE_FLAG_PHSYNC,
+ mode->flags & DRM_MODE_FLAG_PVSYNC);
+
+ dcss_dtg_css_set(dcss, dcss_crtc->opipe_pix_format);
+
+ dcss_ss_enable(dcss, true);
+ dcss_dtg_enable(dcss, true, NULL);
+ dcss_ctxld_enable(dcss);
+
+ reinit_completion(&dcss_crtc->en_dis_completion);
+ wait_for_completion_timeout(&dcss_crtc->en_dis_completion,
+ msecs_to_jiffies(100));
+
+ crtc->enabled = true;
+}
+
+static void dcss_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+ struct dcss_soc *dcss = dev_get_drvdata(dcss_crtc->dev->parent);
+
+ drm_atomic_helper_disable_planes_on_crtc(old_crtc_state, false);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ dcss_ss_enable(dcss, false);
+ dcss_dtg_enable(dcss, false, &dcss_crtc->en_dis_completion);
+ dcss_ctxld_enable(dcss);
+
+ crtc->enabled = false;
+
+ reinit_completion(&dcss_crtc->en_dis_completion);
+ wait_for_completion_timeout(&dcss_crtc->en_dis_completion,
+ msecs_to_jiffies(100));
+
+ drm_crtc_vblank_off(crtc);
+
+ pm_runtime_put_sync(dcss_crtc->dev->parent);
+}
+
+static const struct drm_crtc_helper_funcs dcss_helper_funcs = {
+ .atomic_check = dcss_crtc_atomic_check,
+ .atomic_begin = dcss_crtc_atomic_begin,
+ .atomic_flush = dcss_crtc_atomic_flush,
+ .enable = dcss_crtc_enable,
+ .atomic_disable = dcss_crtc_atomic_disable,
+};
+
+static int dcss_enable_vblank(struct drm_crtc *crtc)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+ struct dcss_soc *dcss = dev_get_drvdata(dcss_crtc->dev->parent);
+
+ if (dcss_crtc->irq_enabled)
+ return 0;
+
+ dcss_crtc->irq_enabled = true;
+
+ dcss_req_pm_qos(dcss, true);
+
+ dcss_vblank_irq_enable(dcss, true);
+
+ enable_irq(dcss_crtc->irq);
+
+ return 0;
+}
+
+static void dcss_disable_vblank(struct drm_crtc *crtc)
+{
+ struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
+ base);
+ struct dcss_soc *dcss = dev_get_drvdata(dcss_crtc->dev->parent);
+
+ disable_irq_nosync(dcss_crtc->irq);
+
+ dcss_vblank_irq_enable(dcss, false);
+
+ dcss_req_pm_qos(dcss, false);
+
+ dcss_crtc->irq_enabled = false;
+}
+
+static const struct imx_drm_crtc_helper_funcs dcss_crtc_helper_funcs = {
+ .enable_vblank = dcss_enable_vblank,
+ .disable_vblank = dcss_disable_vblank,
+ .crtc_funcs = &dcss_crtc_funcs,
+ .crtc_helper_funcs = &dcss_helper_funcs,
+};
+
+static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id)
+{
+ struct dcss_crtc *dcss_crtc = dev_id;
+ struct dcss_soc *dcss = dev_get_drvdata(dcss_crtc->dev->parent);
+
+ dcss_trace_module(TRACE_DRM_CRTC, TRACE_VBLANK);
+
+ complete(&dcss_crtc->en_dis_completion);
+
+ if (dcss_ctxld_is_flushed(dcss))
+ drm_crtc_handle_vblank(&dcss_crtc->base);
+
+ dcss_vblank_irq_clear(dcss);
+
+ return IRQ_HANDLED;
+}
+
+static int dcss_crtc_init(struct dcss_crtc *crtc,
+ struct dcss_client_platformdata *pdata,
+ struct drm_device *drm)
+{
+ struct dcss_soc *dcss = dev_get_drvdata(crtc->dev->parent);
+ int ret;
+
+ crtc->plane[0] = dcss_plane_init(drm, dcss, drm_crtc_mask(&crtc->base),
+ DRM_PLANE_TYPE_PRIMARY, 2);
+ if (IS_ERR(crtc->plane[0]))
+ return PTR_ERR(crtc->plane[0]);
+
+ ret = imx_drm_add_crtc(drm, &crtc->base, &crtc->imx_crtc,
+ &crtc->plane[0]->base,
+ &dcss_crtc_helper_funcs, pdata->of_node);
+ if (ret) {
+ dev_err(crtc->dev, "failed to init crtc\n");
+ return ret;
+ }
+
+ crtc->plane[1] = dcss_plane_init(drm, dcss, drm_crtc_mask(&crtc->base),
+ DRM_PLANE_TYPE_OVERLAY, 1);
+ if (IS_ERR(crtc->plane[1]))
+ crtc->plane[1] = NULL;
+
+ crtc->plane[2] = dcss_plane_init(drm, dcss, drm_crtc_mask(&crtc->base),
+ DRM_PLANE_TYPE_OVERLAY, 0);
+ if (IS_ERR(crtc->plane[2]))
+ crtc->plane[2] = NULL;
+
+ crtc->alpha = drm_property_create_range(drm, 0, "alpha", 0, 255);
+ if (!crtc->alpha) {
+ dev_err(crtc->dev, "cannot create alpha property\n");
+ return -ENOMEM;
+ }
+
+ crtc->use_global = drm_property_create_range(drm, 0,
+ "use_global_alpha", 0, 1);
+ if (!crtc->use_global) {
+ dev_err(crtc->dev, "cannot create use_global property\n");
+ return -ENOMEM;
+ }
+
+ crtc->dtrc_table_ofs = drm_property_create_range(drm, 0,
+ "dtrc_table_ofs", 0,
+ ULLONG_MAX);
+ if (!crtc->dtrc_table_ofs) {
+ dev_err(crtc->dev, "cannot create dtrc_table_ofs property\n");
+ return -ENOMEM;
+ }
+
+ /* attach alpha property to channel 0 */
+ drm_object_attach_property(&crtc->plane[0]->base.base,
+ crtc->alpha, 255);
+ crtc->plane[0]->alpha_prop = crtc->alpha;
+
+ drm_object_attach_property(&crtc->plane[0]->base.base,
+ crtc->use_global, 0);
+ crtc->plane[0]->use_global_prop = crtc->use_global;
+
+ /* attach DTRC table offsets property to overlay planes */
+ drm_object_attach_property(&crtc->plane[1]->base.base,
+ crtc->dtrc_table_ofs, 0);
+ crtc->plane[1]->dtrc_table_ofs_prop = crtc->dtrc_table_ofs;
+
+ drm_object_attach_property(&crtc->plane[2]->base.base,
+ crtc->dtrc_table_ofs, 0);
+ crtc->plane[2]->dtrc_table_ofs_prop = crtc->dtrc_table_ofs;
+
+ crtc->irq = dcss_vblank_irq_get(dcss);
+ if (crtc->irq < 0) {
+ dev_err(crtc->dev, "unable to get vblank interrupt\n");
+ return crtc->irq;
+ }
+
+ init_completion(&crtc->en_dis_completion);
+
+ ret = devm_request_irq(crtc->dev, crtc->irq, dcss_crtc_irq_handler,
+ IRQF_TRIGGER_RISING, "dcss_drm", crtc);
+ if (ret) {
+ dev_err(crtc->dev, "irq request failed with %d.\n", ret);
+ return ret;
+ }
+
+ disable_irq(crtc->irq);
+
+ return 0;
+}
+
+static int dcss_crtc_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct dcss_client_platformdata *pdata = dev->platform_data;
+ struct drm_device *drm = data;
+ struct dcss_crtc *crtc;
+ int ret;
+
+ crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
+ if (!crtc)
+ return -ENOMEM;
+
+ crtc->dev = dev;
+
+ ret = dcss_crtc_init(crtc, pdata, drm);
+ if (ret)
+ return ret;
+
+ if (!drm->mode_config.funcs)
+ drm->mode_config.funcs = &dcss_drm_mode_config_funcs;
+
+ if (!drm->mode_config.helper_private)
+ drm->mode_config.helper_private = &dcss_drm_mode_config_helpers;
+
+ dev_set_drvdata(dev, crtc);
+
+ return 0;
+}
+
+static void dcss_crtc_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+}
+
+static const struct component_ops dcss_crtc_ops = {
+ .bind = dcss_crtc_bind,
+ .unbind = dcss_crtc_unbind,
+};
+
+static int dcss_crtc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->platform_data) {
+ dev_err(dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ return component_add(dev, &dcss_crtc_ops);
+}
+
+static int dcss_crtc_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &dcss_crtc_ops);
+ return 0;
+}
+
+static struct platform_driver dcss_crtc_driver = {
+ .driver = {
+ .name = "imx-dcss-crtc",
+ },
+ .probe = dcss_crtc_probe,
+ .remove = dcss_crtc_remove,
+};
+module_platform_driver(dcss_crtc_driver);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@nxp.com>");
+MODULE_DESCRIPTION("i.MX DCSS CRTC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-dcss-crtc");