diff options
Diffstat (limited to 'drivers/gpu/drm/imx/dcss/dcss-crtc.c')
-rw-r--r-- | drivers/gpu/drm/imx/dcss/dcss-crtc.c | 501 |
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"); |