blob: 3c2e5029f9f262999f2d6d4847afb698be15497e [file] [log] [blame]
/*
* 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"
struct dcss_crtc {
struct device *dev;
struct drm_crtc base;
struct imx_drm_crtc *imx_crtc;
struct dcss_plane *plane[3];
int irq;
struct drm_property *alpha;
struct drm_property *use_global;
struct drm_property *dtrc_table_ofs;
struct completion disable_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;
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);
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_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->bpc >= 10))
dcss_crtc->opipe_pix_format = DRM_FORMAT_P010;
else
dcss_crtc->opipe_pix_format = DRM_FORMAT_ARGB8888;
DRM_INFO("OPIPE_CFG: gamut = %d, nl = %d, pr = %d, pix_format = %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 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_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);
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);
drm_crtc_vblank_off(crtc);
dcss_ss_enable(dcss, false);
dcss_dtg_enable(dcss, false, &dcss_crtc->disable_completion);
dcss_ctxld_enable(dcss);
crtc->enabled = false;
wait_for_completion_timeout(&dcss_crtc->disable_completion,
msecs_to_jiffies(100));
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);
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);
}
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);
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->disable_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");