Commit 9021c317 authored by Laurentiu Palcu's avatar Laurentiu Palcu Committed by Lucas Stach

drm/imx: Add initial support for DCSS on iMX8MQ

This adds initial support for iMX8MQ's Display Controller Subsystem (DCSS).
Some of its capabilities include:
 * 4K@60fps;
 * HDR10;
 * one graphics and 2 video pipelines;
 * on-the-fly decompression of compressed video and graphics;

The reference manual can be found here:
https://www.nxp.com/webapp/Download?colCode=IMX8MDQLQRM

The current patch adds only basic functionality: one primary plane for
graphics, linear, tiled and super-tiled buffers support (no graphics
decompression yet), no HDR10 and no video planes.

Video planes support and HDR10 will be added in subsequent patches once
per-plane de-gamma/CSC/gamma support is in.
Signed-off-by: default avatarLaurentiu Palcu <laurentiu.palcu@nxp.com>
Reviewed-by: default avatarLucas Stach <l.stach@pengutronix.de>
Acked-by: default avatarGuido Günther <agx@sigxcpu.org>
Signed-off-by: default avatarLucas Stach <l.stach@pengutronix.de>
Link: https://patchwork.freedesktop.org/patch/msgid/20200731081836.3048-3-laurentiu.palcu@oss.nxp.com
parent ce625f45
......@@ -39,3 +39,5 @@ config DRM_IMX_HDMI
depends on DRM_IMX
help
Choose this if you want to use HDMI on i.MX6.
source "drivers/gpu/drm/imx/dcss/Kconfig"
......@@ -9,3 +9,4 @@ obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o
obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o
obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o
obj-$(CONFIG_DRM_IMX_DCSS) += dcss/
config DRM_IMX_DCSS
tristate "i.MX8MQ DCSS"
select IMX_IRQSTEER
select DRM_KMS_CMA_HELPER
select VIDEOMODE_HELPERS
depends on DRM && ARCH_MXC
help
Choose this if you have a NXP i.MX8MQ based system and want to use the
Display Controller Subsystem. This option enables DCSS support.
imx-dcss-objs := dcss-drv.o dcss-dev.o dcss-blkctl.o dcss-ctxld.o dcss-dtg.o \
dcss-ss.o dcss-dpr.o dcss-scaler.o dcss-kms.o dcss-crtc.o \
dcss-plane.o
obj-$(CONFIG_DRM_IMX_DCSS) += imx-dcss.o
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP.
*/
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include "dcss-dev.h"
#define DCSS_BLKCTL_RESET_CTRL 0x00
#define B_CLK_RESETN BIT(0)
#define APB_CLK_RESETN BIT(1)
#define P_CLK_RESETN BIT(2)
#define RTR_CLK_RESETN BIT(4)
#define DCSS_BLKCTL_CONTROL0 0x10
#define HDMI_MIPI_CLK_SEL BIT(0)
#define DISPMIX_REFCLK_SEL_POS 4
#define DISPMIX_REFCLK_SEL_MASK GENMASK(5, 4)
#define DISPMIX_PIXCLK_SEL BIT(8)
#define HDMI_SRC_SECURE_EN BIT(16)
struct dcss_blkctl {
struct dcss_dev *dcss;
void __iomem *base_reg;
};
void dcss_blkctl_cfg(struct dcss_blkctl *blkctl)
{
if (blkctl->dcss->hdmi_output)
dcss_writel(0, blkctl->base_reg + DCSS_BLKCTL_CONTROL0);
else
dcss_writel(DISPMIX_PIXCLK_SEL,
blkctl->base_reg + DCSS_BLKCTL_CONTROL0);
dcss_set(B_CLK_RESETN | APB_CLK_RESETN | P_CLK_RESETN | RTR_CLK_RESETN,
blkctl->base_reg + DCSS_BLKCTL_RESET_CTRL);
}
int dcss_blkctl_init(struct dcss_dev *dcss, unsigned long blkctl_base)
{
struct dcss_blkctl *blkctl;
blkctl = kzalloc(sizeof(*blkctl), GFP_KERNEL);
if (!blkctl)
return -ENOMEM;
blkctl->base_reg = ioremap(blkctl_base, SZ_4K);
if (!blkctl->base_reg) {
dev_err(dcss->dev, "unable to remap BLK CTRL base\n");
kfree(blkctl);
return -ENOMEM;
}
dcss->blkctl = blkctl;
blkctl->dcss = dcss;
dcss_blkctl_cfg(blkctl);
return 0;
}
void dcss_blkctl_exit(struct dcss_blkctl *blkctl)
{
if (blkctl->base_reg)
iounmap(blkctl->base_reg);
kfree(blkctl);
}
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_vblank.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "dcss-dev.h"
#include "dcss-kms.h"
static int dcss_enable_vblank(struct drm_crtc *crtc)
{
struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
base);
struct dcss_dev *dcss = crtc->dev->dev_private;
dcss_dtg_vblank_irq_enable(dcss->dtg, true);
dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, 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_dev *dcss = dcss_crtc->base.dev->dev_private;
disable_irq_nosync(dcss_crtc->irq);
dcss_dtg_vblank_irq_enable(dcss->dtg, false);
if (dcss_crtc->disable_ctxld_kick_irq)
dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, false);
}
static const struct drm_crtc_funcs dcss_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = drm_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = dcss_enable_vblank,
.disable_vblank = dcss_disable_vblank,
};
static void dcss_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
drm_crtc_vblank_on(crtc);
}
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_dev *dcss = dcss_crtc->base.dev->dev_private;
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);
if (dcss_dtg_is_enabled(dcss->dtg))
dcss_ctxld_enable(dcss->ctxld);
}
static void dcss_crtc_atomic_enable(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_dev *dcss = dcss_crtc->base.dev->dev_private;
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode;
struct videomode vm;
drm_display_mode_to_videomode(mode, &vm);
pm_runtime_get_sync(dcss->dev);
vm.pixelclock = mode->crtc_clock * 1000;
dcss_ss_subsam_set(dcss->ss);
dcss_dtg_css_set(dcss->dtg);
if (!drm_mode_equal(mode, old_mode) || !old_crtc_state->active) {
dcss_dtg_sync_set(dcss->dtg, &vm);
dcss_ss_sync_set(dcss->ss, &vm,
mode->flags & DRM_MODE_FLAG_PHSYNC,
mode->flags & DRM_MODE_FLAG_PVSYNC);
}
dcss_enable_dtg_and_ss(dcss);
dcss_ctxld_enable(dcss->ctxld);
/* Allow CTXLD kick interrupt to be disabled when VBLANK is disabled. */
dcss_crtc->disable_ctxld_kick_irq = 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_dev *dcss = dcss_crtc->base.dev->dev_private;
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode;
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_dtg_ctxld_kick_irq_enable(dcss->dtg, true);
reinit_completion(&dcss->disable_completion);
dcss_disable_dtg_and_ss(dcss);
dcss_ctxld_enable(dcss->ctxld);
if (!drm_mode_equal(mode, old_mode) || !crtc->state->active)
if (!wait_for_completion_timeout(&dcss->disable_completion,
msecs_to_jiffies(100)))
dev_err(dcss->dev, "Shutting off DTG timed out.\n");
/*
* Do not shut off CTXLD kick interrupt when shutting VBLANK off. It
* will be needed to commit the last changes, before going to suspend.
*/
dcss_crtc->disable_ctxld_kick_irq = false;
drm_crtc_vblank_off(crtc);
pm_runtime_mark_last_busy(dcss->dev);
pm_runtime_put_autosuspend(dcss->dev);
}
static const struct drm_crtc_helper_funcs dcss_helper_funcs = {
.atomic_begin = dcss_crtc_atomic_begin,
.atomic_flush = dcss_crtc_atomic_flush,
.atomic_enable = dcss_crtc_atomic_enable,
.atomic_disable = dcss_crtc_atomic_disable,
};
static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id)
{
struct dcss_crtc *dcss_crtc = dev_id;
struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
if (!dcss_dtg_vblank_irq_valid(dcss->dtg))
return IRQ_NONE;
if (dcss_ctxld_is_flushed(dcss->ctxld))
drm_crtc_handle_vblank(&dcss_crtc->base);
dcss_dtg_vblank_irq_clear(dcss->dtg);
return IRQ_HANDLED;
}
int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm)
{
struct dcss_dev *dcss = drm->dev_private;
struct platform_device *pdev = to_platform_device(dcss->dev);
int ret;
crtc->plane[0] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base),
DRM_PLANE_TYPE_PRIMARY, 0);
if (IS_ERR(crtc->plane[0]))
return PTR_ERR(crtc->plane[0]);
crtc->base.port = dcss->of_port;
drm_crtc_helper_add(&crtc->base, &dcss_helper_funcs);
ret = drm_crtc_init_with_planes(drm, &crtc->base, &crtc->plane[0]->base,
NULL, &dcss_crtc_funcs, NULL);
if (ret) {
dev_err(dcss->dev, "failed to init crtc\n");
return ret;
}
crtc->irq = platform_get_irq_byname(pdev, "vblank");
if (crtc->irq < 0)
return crtc->irq;
ret = request_irq(crtc->irq, dcss_crtc_irq_handler,
0, "dcss_drm", crtc);
if (ret) {
dev_err(dcss->dev, "irq request failed with %d.\n", ret);
return ret;
}
disable_irq(crtc->irq);
return 0;
}
void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm)
{
free_irq(crtc->irq, crtc);
}
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP.
*/
#include <linux/clk.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <drm/drm_modeset_helper.h>
#include "dcss-dev.h"
static void dcss_clocks_enable(struct dcss_dev *dcss)
{
clk_prepare_enable(dcss->axi_clk);
clk_prepare_enable(dcss->apb_clk);
clk_prepare_enable(dcss->rtrm_clk);
clk_prepare_enable(dcss->dtrc_clk);
clk_prepare_enable(dcss->pix_clk);
}
static void dcss_clocks_disable(struct dcss_dev *dcss)
{
clk_disable_unprepare(dcss->pix_clk);
clk_disable_unprepare(dcss->dtrc_clk);
clk_disable_unprepare(dcss->rtrm_clk);
clk_disable_unprepare(dcss->apb_clk);
clk_disable_unprepare(dcss->axi_clk);
}
static void dcss_disable_dtg_and_ss_cb(void *data)
{
struct dcss_dev *dcss = data;
dcss->disable_callback = NULL;
dcss_ss_shutoff(dcss->ss);
dcss_dtg_shutoff(dcss->dtg);
complete(&dcss->disable_completion);
}
void dcss_disable_dtg_and_ss(struct dcss_dev *dcss)
{
dcss->disable_callback = dcss_disable_dtg_and_ss_cb;
}
void dcss_enable_dtg_and_ss(struct dcss_dev *dcss)
{
if (dcss->disable_callback)
dcss->disable_callback = NULL;
dcss_dtg_enable(dcss->dtg);
dcss_ss_enable(dcss->ss);
}
static int dcss_submodules_init(struct dcss_dev *dcss)
{
int ret = 0;
u32 base_addr = dcss->start_addr;
const struct dcss_type_data *devtype = dcss->devtype;
dcss_clocks_enable(dcss);
ret = dcss_blkctl_init(dcss, base_addr + devtype->blkctl_ofs);
if (ret)
return ret;
ret = dcss_ctxld_init(dcss, base_addr + devtype->ctxld_ofs);
if (ret)
goto ctxld_err;
ret = dcss_dtg_init(dcss, base_addr + devtype->dtg_ofs);
if (ret)
goto dtg_err;
ret = dcss_ss_init(dcss, base_addr + devtype->ss_ofs);
if (ret)
goto ss_err;
ret = dcss_dpr_init(dcss, base_addr + devtype->dpr_ofs);
if (ret)
goto dpr_err;
ret = dcss_scaler_init(dcss, base_addr + devtype->scaler_ofs);
if (ret)
goto scaler_err;
dcss_clocks_disable(dcss);
return 0;
scaler_err:
dcss_dpr_exit(dcss->dpr);
dpr_err:
dcss_ss_exit(dcss->ss);
ss_err:
dcss_dtg_exit(dcss->dtg);
dtg_err:
dcss_ctxld_exit(dcss->ctxld);
ctxld_err:
dcss_blkctl_exit(dcss->blkctl);
dcss_clocks_disable(dcss);
return ret;
}
static void dcss_submodules_stop(struct dcss_dev *dcss)
{
dcss_clocks_enable(dcss);
dcss_scaler_exit(dcss->scaler);
dcss_dpr_exit(dcss->dpr);
dcss_ss_exit(dcss->ss);
dcss_dtg_exit(dcss->dtg);
dcss_ctxld_exit(dcss->ctxld);
dcss_blkctl_exit(dcss->blkctl);
dcss_clocks_disable(dcss);
}
static int dcss_clks_init(struct dcss_dev *dcss)
{
int i;
struct {
const char *id;
struct clk **clk;
} clks[] = {
{"apb", &dcss->apb_clk},
{"axi", &dcss->axi_clk},
{"pix", &dcss->pix_clk},
{"rtrm", &dcss->rtrm_clk},
{"dtrc", &dcss->dtrc_clk},
};
for (i = 0; i < ARRAY_SIZE(clks); i++) {
*clks[i].clk = devm_clk_get(dcss->dev, clks[i].id);
if (IS_ERR(*clks[i].clk)) {
dev_err(dcss->dev, "failed to get %s clock\n",
clks[i].id);
return PTR_ERR(*clks[i].clk);
}
}
return 0;
}
static void dcss_clks_release(struct dcss_dev *dcss)
{
devm_clk_put(dcss->dev, dcss->dtrc_clk);
devm_clk_put(dcss->dev, dcss->rtrm_clk);
devm_clk_put(dcss->dev, dcss->pix_clk);
devm_clk_put(dcss->dev, dcss->axi_clk);
devm_clk_put(dcss->dev, dcss->apb_clk);
}
struct dcss_dev *dcss_dev_create(struct device *dev, bool hdmi_output)
{
struct platform_device *pdev = to_platform_device(dev);
int ret;
struct resource *res;
struct dcss_dev *dcss;
const struct dcss_type_data *devtype;
devtype = of_device_get_match_data(dev);
if (!devtype) {
dev_err(dev, "no device match found\n");
return ERR_PTR(-ENODEV);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "cannot get memory resource\n");
return ERR_PTR(-EINVAL);
}
dcss = kzalloc(sizeof(*dcss), GFP_KERNEL);
if (!dcss)
return ERR_PTR(-ENOMEM);
dcss->dev = dev;
dcss->devtype = devtype;
dcss->hdmi_output = hdmi_output;
ret = dcss_clks_init(dcss);
if (ret) {
dev_err(dev, "clocks initialization failed\n");
goto err;
}
dcss->of_port = of_graph_get_port_by_id(dev->of_node, 0);
if (!dcss->of_port) {
dev_err(dev, "no port@0 node in %s\n", dev->of_node->full_name);
ret = -ENODEV;
goto clks_err;
}
dcss->start_addr = res->start;
ret = dcss_submodules_init(dcss);
if (ret) {
dev_err(dev, "submodules initialization failed\n");
goto clks_err;
}
init_completion(&dcss->disable_completion);
pm_runtime_set_autosuspend_delay(dev, 100);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_suspended(dev);
pm_runtime_allow(dev);
pm_runtime_enable(dev);
return dcss;
clks_err:
dcss_clks_release(dcss);
err:
kfree(dcss);
return ERR_PTR(ret);
}
void dcss_dev_destroy(struct dcss_dev *dcss)
{
if (!pm_runtime_suspended(dcss->dev)) {
dcss_ctxld_suspend(dcss->ctxld);
dcss_clocks_disable(dcss);
}
pm_runtime_disable(dcss->dev);
dcss_submodules_stop(dcss);
dcss_clks_release(dcss);
kfree(dcss);
}
#ifdef CONFIG_PM_SLEEP
int dcss_dev_suspend(struct device *dev)
{
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev);
int ret;
drm_mode_config_helper_suspend(dcss_drv_dev_to_drm(dev));
if (pm_runtime_suspended(dev))
return 0;
ret = dcss_ctxld_suspend(dcss->ctxld);
if (ret)
return ret;
dcss_clocks_disable(dcss);
return 0;
}
int dcss_dev_resume(struct device *dev)
{
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev);
if (pm_runtime_suspended(dev)) {
drm_mode_config_helper_resume(dcss_drv_dev_to_drm(dev));
return 0;
}
dcss_clocks_enable(dcss);
dcss_blkctl_cfg(dcss->blkctl);
dcss_ctxld_resume(dcss->ctxld);
drm_mode_config_helper_resume(dcss_drv_dev_to_drm(dev));
return 0;
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
int dcss_dev_runtime_suspend(struct device *dev)
{
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev);
int ret;
ret = dcss_ctxld_suspend(dcss->ctxld);
if (ret)
return ret;
dcss_clocks_disable(dcss);
return 0;
}
int dcss_dev_runtime_resume(struct device *dev)
{
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev);
dcss_clocks_enable(dcss);
dcss_blkctl_cfg(dcss->blkctl);
dcss_ctxld_resume(dcss->ctxld);
return 0;
}
#endif /* CONFIG_PM */
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2019 NXP.
*/
#ifndef __DCSS_PRV_H__
#define __DCSS_PRV_H__
#include <drm/drm_fourcc.h>
#include <linux/io.h>
#include <video/videomode.h>
#define SET 0x04
#define CLR 0x08
#define TGL 0x0C
#define dcss_writel(v, c) writel((v), (c))
#define dcss_readl(c) readl(c)
#define dcss_set(v, c) writel((v), (c) + SET)
#define dcss_clr(v, c) writel((v), (c) + CLR)
#define dcss_toggle(v, c) writel((v), (c) + TGL)
static inline void dcss_update(u32 v, u32 m, void __iomem *c)
{
writel((readl(c) & ~(m)) | (v), (c));
}
#define DCSS_DBG_REG(reg) {.name = #reg, .ofs = reg}
enum {
DCSS_IMX8MQ = 0,
};
struct dcss_type_data {
const char *name;
u32 blkctl_ofs;
u32 ctxld_ofs;
u32 rdsrc_ofs;
u32 wrscl_ofs;
u32 dtg_ofs;
u32 scaler_ofs;
u32 ss_ofs;
u32 dpr_ofs;
u32 dtrc_ofs;
u32 dec400d_ofs;
u32 hdr10_ofs;
};
struct dcss_debug_reg {
char *name;
u32 ofs;
};
enum dcss_ctxld_ctx_type {
CTX_DB,
CTX_SB_HP, /* high-priority */
CTX_SB_LP, /* low-priority */
};
struct dcss_dev {
struct device *dev;
const struct dcss_type_data *devtype;
struct device_node *of_port;
u32 start_addr;
struct dcss_blkctl *blkctl;
struct dcss_ctxld *ctxld;
struct dcss_dpr *dpr;
struct dcss_dtg *dtg;
struct dcss_ss *ss;
struct dcss_hdr10 *hdr10;
struct dcss_scaler *scaler;
struct dcss_dtrc *dtrc;
struct dcss_dec400d *dec400d;
struct dcss_wrscl *wrscl;
struct dcss_rdsrc *rdsrc;
struct clk *apb_clk;
struct clk *axi_clk;
struct clk *pix_clk;
struct clk *rtrm_clk;
struct clk *dtrc_clk;
struct clk *pll_src_clk;
struct clk *pll_phy_ref_clk;
bool hdmi_output;
void (*disable_callback)(void *data);
struct completion disable_completion;
};
struct dcss_dev *dcss_drv_dev_to_dcss(struct device *dev);
struct drm_device *dcss_drv_dev_to_drm(struct device *dev);
struct dcss_dev *dcss_dev_create(struct device *dev, bool hdmi_output);
void dcss_dev_destroy(struct dcss_dev *dcss);
int dcss_dev_runtime_suspend(struct device *dev);
int dcss_dev_runtime_resume(struct device *dev);
int dcss_dev_suspend(struct device *dev);
int dcss_dev_resume(struct device *dev);
void dcss_enable_dtg_and_ss(struct dcss_dev *dcss);
void dcss_disable_dtg_and_ss(struct dcss_dev *dcss);
/* BLKCTL */
int dcss_blkctl_init(struct dcss_dev *dcss, unsigned long blkctl_base);
void dcss_blkctl_cfg(struct dcss_blkctl *blkctl);
void dcss_blkctl_exit(struct dcss_blkctl *blkctl);
/* CTXLD */
int dcss_ctxld_init(struct dcss_dev *dcss, unsigned long ctxld_base);
void dcss_ctxld_exit(struct dcss_ctxld *ctxld);
void dcss_ctxld_write(struct dcss_ctxld *ctxld, u32 ctx_id,
u32 val, u32 reg_idx);
int dcss_ctxld_resume(struct dcss_ctxld *dcss_ctxld);
int dcss_ctxld_suspend(struct dcss_ctxld *dcss_ctxld);
void dcss_ctxld_write_irqsafe(struct dcss_ctxld *ctlxd, u32 ctx_id, u32 val,
u32 reg_ofs);
void dcss_ctxld_kick(struct dcss_ctxld *ctxld);
bool dcss_ctxld_is_flushed(struct dcss_ctxld *ctxld);
int dcss_ctxld_enable(struct dcss_ctxld *ctxld);
void dcss_ctxld_register_completion(struct dcss_ctxld *ctxld,
struct completion *dis_completion);
void dcss_ctxld_assert_locked(struct dcss_ctxld *ctxld);
/* DPR */
int dcss_dpr_init(struct dcss_dev *dcss, unsigned long dpr_base);
void dcss_dpr_exit(struct dcss_dpr *dpr);
void dcss_dpr_write_sysctrl(struct dcss_dpr *dpr);
void dcss_dpr_set_res(struct dcss_dpr *dpr, int ch_num, u32 xres, u32 yres);
void dcss_dpr_addr_set(struct dcss_dpr *dpr, int ch_num, u32 luma_base_addr,
u32 chroma_base_addr, u16 pitch);
void dcss_dpr_enable(struct dcss_dpr *dpr, int ch_num, bool en);
void dcss_dpr_format_set(struct dcss_dpr *dpr, int ch_num,
const struct drm_format_info *format, u64 modifier);
void dcss_dpr_set_rotation(struct dcss_dpr *dpr, int ch_num, u32 rotation);
/* DTG */
int dcss_dtg_init(struct dcss_dev *dcss, unsigned long dtg_base);
void dcss_dtg_exit(struct dcss_dtg *dtg);
bool dcss_dtg_vblank_irq_valid(struct dcss_dtg *dtg);
void dcss_dtg_vblank_irq_enable(struct dcss_dtg *dtg, bool en);
void dcss_dtg_vblank_irq_clear(struct dcss_dtg *dtg);
void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm);
void dcss_dtg_css_set(struct dcss_dtg *dtg);
void dcss_dtg_enable(struct dcss_dtg *dtg);
void dcss_dtg_shutoff(struct dcss_dtg *dtg);
bool dcss_dtg_is_enabled(struct dcss_dtg *dtg);
void dcss_dtg_ctxld_kick_irq_enable(struct dcss_dtg *dtg, bool en);
bool dcss_dtg_global_alpha_changed(struct dcss_dtg *dtg, int ch_num, int alpha);
void dcss_dtg_plane_alpha_set(struct dcss_dtg *dtg, int ch_num,
const struct drm_format_info *format, int alpha);
void dcss_dtg_plane_pos_set(struct dcss_dtg *dtg, int ch_num,
int px, int py, int pw, int ph);
void dcss_dtg_ch_enable(struct dcss_dtg *dtg, int ch_num, bool en);
/* SUBSAM */
int dcss_ss_init(struct dcss_dev *dcss, unsigned long subsam_base);
void dcss_ss_exit(struct dcss_ss *ss);
void dcss_ss_enable(struct dcss_ss *ss);
void dcss_ss_shutoff(struct dcss_ss *ss);
void dcss_ss_subsam_set(struct dcss_ss *ss);
void dcss_ss_sync_set(struct dcss_ss *ss, struct videomode *vm,
bool phsync, bool pvsync);
/* SCALER */
int dcss_scaler_init(struct dcss_dev *dcss, unsigned long scaler_base);
void dcss_scaler_exit(struct dcss_scaler *scl);
void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num,
const struct drm_format_info *format,
int src_xres, int src_yres, int dst_xres, int dst_yres,
u32 vrefresh_hz);
void dcss_scaler_ch_enable(struct dcss_scaler *scl, int ch_num, bool en);
int dcss_scaler_get_min_max_ratios(struct dcss_scaler *scl, int ch_num,
int *min, int *max);
void dcss_scaler_write_sclctrl(struct dcss_scaler *scl);
#endif /* __DCSS_PRV_H__ */
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <drm/drm_of.h>
#include "dcss-dev.h"
#include "dcss-kms.h"
struct dcss_drv {
struct dcss_dev *dcss;
struct dcss_kms_dev *kms;
};
struct dcss_dev *dcss_drv_dev_to_dcss(struct device *dev)
{
struct dcss_drv *mdrv = dev_get_drvdata(dev);
return mdrv ? mdrv->dcss : NULL;
}
struct drm_device *dcss_drv_dev_to_drm(struct device *dev)
{
struct dcss_drv *mdrv = dev_get_drvdata(dev);
return mdrv ? &mdrv->kms->base : NULL;
}
static int dcss_drv_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *remote;
struct dcss_drv *mdrv;
int err = 0;
bool hdmi_output = true;
if (!dev->of_node)
return -ENODEV;
remote = of_graph_get_remote_node(dev->of_node, 0, 0);
if (!remote)
return -ENODEV;
hdmi_output = !of_device_is_compatible(remote, "fsl,imx8mq-nwl-dsi");
of_node_put(remote);
mdrv = kzalloc(sizeof(*mdrv), GFP_KERNEL);
if (!mdrv)
return -ENOMEM;
mdrv->dcss = dcss_dev_create(dev, hdmi_output);
if (IS_ERR(mdrv->dcss)) {
err = PTR_ERR(mdrv->dcss);
goto err;
}
dev_set_drvdata(dev, mdrv);
mdrv->kms = dcss_kms_attach(mdrv->dcss);
if (IS_ERR(mdrv->kms)) {
err = PTR_ERR(mdrv->kms);
goto dcss_shutoff;
}
return 0;
dcss_shutoff:
dcss_dev_destroy(mdrv->dcss);
dev_set_drvdata(dev, NULL);
err:
kfree(mdrv);
return err;
}
static int dcss_drv_platform_remove(struct platform_device *pdev)
{
struct dcss_drv *mdrv = dev_get_drvdata(&pdev->dev);
if (!mdrv)
return 0;
dcss_kms_detach(mdrv->kms);
dcss_dev_destroy(mdrv->dcss);
dev_set_drvdata(&pdev->dev, NULL);
kfree(mdrv);
return 0;
}
static struct dcss_type_data dcss_types[] = {
[DCSS_IMX8MQ] = {
.name = "DCSS_IMX8MQ",
.blkctl_ofs = 0x2F000,
.ctxld_ofs = 0x23000,
.dtg_ofs = 0x20000,
.scaler_ofs = 0x1C000,
.ss_ofs = 0x1B000,
.dpr_ofs = 0x18000,
},
};
static const struct of_device_id dcss_of_match[] = {
{ .compatible = "nxp,imx8mq-dcss", .data = &dcss_types[DCSS_IMX8MQ], },
{},
};
MODULE_DEVICE_TABLE(of, dcss_of_match);
static const struct dev_pm_ops dcss_dev_pm = {
SET_SYSTEM_SLEEP_PM_OPS(dcss_dev_suspend, dcss_dev_resume)
SET_RUNTIME_PM_OPS(dcss_dev_runtime_suspend,
dcss_dev_runtime_resume, NULL)
};
static struct platform_driver dcss_platform_driver = {
.probe = dcss_drv_platform_probe,
.remove = dcss_drv_platform_remove,
.driver = {
.name = "imx-dcss",
.of_match_table = dcss_of_match,
.pm = &dcss_dev_pm,
},
};
module_platform_driver(dcss_platform_driver);
MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@nxp.com>");
MODULE_DESCRIPTION("DCSS driver for i.MX8MQ");
MODULE_LICENSE("GPL v2");
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP.
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "dcss-dev.h"
#include "dcss-kms.h"
DEFINE_DRM_GEM_CMA_FOPS(dcss_cma_fops);
static const struct drm_mode_config_funcs dcss_drm_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static struct drm_driver dcss_kms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.gem_free_object_unlocked = drm_gem_cma_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.dumb_create = drm_gem_cma_dumb_create,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = drm_gem_prime_import,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
.gem_prime_vmap = drm_gem_cma_prime_vmap,
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
.gem_prime_mmap = drm_gem_cma_prime_mmap,
.fops = &dcss_cma_fops,
.name = "imx-dcss",
.desc = "i.MX8MQ Display Subsystem",
.date = "20190917",
.major = 1,
.minor = 0,
.patchlevel = 0,
};
static const struct drm_mode_config_helper_funcs dcss_mode_config_helpers = {
.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
};
static void dcss_kms_mode_config_init(struct dcss_kms_dev *kms)
{
struct drm_mode_config *config = &kms->base.mode_config;
drm_mode_config_init(&kms->base);
config->min_width = 1;
config->min_height = 1;
config->max_width = 4096;
config->max_height = 4096;
config->allow_fb_modifiers = true;
config->normalize_zpos = true;
config->funcs = &dcss_drm_mode_config_funcs;
config->helper_private = &dcss_mode_config_helpers;
}
static const struct drm_encoder_funcs dcss_kms_simple_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static int dcss_kms_setup_encoder(struct dcss_kms_dev *kms)
{
struct drm_device *ddev = &kms->base;
struct drm_encoder *encoder = &kms->encoder;
struct drm_crtc *crtc = (struct drm_crtc *)&kms->crtc;
struct drm_panel *panel;
struct drm_bridge *bridge;
int ret;
ret = drm_of_find_panel_or_bridge(ddev->dev->of_node, 0, 0,
&panel, &bridge);
if (ret)
return ret;
if (!bridge) {
dev_err(ddev->dev, "No bridge found %d.\n", ret);
return -ENODEV;
}
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drm_encoder_init(&kms->base, encoder,
&dcss_kms_simple_encoder_funcs,
DRM_MODE_ENCODER_NONE, NULL);
if (ret) {
dev_err(ddev->dev, "Failed initializing encoder %d.\n", ret);
return ret;
}
return drm_bridge_attach(encoder, bridge, NULL, 0);
}
struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss)
{
struct dcss_kms_dev *kms;
struct drm_device *drm;
struct dcss_crtc *crtc;
int ret;
kms = devm_drm_dev_alloc(dcss->dev, &dcss_kms_driver,
struct dcss_kms_dev, base);
if (IS_ERR(kms))
return kms;
drm = &kms->base;
crtc = &kms->crtc;
drm->dev_private = dcss;
dcss_kms_mode_config_init(kms);
ret = drm_vblank_init(drm, 1);
if (ret)
goto cleanup_mode_config;
drm->irq_enabled = true;
ret = dcss_crtc_init(crtc, drm);
if (ret)
goto cleanup_mode_config;
ret = dcss_kms_setup_encoder(kms);
if (ret)
goto cleanup_crtc;
drm_mode_config_reset(drm);
drm_kms_helper_poll_init(drm);
ret = drm_dev_register(drm, 0);
if (ret)
goto cleanup_crtc;
drm_fbdev_generic_setup(drm, 32);
return kms;
cleanup_crtc:
drm_kms_helper_poll_fini(drm);
dcss_crtc_deinit(crtc, drm);
cleanup_mode_config:
drm_mode_config_cleanup(drm);
drm->dev_private = NULL;
return ERR_PTR(ret);
}
void dcss_kms_detach(struct dcss_kms_dev *kms)
{
struct drm_device *drm = &kms->base;
drm_dev_unregister(drm);
drm_kms_helper_poll_fini(drm);
drm_atomic_helper_shutdown(drm);
drm_crtc_vblank_off(&kms->crtc.base);
drm->irq_enabled = false;
drm_mode_config_cleanup(drm);
dcss_crtc_deinit(&kms->crtc, drm);
drm->dev_private = NULL;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2019 NXP.
*/
#ifndef _DCSS_KMS_H_
#define _DCSS_KMS_H_
#include <drm/drm_encoder.h>
struct dcss_plane {
struct drm_plane base;
int ch_num;
};
struct dcss_crtc {
struct drm_crtc base;
struct drm_crtc_state *state;
struct dcss_plane *plane[3];
int irq;
bool disable_ctxld_kick_irq;
};
struct dcss_kms_dev {
struct drm_device base;
struct dcss_crtc crtc;
struct drm_encoder encoder;
};
struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss);
void dcss_kms_detach(struct dcss_kms_dev *kms);
int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm);
void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm);
struct dcss_plane *dcss_plane_init(struct drm_device *drm,
unsigned int possible_crtcs,
enum drm_plane_type type,
unsigned int zpos);
#endif
This diff is collapsed.
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP.
*/
#include <linux/device.h>
#include <linux/slab.h>
#include "dcss-dev.h"
#define DCSS_SS_SYS_CTRL 0x00
#define RUN_EN BIT(0)
#define DCSS_SS_DISPLAY 0x10
#define LRC_X_POS 0
#define LRC_X_MASK GENMASK(12, 0)
#define LRC_Y_POS 16
#define LRC_Y_MASK GENMASK(28, 16)
#define DCSS_SS_HSYNC 0x20
#define DCSS_SS_VSYNC 0x30
#define SYNC_START_POS 0
#define SYNC_START_MASK GENMASK(12, 0)
#define SYNC_END_POS 16
#define SYNC_END_MASK GENMASK(28, 16)
#define SYNC_POL BIT(31)
#define DCSS_SS_DE_ULC 0x40
#define ULC_X_POS 0
#define ULC_X_MASK GENMASK(12, 0)
#define ULC_Y_POS 16
#define ULC_Y_MASK GENMASK(28, 16)
#define ULC_POL BIT(31)
#define DCSS_SS_DE_LRC 0x50
#define DCSS_SS_MODE 0x60
#define PIPE_MODE_POS 0
#define PIPE_MODE_MASK GENMASK(1, 0)
#define DCSS_SS_COEFF 0x70
#define HORIZ_A_POS 0
#define HORIZ_A_MASK GENMASK(3, 0)
#define HORIZ_B_POS 4
#define HORIZ_B_MASK GENMASK(7, 4)
#define HORIZ_C_POS 8
#define HORIZ_C_MASK GENMASK(11, 8)
#define HORIZ_H_NORM_POS 12
#define HORIZ_H_NORM_MASK GENMASK(14, 12)
#define VERT_A_POS 16
#define VERT_A_MASK GENMASK(19, 16)
#define VERT_B_POS 20
#define VERT_B_MASK GENMASK(23, 20)
#define VERT_C_POS 24
#define VERT_C_MASK GENMASK(27, 24)
#define VERT_H_NORM_POS 28
#define VERT_H_NORM_MASK GENMASK(30, 28)
#define DCSS_SS_CLIP_CB 0x80
#define DCSS_SS_CLIP_CR 0x90
#define CLIP_MIN_POS 0
#define CLIP_MIN_MASK GENMASK(9, 0)
#define CLIP_MAX_POS 0
#define CLIP_MAX_MASK GENMASK(23, 16)
#define DCSS_SS_INTER_MODE 0xA0
#define INT_EN BIT(0)
#define VSYNC_SHIFT BIT(1)
struct dcss_ss {
struct device *dev;
void __iomem *base_reg;
u32 base_ofs;
struct dcss_ctxld *ctxld;
u32 ctx_id;
bool in_use;
};
static void dcss_ss_write(struct dcss_ss *ss, u32 val, u32 ofs)
{
if (!ss->in_use)
dcss_writel(val, ss->base_reg + ofs);
dcss_ctxld_write(ss->ctxld, ss->ctx_id, val,
ss->base_ofs + ofs);
}
int dcss_ss_init(struct dcss_dev *dcss, unsigned long ss_base)
{
struct dcss_ss *ss;
ss = kzalloc(sizeof(*ss), GFP_KERNEL);
if (!ss)
return -ENOMEM;
dcss->ss = ss;
ss->dev = dcss->dev;
ss->ctxld = dcss->ctxld;
ss->base_reg = ioremap(ss_base, SZ_4K);
if (!ss->base_reg) {
dev_err(dcss->dev, "ss: unable to remap ss base\n");
kfree(ss);
return -ENOMEM;
}
ss->base_ofs = ss_base;
ss->ctx_id = CTX_SB_HP;
return 0;
}
void dcss_ss_exit(struct dcss_ss *ss)
{
/* stop SS */
dcss_writel(0, ss->base_reg + DCSS_SS_SYS_CTRL);
if (ss->base_reg)
iounmap(ss->base_reg);
kfree(ss);
}
void dcss_ss_subsam_set(struct dcss_ss *ss)
{
dcss_ss_write(ss, 0x41614161, DCSS_SS_COEFF);
dcss_ss_write(ss, 0, DCSS_SS_MODE);
dcss_ss_write(ss, 0x03ff0000, DCSS_SS_CLIP_CB);
dcss_ss_write(ss, 0x03ff0000, DCSS_SS_CLIP_CR);
}
void dcss_ss_sync_set(struct dcss_ss *ss, struct videomode *vm,
bool phsync, bool pvsync)
{
u16 lrc_x, lrc_y;
u16 hsync_start, hsync_end;
u16 vsync_start, vsync_end;
u16 de_ulc_x, de_ulc_y;
u16 de_lrc_x, de_lrc_y;
lrc_x = vm->hfront_porch + vm->hback_porch + vm->hsync_len +
vm->hactive - 1;
lrc_y = vm->vfront_porch + vm->vback_porch + vm->vsync_len +
vm->vactive - 1;
dcss_ss_write(ss, (lrc_y << LRC_Y_POS) | lrc_x, DCSS_SS_DISPLAY);
hsync_start = vm->hfront_porch + vm->hback_porch + vm->hsync_len +
vm->hactive - 1;
hsync_end = vm->hsync_len - 1;
dcss_ss_write(ss, (phsync ? SYNC_POL : 0) |
((u32)hsync_end << SYNC_END_POS) | hsync_start,
DCSS_SS_HSYNC);
vsync_start = vm->vfront_porch - 1;
vsync_end = vm->vfront_porch + vm->vsync_len - 1;
dcss_ss_write(ss, (pvsync ? SYNC_POL : 0) |
((u32)vsync_end << SYNC_END_POS) | vsync_start,
DCSS_SS_VSYNC);
de_ulc_x = vm->hsync_len + vm->hback_porch - 1;
de_ulc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch;
dcss_ss_write(ss, SYNC_POL | ((u32)de_ulc_y << ULC_Y_POS) | de_ulc_x,
DCSS_SS_DE_ULC);
de_lrc_x = vm->hsync_len + vm->hback_porch + vm->hactive - 1;
de_lrc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch +
vm->vactive - 1;
dcss_ss_write(ss, (de_lrc_y << LRC_Y_POS) | de_lrc_x, DCSS_SS_DE_LRC);
}
void dcss_ss_enable(struct dcss_ss *ss)
{
dcss_ss_write(ss, RUN_EN, DCSS_SS_SYS_CTRL);
ss->in_use = true;
}
void dcss_ss_shutoff(struct dcss_ss *ss)
{
dcss_writel(0, ss->base_reg + DCSS_SS_SYS_CTRL);
ss->in_use = false;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment