You've already forked linux-rockchip
mirror of
https://github.com/armbian/linux-rockchip.git
synced 2026-01-06 11:08:10 -08:00
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: Laurentiu Palcu <laurentiu.palcu@nxp.com> Reviewed-by: Lucas Stach <l.stach@pengutronix.de> Acked-by: Guido Günther <agx@sigxcpu.org> Signed-off-by: Lucas Stach <l.stach@pengutronix.de> Link: https://patchwork.freedesktop.org/patch/msgid/20200731081836.3048-3-laurentiu.palcu@oss.nxp.com
This commit is contained in:
committed by
Lucas Stach
parent
ce625f4567
commit
9021c317b7
@@ -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/
|
||||
|
||||
9
drivers/gpu/drm/imx/dcss/Kconfig
Normal file
9
drivers/gpu/drm/imx/dcss/Kconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
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.
|
||||
6
drivers/gpu/drm/imx/dcss/Makefile
Normal file
6
drivers/gpu/drm/imx/dcss/Makefile
Normal file
@@ -0,0 +1,6 @@
|
||||
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
|
||||
|
||||
70
drivers/gpu/drm/imx/dcss/dcss-blkctl.c
Normal file
70
drivers/gpu/drm/imx/dcss/dcss-blkctl.c
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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);
|
||||
}
|
||||
219
drivers/gpu/drm/imx/dcss/dcss-crtc.c
Normal file
219
drivers/gpu/drm/imx/dcss/dcss-crtc.c
Normal file
@@ -0,0 +1,219 @@
|
||||
// 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);
|
||||
}
|
||||
424
drivers/gpu/drm/imx/dcss/dcss-ctxld.c
Normal file
424
drivers/gpu/drm/imx/dcss/dcss-ctxld.c
Normal file
@@ -0,0 +1,424 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 NXP.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "dcss-dev.h"
|
||||
|
||||
#define DCSS_CTXLD_CONTROL_STATUS 0x0
|
||||
#define CTXLD_ENABLE BIT(0)
|
||||
#define ARB_SEL BIT(1)
|
||||
#define RD_ERR_EN BIT(2)
|
||||
#define DB_COMP_EN BIT(3)
|
||||
#define SB_HP_COMP_EN BIT(4)
|
||||
#define SB_LP_COMP_EN BIT(5)
|
||||
#define DB_PEND_SB_REC_EN BIT(6)
|
||||
#define SB_PEND_DISP_ACTIVE_EN BIT(7)
|
||||
#define AHB_ERR_EN BIT(8)
|
||||
#define RD_ERR BIT(16)
|
||||
#define DB_COMP BIT(17)
|
||||
#define SB_HP_COMP BIT(18)
|
||||
#define SB_LP_COMP BIT(19)
|
||||
#define DB_PEND_SB_REC BIT(20)
|
||||
#define SB_PEND_DISP_ACTIVE BIT(21)
|
||||
#define AHB_ERR BIT(22)
|
||||
#define DCSS_CTXLD_DB_BASE_ADDR 0x10
|
||||
#define DCSS_CTXLD_DB_COUNT 0x14
|
||||
#define DCSS_CTXLD_SB_BASE_ADDR 0x18
|
||||
#define DCSS_CTXLD_SB_COUNT 0x1C
|
||||
#define SB_HP_COUNT_POS 0
|
||||
#define SB_HP_COUNT_MASK 0xffff
|
||||
#define SB_LP_COUNT_POS 16
|
||||
#define SB_LP_COUNT_MASK 0xffff0000
|
||||
#define DCSS_AHB_ERR_ADDR 0x20
|
||||
|
||||
#define CTXLD_IRQ_COMPLETION (DB_COMP | SB_HP_COMP | SB_LP_COMP)
|
||||
#define CTXLD_IRQ_ERROR (RD_ERR | DB_PEND_SB_REC | AHB_ERR)
|
||||
|
||||
/* The following sizes are in context loader entries, 8 bytes each. */
|
||||
#define CTXLD_DB_CTX_ENTRIES 1024 /* max 65536 */
|
||||
#define CTXLD_SB_LP_CTX_ENTRIES 10240 /* max 65536 */
|
||||
#define CTXLD_SB_HP_CTX_ENTRIES 20000 /* max 65536 */
|
||||
#define CTXLD_SB_CTX_ENTRIES (CTXLD_SB_LP_CTX_ENTRIES + \
|
||||
CTXLD_SB_HP_CTX_ENTRIES)
|
||||
|
||||
/* Sizes, in entries, of the DB, SB_HP and SB_LP context regions. */
|
||||
static u16 dcss_ctxld_ctx_size[3] = {
|
||||
CTXLD_DB_CTX_ENTRIES,
|
||||
CTXLD_SB_HP_CTX_ENTRIES,
|
||||
CTXLD_SB_LP_CTX_ENTRIES
|
||||
};
|
||||
|
||||
/* this represents an entry in the context loader map */
|
||||
struct dcss_ctxld_item {
|
||||
u32 val;
|
||||
u32 ofs;
|
||||
};
|
||||
|
||||
#define CTX_ITEM_SIZE sizeof(struct dcss_ctxld_item)
|
||||
|
||||
struct dcss_ctxld {
|
||||
struct device *dev;
|
||||
void __iomem *ctxld_reg;
|
||||
int irq;
|
||||
bool irq_en;
|
||||
|
||||
struct dcss_ctxld_item *db[2];
|
||||
struct dcss_ctxld_item *sb_hp[2];
|
||||
struct dcss_ctxld_item *sb_lp[2];
|
||||
|
||||
dma_addr_t db_paddr[2];
|
||||
dma_addr_t sb_paddr[2];
|
||||
|
||||
u16 ctx_size[2][3]; /* holds the sizes of DB, SB_HP and SB_LP ctx */
|
||||
u8 current_ctx;
|
||||
|
||||
bool in_use;
|
||||
bool armed;
|
||||
|
||||
spinlock_t lock; /* protects concurent access to private data */
|
||||
};
|
||||
|
||||
static irqreturn_t dcss_ctxld_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct dcss_ctxld *ctxld = data;
|
||||
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(ctxld->dev);
|
||||
u32 irq_status;
|
||||
|
||||
irq_status = dcss_readl(ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS);
|
||||
|
||||
if (irq_status & CTXLD_IRQ_COMPLETION &&
|
||||
!(irq_status & CTXLD_ENABLE) && ctxld->in_use) {
|
||||
ctxld->in_use = false;
|
||||
|
||||
if (dcss && dcss->disable_callback)
|
||||
dcss->disable_callback(dcss);
|
||||
} else if (irq_status & CTXLD_IRQ_ERROR) {
|
||||
/*
|
||||
* Except for throwing an error message and clearing the status
|
||||
* register, there's not much we can do here.
|
||||
*/
|
||||
dev_err(ctxld->dev, "ctxld: error encountered: %08x\n",
|
||||
irq_status);
|
||||
dev_err(ctxld->dev, "ctxld: db=%d, sb_hp=%d, sb_lp=%d\n",
|
||||
ctxld->ctx_size[ctxld->current_ctx ^ 1][CTX_DB],
|
||||
ctxld->ctx_size[ctxld->current_ctx ^ 1][CTX_SB_HP],
|
||||
ctxld->ctx_size[ctxld->current_ctx ^ 1][CTX_SB_LP]);
|
||||
}
|
||||
|
||||
dcss_clr(irq_status & (CTXLD_IRQ_ERROR | CTXLD_IRQ_COMPLETION),
|
||||
ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int dcss_ctxld_irq_config(struct dcss_ctxld *ctxld,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ctxld->irq = platform_get_irq_byname(pdev, "ctxld");
|
||||
if (ctxld->irq < 0)
|
||||
return ctxld->irq;
|
||||
|
||||
ret = request_irq(ctxld->irq, dcss_ctxld_irq_handler,
|
||||
0, "dcss_ctxld", ctxld);
|
||||
if (ret) {
|
||||
dev_err(ctxld->dev, "ctxld: irq request failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctxld->irq_en = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dcss_ctxld_hw_cfg(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
dcss_writel(RD_ERR_EN | SB_HP_COMP_EN |
|
||||
DB_PEND_SB_REC_EN | AHB_ERR_EN | RD_ERR | AHB_ERR,
|
||||
ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS);
|
||||
}
|
||||
|
||||
static void dcss_ctxld_free_ctx(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
struct dcss_ctxld_item *ctx;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (ctxld->db[i]) {
|
||||
dma_free_coherent(ctxld->dev,
|
||||
CTXLD_DB_CTX_ENTRIES * sizeof(*ctx),
|
||||
ctxld->db[i], ctxld->db_paddr[i]);
|
||||
ctxld->db[i] = NULL;
|
||||
ctxld->db_paddr[i] = 0;
|
||||
}
|
||||
|
||||
if (ctxld->sb_hp[i]) {
|
||||
dma_free_coherent(ctxld->dev,
|
||||
CTXLD_SB_CTX_ENTRIES * sizeof(*ctx),
|
||||
ctxld->sb_hp[i], ctxld->sb_paddr[i]);
|
||||
ctxld->sb_hp[i] = NULL;
|
||||
ctxld->sb_paddr[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int dcss_ctxld_alloc_ctx(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
struct dcss_ctxld_item *ctx;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
ctx = dma_alloc_coherent(ctxld->dev,
|
||||
CTXLD_DB_CTX_ENTRIES * sizeof(*ctx),
|
||||
&ctxld->db_paddr[i], GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctxld->db[i] = ctx;
|
||||
|
||||
ctx = dma_alloc_coherent(ctxld->dev,
|
||||
CTXLD_SB_CTX_ENTRIES * sizeof(*ctx),
|
||||
&ctxld->sb_paddr[i], GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctxld->sb_hp[i] = ctx;
|
||||
ctxld->sb_lp[i] = ctx + CTXLD_SB_HP_CTX_ENTRIES;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dcss_ctxld_init(struct dcss_dev *dcss, unsigned long ctxld_base)
|
||||
{
|
||||
struct dcss_ctxld *ctxld;
|
||||
int ret;
|
||||
|
||||
ctxld = kzalloc(sizeof(*ctxld), GFP_KERNEL);
|
||||
if (!ctxld)
|
||||
return -ENOMEM;
|
||||
|
||||
dcss->ctxld = ctxld;
|
||||
ctxld->dev = dcss->dev;
|
||||
|
||||
spin_lock_init(&ctxld->lock);
|
||||
|
||||
ret = dcss_ctxld_alloc_ctx(ctxld);
|
||||
if (ret) {
|
||||
dev_err(dcss->dev, "ctxld: cannot allocate context memory.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ctxld->ctxld_reg = ioremap(ctxld_base, SZ_4K);
|
||||
if (!ctxld->ctxld_reg) {
|
||||
dev_err(dcss->dev, "ctxld: unable to remap ctxld base\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = dcss_ctxld_irq_config(ctxld, to_platform_device(dcss->dev));
|
||||
if (ret)
|
||||
goto err_irq;
|
||||
|
||||
dcss_ctxld_hw_cfg(ctxld);
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
iounmap(ctxld->ctxld_reg);
|
||||
|
||||
err:
|
||||
dcss_ctxld_free_ctx(ctxld);
|
||||
kfree(ctxld);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dcss_ctxld_exit(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
free_irq(ctxld->irq, ctxld);
|
||||
|
||||
if (ctxld->ctxld_reg)
|
||||
iounmap(ctxld->ctxld_reg);
|
||||
|
||||
dcss_ctxld_free_ctx(ctxld);
|
||||
kfree(ctxld);
|
||||
}
|
||||
|
||||
static int dcss_ctxld_enable_locked(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
int curr_ctx = ctxld->current_ctx;
|
||||
u32 db_base, sb_base, sb_count;
|
||||
u32 sb_hp_cnt, sb_lp_cnt, db_cnt;
|
||||
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(ctxld->dev);
|
||||
|
||||
if (!dcss)
|
||||
return 0;
|
||||
|
||||
dcss_dpr_write_sysctrl(dcss->dpr);
|
||||
|
||||
dcss_scaler_write_sclctrl(dcss->scaler);
|
||||
|
||||
sb_hp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_HP];
|
||||
sb_lp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_LP];
|
||||
db_cnt = ctxld->ctx_size[curr_ctx][CTX_DB];
|
||||
|
||||
/* make sure SB_LP context area comes after SB_HP */
|
||||
if (sb_lp_cnt &&
|
||||
ctxld->sb_lp[curr_ctx] != ctxld->sb_hp[curr_ctx] + sb_hp_cnt) {
|
||||
struct dcss_ctxld_item *sb_lp_adjusted;
|
||||
|
||||
sb_lp_adjusted = ctxld->sb_hp[curr_ctx] + sb_hp_cnt;
|
||||
|
||||
memcpy(sb_lp_adjusted, ctxld->sb_lp[curr_ctx],
|
||||
sb_lp_cnt * CTX_ITEM_SIZE);
|
||||
}
|
||||
|
||||
db_base = db_cnt ? ctxld->db_paddr[curr_ctx] : 0;
|
||||
|
||||
dcss_writel(db_base, ctxld->ctxld_reg + DCSS_CTXLD_DB_BASE_ADDR);
|
||||
dcss_writel(db_cnt, ctxld->ctxld_reg + DCSS_CTXLD_DB_COUNT);
|
||||
|
||||
if (sb_hp_cnt)
|
||||
sb_count = ((sb_hp_cnt << SB_HP_COUNT_POS) & SB_HP_COUNT_MASK) |
|
||||
((sb_lp_cnt << SB_LP_COUNT_POS) & SB_LP_COUNT_MASK);
|
||||
else
|
||||
sb_count = (sb_lp_cnt << SB_HP_COUNT_POS) & SB_HP_COUNT_MASK;
|
||||
|
||||
sb_base = sb_count ? ctxld->sb_paddr[curr_ctx] : 0;
|
||||
|
||||
dcss_writel(sb_base, ctxld->ctxld_reg + DCSS_CTXLD_SB_BASE_ADDR);
|
||||
dcss_writel(sb_count, ctxld->ctxld_reg + DCSS_CTXLD_SB_COUNT);
|
||||
|
||||
/* enable the context loader */
|
||||
dcss_set(CTXLD_ENABLE, ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS);
|
||||
|
||||
ctxld->in_use = true;
|
||||
|
||||
/*
|
||||
* Toggle the current context to the alternate one so that any updates
|
||||
* in the modules' settings take place there.
|
||||
*/
|
||||
ctxld->current_ctx ^= 1;
|
||||
|
||||
ctxld->ctx_size[ctxld->current_ctx][CTX_DB] = 0;
|
||||
ctxld->ctx_size[ctxld->current_ctx][CTX_SB_HP] = 0;
|
||||
ctxld->ctx_size[ctxld->current_ctx][CTX_SB_LP] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dcss_ctxld_enable(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
spin_lock_irq(&ctxld->lock);
|
||||
ctxld->armed = true;
|
||||
spin_unlock_irq(&ctxld->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dcss_ctxld_kick(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ctxld->lock, flags);
|
||||
if (ctxld->armed && !ctxld->in_use) {
|
||||
ctxld->armed = false;
|
||||
dcss_ctxld_enable_locked(ctxld);
|
||||
}
|
||||
spin_unlock_irqrestore(&ctxld->lock, flags);
|
||||
}
|
||||
|
||||
void dcss_ctxld_write_irqsafe(struct dcss_ctxld *ctxld, u32 ctx_id, u32 val,
|
||||
u32 reg_ofs)
|
||||
{
|
||||
int curr_ctx = ctxld->current_ctx;
|
||||
struct dcss_ctxld_item *ctx[] = {
|
||||
[CTX_DB] = ctxld->db[curr_ctx],
|
||||
[CTX_SB_HP] = ctxld->sb_hp[curr_ctx],
|
||||
[CTX_SB_LP] = ctxld->sb_lp[curr_ctx]
|
||||
};
|
||||
int item_idx = ctxld->ctx_size[curr_ctx][ctx_id];
|
||||
|
||||
if (item_idx + 1 > dcss_ctxld_ctx_size[ctx_id]) {
|
||||
WARN_ON(1);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx[ctx_id][item_idx].val = val;
|
||||
ctx[ctx_id][item_idx].ofs = reg_ofs;
|
||||
ctxld->ctx_size[curr_ctx][ctx_id] += 1;
|
||||
}
|
||||
|
||||
void dcss_ctxld_write(struct dcss_ctxld *ctxld, u32 ctx_id,
|
||||
u32 val, u32 reg_ofs)
|
||||
{
|
||||
spin_lock_irq(&ctxld->lock);
|
||||
dcss_ctxld_write_irqsafe(ctxld, ctx_id, val, reg_ofs);
|
||||
spin_unlock_irq(&ctxld->lock);
|
||||
}
|
||||
|
||||
bool dcss_ctxld_is_flushed(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
return ctxld->ctx_size[ctxld->current_ctx][CTX_DB] == 0 &&
|
||||
ctxld->ctx_size[ctxld->current_ctx][CTX_SB_HP] == 0 &&
|
||||
ctxld->ctx_size[ctxld->current_ctx][CTX_SB_LP] == 0;
|
||||
}
|
||||
|
||||
int dcss_ctxld_resume(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
dcss_ctxld_hw_cfg(ctxld);
|
||||
|
||||
if (!ctxld->irq_en) {
|
||||
enable_irq(ctxld->irq);
|
||||
ctxld->irq_en = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dcss_ctxld_suspend(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(500);
|
||||
|
||||
if (!dcss_ctxld_is_flushed(ctxld)) {
|
||||
dcss_ctxld_kick(ctxld);
|
||||
|
||||
while (!time_after(jiffies, timeout) && ctxld->in_use)
|
||||
msleep(20);
|
||||
|
||||
if (time_after(jiffies, timeout))
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
spin_lock_irq(&ctxld->lock);
|
||||
|
||||
if (ctxld->irq_en) {
|
||||
disable_irq_nosync(ctxld->irq);
|
||||
ctxld->irq_en = false;
|
||||
}
|
||||
|
||||
/* reset context region and sizes */
|
||||
ctxld->current_ctx = 0;
|
||||
ctxld->ctx_size[0][CTX_DB] = 0;
|
||||
ctxld->ctx_size[0][CTX_SB_HP] = 0;
|
||||
ctxld->ctx_size[0][CTX_SB_LP] = 0;
|
||||
|
||||
spin_unlock_irq(&ctxld->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dcss_ctxld_assert_locked(struct dcss_ctxld *ctxld)
|
||||
{
|
||||
lockdep_assert_held(&ctxld->lock);
|
||||
}
|
||||
314
drivers/gpu/drm/imx/dcss/dcss-dev.c
Normal file
314
drivers/gpu/drm/imx/dcss/dcss-dev.c
Normal file
@@ -0,0 +1,314 @@
|
||||
// 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 */
|
||||
177
drivers/gpu/drm/imx/dcss/dcss-dev.h
Normal file
177
drivers/gpu/drm/imx/dcss/dcss-dev.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/* 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__ */
|
||||
562
drivers/gpu/drm/imx/dcss/dcss-dpr.c
Normal file
562
drivers/gpu/drm/imx/dcss/dcss-dpr.c
Normal file
File diff suppressed because it is too large
Load Diff
138
drivers/gpu/drm/imx/dcss/dcss-drv.c
Normal file
138
drivers/gpu/drm/imx/dcss/dcss-drv.c
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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");
|
||||
409
drivers/gpu/drm/imx/dcss/dcss-dtg.c
Normal file
409
drivers/gpu/drm/imx/dcss/dcss-dtg.c
Normal file
@@ -0,0 +1,409 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 NXP.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "dcss-dev.h"
|
||||
|
||||
#define DCSS_DTG_TC_CONTROL_STATUS 0x00
|
||||
#define CH3_EN BIT(0)
|
||||
#define CH2_EN BIT(1)
|
||||
#define CH1_EN BIT(2)
|
||||
#define OVL_DATA_MODE BIT(3)
|
||||
#define BLENDER_VIDEO_ALPHA_SEL BIT(7)
|
||||
#define DTG_START BIT(8)
|
||||
#define DBY_MODE_EN BIT(9)
|
||||
#define CH1_ALPHA_SEL BIT(10)
|
||||
#define CSS_PIX_COMP_SWAP_POS 12
|
||||
#define CSS_PIX_COMP_SWAP_MASK GENMASK(14, 12)
|
||||
#define DEFAULT_FG_ALPHA_POS 24
|
||||
#define DEFAULT_FG_ALPHA_MASK GENMASK(31, 24)
|
||||
#define DCSS_DTG_TC_DTG 0x04
|
||||
#define DCSS_DTG_TC_DISP_TOP 0x08
|
||||
#define DCSS_DTG_TC_DISP_BOT 0x0C
|
||||
#define DCSS_DTG_TC_CH1_TOP 0x10
|
||||
#define DCSS_DTG_TC_CH1_BOT 0x14
|
||||
#define DCSS_DTG_TC_CH2_TOP 0x18
|
||||
#define DCSS_DTG_TC_CH2_BOT 0x1C
|
||||
#define DCSS_DTG_TC_CH3_TOP 0x20
|
||||
#define DCSS_DTG_TC_CH3_BOT 0x24
|
||||
#define TC_X_POS 0
|
||||
#define TC_X_MASK GENMASK(12, 0)
|
||||
#define TC_Y_POS 16
|
||||
#define TC_Y_MASK GENMASK(28, 16)
|
||||
#define DCSS_DTG_TC_CTXLD 0x28
|
||||
#define TC_CTXLD_DB_Y_POS 0
|
||||
#define TC_CTXLD_DB_Y_MASK GENMASK(12, 0)
|
||||
#define TC_CTXLD_SB_Y_POS 16
|
||||
#define TC_CTXLD_SB_Y_MASK GENMASK(28, 16)
|
||||
#define DCSS_DTG_TC_CH1_BKRND 0x2C
|
||||
#define DCSS_DTG_TC_CH2_BKRND 0x30
|
||||
#define BKRND_R_Y_COMP_POS 20
|
||||
#define BKRND_R_Y_COMP_MASK GENMASK(29, 20)
|
||||
#define BKRND_G_U_COMP_POS 10
|
||||
#define BKRND_G_U_COMP_MASK GENMASK(19, 10)
|
||||
#define BKRND_B_V_COMP_POS 0
|
||||
#define BKRND_B_V_COMP_MASK GENMASK(9, 0)
|
||||
#define DCSS_DTG_BLENDER_DBY_RANGEINV 0x38
|
||||
#define DCSS_DTG_BLENDER_DBY_RANGEMIN 0x3C
|
||||
#define DCSS_DTG_BLENDER_DBY_BDP 0x40
|
||||
#define DCSS_DTG_BLENDER_BKRND_I 0x44
|
||||
#define DCSS_DTG_BLENDER_BKRND_P 0x48
|
||||
#define DCSS_DTG_BLENDER_BKRND_T 0x4C
|
||||
#define DCSS_DTG_LINE0_INT 0x50
|
||||
#define DCSS_DTG_LINE1_INT 0x54
|
||||
#define DCSS_DTG_BG_ALPHA_DEFAULT 0x58
|
||||
#define DCSS_DTG_INT_STATUS 0x5C
|
||||
#define DCSS_DTG_INT_CONTROL 0x60
|
||||
#define DCSS_DTG_TC_CH3_BKRND 0x64
|
||||
#define DCSS_DTG_INT_MASK 0x68
|
||||
#define LINE0_IRQ BIT(0)
|
||||
#define LINE1_IRQ BIT(1)
|
||||
#define LINE2_IRQ BIT(2)
|
||||
#define LINE3_IRQ BIT(3)
|
||||
#define DCSS_DTG_LINE2_INT 0x6C
|
||||
#define DCSS_DTG_LINE3_INT 0x70
|
||||
#define DCSS_DTG_DBY_OL 0x74
|
||||
#define DCSS_DTG_DBY_BL 0x78
|
||||
#define DCSS_DTG_DBY_EL 0x7C
|
||||
|
||||
struct dcss_dtg {
|
||||
struct device *dev;
|
||||
struct dcss_ctxld *ctxld;
|
||||
void __iomem *base_reg;
|
||||
u32 base_ofs;
|
||||
|
||||
u32 ctx_id;
|
||||
|
||||
bool in_use;
|
||||
|
||||
u32 dis_ulc_x;
|
||||
u32 dis_ulc_y;
|
||||
|
||||
u32 control_status;
|
||||
u32 alpha;
|
||||
u32 alpha_cfg;
|
||||
|
||||
int ctxld_kick_irq;
|
||||
bool ctxld_kick_irq_en;
|
||||
};
|
||||
|
||||
static void dcss_dtg_write(struct dcss_dtg *dtg, u32 val, u32 ofs)
|
||||
{
|
||||
if (!dtg->in_use)
|
||||
dcss_writel(val, dtg->base_reg + ofs);
|
||||
|
||||
dcss_ctxld_write(dtg->ctxld, dtg->ctx_id,
|
||||
val, dtg->base_ofs + ofs);
|
||||
}
|
||||
|
||||
static irqreturn_t dcss_dtg_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct dcss_dtg *dtg = data;
|
||||
u32 status;
|
||||
|
||||
status = dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS);
|
||||
|
||||
if (!(status & LINE0_IRQ))
|
||||
return IRQ_NONE;
|
||||
|
||||
dcss_ctxld_kick(dtg->ctxld);
|
||||
|
||||
dcss_writel(status & LINE0_IRQ, dtg->base_reg + DCSS_DTG_INT_CONTROL);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int dcss_dtg_irq_config(struct dcss_dtg *dtg,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dtg->ctxld_kick_irq = platform_get_irq_byname(pdev, "ctxld_kick");
|
||||
if (dtg->ctxld_kick_irq < 0)
|
||||
return dtg->ctxld_kick_irq;
|
||||
|
||||
dcss_update(0, LINE0_IRQ | LINE1_IRQ,
|
||||
dtg->base_reg + DCSS_DTG_INT_MASK);
|
||||
|
||||
ret = request_irq(dtg->ctxld_kick_irq, dcss_dtg_irq_handler,
|
||||
0, "dcss_ctxld_kick", dtg);
|
||||
if (ret) {
|
||||
dev_err(dtg->dev, "dtg: irq request failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
disable_irq(dtg->ctxld_kick_irq);
|
||||
|
||||
dtg->ctxld_kick_irq_en = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dcss_dtg_init(struct dcss_dev *dcss, unsigned long dtg_base)
|
||||
{
|
||||
int ret = 0;
|
||||
struct dcss_dtg *dtg;
|
||||
|
||||
dtg = kzalloc(sizeof(*dtg), GFP_KERNEL);
|
||||
if (!dtg)
|
||||
return -ENOMEM;
|
||||
|
||||
dcss->dtg = dtg;
|
||||
dtg->dev = dcss->dev;
|
||||
dtg->ctxld = dcss->ctxld;
|
||||
|
||||
dtg->base_reg = ioremap(dtg_base, SZ_4K);
|
||||
if (!dtg->base_reg) {
|
||||
dev_err(dcss->dev, "dtg: unable to remap dtg base\n");
|
||||
ret = -ENOMEM;
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
dtg->base_ofs = dtg_base;
|
||||
dtg->ctx_id = CTX_DB;
|
||||
|
||||
dtg->alpha = 255;
|
||||
|
||||
dtg->control_status |= OVL_DATA_MODE | BLENDER_VIDEO_ALPHA_SEL |
|
||||
((dtg->alpha << DEFAULT_FG_ALPHA_POS) & DEFAULT_FG_ALPHA_MASK);
|
||||
|
||||
ret = dcss_dtg_irq_config(dtg, to_platform_device(dcss->dev));
|
||||
if (ret)
|
||||
goto err_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
iounmap(dtg->base_reg);
|
||||
|
||||
err_ioremap:
|
||||
kfree(dtg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dcss_dtg_exit(struct dcss_dtg *dtg)
|
||||
{
|
||||
free_irq(dtg->ctxld_kick_irq, dtg);
|
||||
|
||||
if (dtg->base_reg)
|
||||
iounmap(dtg->base_reg);
|
||||
|
||||
kfree(dtg);
|
||||
}
|
||||
|
||||
void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm)
|
||||
{
|
||||
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dtg->dev);
|
||||
u16 dtg_lrc_x, dtg_lrc_y;
|
||||
u16 dis_ulc_x, dis_ulc_y;
|
||||
u16 dis_lrc_x, dis_lrc_y;
|
||||
u32 sb_ctxld_trig, db_ctxld_trig;
|
||||
u32 pixclock = vm->pixelclock;
|
||||
u32 actual_clk;
|
||||
|
||||
dtg_lrc_x = vm->hfront_porch + vm->hback_porch + vm->hsync_len +
|
||||
vm->hactive - 1;
|
||||
dtg_lrc_y = vm->vfront_porch + vm->vback_porch + vm->vsync_len +
|
||||
vm->vactive - 1;
|
||||
dis_ulc_x = vm->hsync_len + vm->hback_porch - 1;
|
||||
dis_ulc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch - 1;
|
||||
dis_lrc_x = vm->hsync_len + vm->hback_porch + vm->hactive - 1;
|
||||
dis_lrc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch +
|
||||
vm->vactive - 1;
|
||||
|
||||
clk_disable_unprepare(dcss->pix_clk);
|
||||
clk_set_rate(dcss->pix_clk, vm->pixelclock);
|
||||
clk_prepare_enable(dcss->pix_clk);
|
||||
|
||||
actual_clk = clk_get_rate(dcss->pix_clk);
|
||||
if (pixclock != actual_clk) {
|
||||
dev_info(dtg->dev,
|
||||
"Pixel clock set to %u kHz instead of %u kHz.\n",
|
||||
(actual_clk / 1000), (pixclock / 1000));
|
||||
}
|
||||
|
||||
dcss_dtg_write(dtg, ((dtg_lrc_y << TC_Y_POS) | dtg_lrc_x),
|
||||
DCSS_DTG_TC_DTG);
|
||||
dcss_dtg_write(dtg, ((dis_ulc_y << TC_Y_POS) | dis_ulc_x),
|
||||
DCSS_DTG_TC_DISP_TOP);
|
||||
dcss_dtg_write(dtg, ((dis_lrc_y << TC_Y_POS) | dis_lrc_x),
|
||||
DCSS_DTG_TC_DISP_BOT);
|
||||
|
||||
dtg->dis_ulc_x = dis_ulc_x;
|
||||
dtg->dis_ulc_y = dis_ulc_y;
|
||||
|
||||
sb_ctxld_trig = ((0 * dis_lrc_y / 100) << TC_CTXLD_SB_Y_POS) &
|
||||
TC_CTXLD_SB_Y_MASK;
|
||||
db_ctxld_trig = ((99 * dis_lrc_y / 100) << TC_CTXLD_DB_Y_POS) &
|
||||
TC_CTXLD_DB_Y_MASK;
|
||||
|
||||
dcss_dtg_write(dtg, sb_ctxld_trig | db_ctxld_trig, DCSS_DTG_TC_CTXLD);
|
||||
|
||||
/* vblank trigger */
|
||||
dcss_dtg_write(dtg, 0, DCSS_DTG_LINE1_INT);
|
||||
|
||||
/* CTXLD trigger */
|
||||
dcss_dtg_write(dtg, ((90 * dis_lrc_y) / 100) << 16, DCSS_DTG_LINE0_INT);
|
||||
}
|
||||
|
||||
void dcss_dtg_plane_pos_set(struct dcss_dtg *dtg, int ch_num,
|
||||
int px, int py, int pw, int ph)
|
||||
{
|
||||
u16 p_ulc_x, p_ulc_y;
|
||||
u16 p_lrc_x, p_lrc_y;
|
||||
|
||||
p_ulc_x = dtg->dis_ulc_x + px;
|
||||
p_ulc_y = dtg->dis_ulc_y + py;
|
||||
p_lrc_x = p_ulc_x + pw;
|
||||
p_lrc_y = p_ulc_y + ph;
|
||||
|
||||
if (!px && !py && !pw && !ph) {
|
||||
dcss_dtg_write(dtg, 0, DCSS_DTG_TC_CH1_TOP + 0x8 * ch_num);
|
||||
dcss_dtg_write(dtg, 0, DCSS_DTG_TC_CH1_BOT + 0x8 * ch_num);
|
||||
} else {
|
||||
dcss_dtg_write(dtg, ((p_ulc_y << TC_Y_POS) | p_ulc_x),
|
||||
DCSS_DTG_TC_CH1_TOP + 0x8 * ch_num);
|
||||
dcss_dtg_write(dtg, ((p_lrc_y << TC_Y_POS) | p_lrc_x),
|
||||
DCSS_DTG_TC_CH1_BOT + 0x8 * ch_num);
|
||||
}
|
||||
}
|
||||
|
||||
bool dcss_dtg_global_alpha_changed(struct dcss_dtg *dtg, int ch_num, int alpha)
|
||||
{
|
||||
if (ch_num)
|
||||
return false;
|
||||
|
||||
return alpha != dtg->alpha;
|
||||
}
|
||||
|
||||
void dcss_dtg_plane_alpha_set(struct dcss_dtg *dtg, int ch_num,
|
||||
const struct drm_format_info *format, int alpha)
|
||||
{
|
||||
/* we care about alpha only when channel 0 is concerned */
|
||||
if (ch_num)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Use global alpha if pixel format does not have alpha channel or the
|
||||
* user explicitly chose to use global alpha (i.e. alpha is not OPAQUE).
|
||||
*/
|
||||
if (!format->has_alpha || alpha != 255)
|
||||
dtg->alpha_cfg = (alpha << DEFAULT_FG_ALPHA_POS) & DEFAULT_FG_ALPHA_MASK;
|
||||
else /* use per-pixel alpha otherwise */
|
||||
dtg->alpha_cfg = CH1_ALPHA_SEL;
|
||||
|
||||
dtg->alpha = alpha;
|
||||
}
|
||||
|
||||
void dcss_dtg_css_set(struct dcss_dtg *dtg)
|
||||
{
|
||||
dtg->control_status |=
|
||||
(0x5 << CSS_PIX_COMP_SWAP_POS) & CSS_PIX_COMP_SWAP_MASK;
|
||||
}
|
||||
|
||||
void dcss_dtg_enable(struct dcss_dtg *dtg)
|
||||
{
|
||||
dtg->control_status |= DTG_START;
|
||||
|
||||
dtg->control_status &= ~(CH1_ALPHA_SEL | DEFAULT_FG_ALPHA_MASK);
|
||||
dtg->control_status |= dtg->alpha_cfg;
|
||||
|
||||
dcss_dtg_write(dtg, dtg->control_status, DCSS_DTG_TC_CONTROL_STATUS);
|
||||
|
||||
dtg->in_use = true;
|
||||
}
|
||||
|
||||
void dcss_dtg_shutoff(struct dcss_dtg *dtg)
|
||||
{
|
||||
dtg->control_status &= ~DTG_START;
|
||||
|
||||
dcss_writel(dtg->control_status,
|
||||
dtg->base_reg + DCSS_DTG_TC_CONTROL_STATUS);
|
||||
|
||||
dtg->in_use = false;
|
||||
}
|
||||
|
||||
bool dcss_dtg_is_enabled(struct dcss_dtg *dtg)
|
||||
{
|
||||
return dtg->in_use;
|
||||
}
|
||||
|
||||
void dcss_dtg_ch_enable(struct dcss_dtg *dtg, int ch_num, bool en)
|
||||
{
|
||||
u32 ch_en_map[] = {CH1_EN, CH2_EN, CH3_EN};
|
||||
u32 control_status;
|
||||
|
||||
control_status = dtg->control_status & ~ch_en_map[ch_num];
|
||||
control_status |= en ? ch_en_map[ch_num] : 0;
|
||||
|
||||
control_status &= ~(CH1_ALPHA_SEL | DEFAULT_FG_ALPHA_MASK);
|
||||
control_status |= dtg->alpha_cfg;
|
||||
|
||||
if (dtg->control_status != control_status)
|
||||
dcss_dtg_write(dtg, control_status, DCSS_DTG_TC_CONTROL_STATUS);
|
||||
|
||||
dtg->control_status = control_status;
|
||||
}
|
||||
|
||||
void dcss_dtg_vblank_irq_enable(struct dcss_dtg *dtg, bool en)
|
||||
{
|
||||
u32 status;
|
||||
u32 mask = en ? LINE1_IRQ : 0;
|
||||
|
||||
if (en) {
|
||||
status = dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS);
|
||||
dcss_writel(status & LINE1_IRQ,
|
||||
dtg->base_reg + DCSS_DTG_INT_CONTROL);
|
||||
}
|
||||
|
||||
dcss_update(mask, LINE1_IRQ, dtg->base_reg + DCSS_DTG_INT_MASK);
|
||||
}
|
||||
|
||||
void dcss_dtg_ctxld_kick_irq_enable(struct dcss_dtg *dtg, bool en)
|
||||
{
|
||||
u32 status;
|
||||
u32 mask = en ? LINE0_IRQ : 0;
|
||||
|
||||
if (en) {
|
||||
status = dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS);
|
||||
|
||||
if (!dtg->ctxld_kick_irq_en) {
|
||||
dcss_writel(status & LINE0_IRQ,
|
||||
dtg->base_reg + DCSS_DTG_INT_CONTROL);
|
||||
enable_irq(dtg->ctxld_kick_irq);
|
||||
dtg->ctxld_kick_irq_en = true;
|
||||
dcss_update(mask, LINE0_IRQ,
|
||||
dtg->base_reg + DCSS_DTG_INT_MASK);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dtg->ctxld_kick_irq_en)
|
||||
return;
|
||||
|
||||
disable_irq_nosync(dtg->ctxld_kick_irq);
|
||||
dtg->ctxld_kick_irq_en = false;
|
||||
|
||||
dcss_update(mask, LINE0_IRQ, dtg->base_reg + DCSS_DTG_INT_MASK);
|
||||
}
|
||||
|
||||
void dcss_dtg_vblank_irq_clear(struct dcss_dtg *dtg)
|
||||
{
|
||||
dcss_update(LINE1_IRQ, LINE1_IRQ, dtg->base_reg + DCSS_DTG_INT_CONTROL);
|
||||
}
|
||||
|
||||
bool dcss_dtg_vblank_irq_valid(struct dcss_dtg *dtg)
|
||||
{
|
||||
return !!(dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS) & LINE1_IRQ);
|
||||
}
|
||||
|
||||
177
drivers/gpu/drm/imx/dcss/dcss-kms.c
Normal file
177
drivers/gpu/drm/imx/dcss/dcss-kms.c
Normal file
@@ -0,0 +1,177 @@
|
||||
// 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;
|
||||
}
|
||||
43
drivers/gpu/drm/imx/dcss/dcss-kms.h
Normal file
43
drivers/gpu/drm/imx/dcss/dcss-kms.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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
|
||||
405
drivers/gpu/drm/imx/dcss/dcss-plane.c
Normal file
405
drivers/gpu/drm/imx/dcss/dcss-plane.c
Normal file
@@ -0,0 +1,405 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 NXP.
|
||||
*/
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
|
||||
#include "dcss-dev.h"
|
||||
#include "dcss-kms.h"
|
||||
|
||||
static const u32 dcss_common_formats[] = {
|
||||
/* RGB */
|
||||
DRM_FORMAT_ARGB8888,
|
||||
DRM_FORMAT_XRGB8888,
|
||||
DRM_FORMAT_ABGR8888,
|
||||
DRM_FORMAT_XBGR8888,
|
||||
DRM_FORMAT_RGBA8888,
|
||||
DRM_FORMAT_RGBX8888,
|
||||
DRM_FORMAT_BGRA8888,
|
||||
DRM_FORMAT_BGRX8888,
|
||||
DRM_FORMAT_XRGB2101010,
|
||||
DRM_FORMAT_XBGR2101010,
|
||||
DRM_FORMAT_RGBX1010102,
|
||||
DRM_FORMAT_BGRX1010102,
|
||||
DRM_FORMAT_ARGB2101010,
|
||||
DRM_FORMAT_ABGR2101010,
|
||||
DRM_FORMAT_RGBA1010102,
|
||||
DRM_FORMAT_BGRA1010102,
|
||||
};
|
||||
|
||||
static const u64 dcss_video_format_modifiers[] = {
|
||||
DRM_FORMAT_MOD_LINEAR,
|
||||
DRM_FORMAT_MOD_INVALID,
|
||||
};
|
||||
|
||||
static const u64 dcss_graphics_format_modifiers[] = {
|
||||
DRM_FORMAT_MOD_VIVANTE_TILED,
|
||||
DRM_FORMAT_MOD_VIVANTE_SUPER_TILED,
|
||||
DRM_FORMAT_MOD_LINEAR,
|
||||
DRM_FORMAT_MOD_INVALID,
|
||||
};
|
||||
|
||||
static inline struct dcss_plane *to_dcss_plane(struct drm_plane *p)
|
||||
{
|
||||
return container_of(p, struct dcss_plane, base);
|
||||
}
|
||||
|
||||
static inline bool dcss_plane_fb_is_linear(const struct drm_framebuffer *fb)
|
||||
{
|
||||
return ((fb->flags & DRM_MODE_FB_MODIFIERS) == 0) ||
|
||||
((fb->flags & DRM_MODE_FB_MODIFIERS) != 0 &&
|
||||
fb->modifier == DRM_FORMAT_MOD_LINEAR);
|
||||
}
|
||||
|
||||
static void dcss_plane_destroy(struct drm_plane *plane)
|
||||
{
|
||||
struct dcss_plane *dcss_plane = container_of(plane, struct dcss_plane,
|
||||
base);
|
||||
|
||||
drm_plane_cleanup(plane);
|
||||
kfree(dcss_plane);
|
||||
}
|
||||
|
||||
static bool dcss_plane_format_mod_supported(struct drm_plane *plane,
|
||||
u32 format,
|
||||
u64 modifier)
|
||||
{
|
||||
switch (plane->type) {
|
||||
case DRM_PLANE_TYPE_PRIMARY:
|
||||
switch (format) {
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_ARGB2101010:
|
||||
return modifier == DRM_FORMAT_MOD_LINEAR ||
|
||||
modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
|
||||
modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED;
|
||||
default:
|
||||
return modifier == DRM_FORMAT_MOD_LINEAR;
|
||||
}
|
||||
break;
|
||||
case DRM_PLANE_TYPE_OVERLAY:
|
||||
return modifier == DRM_FORMAT_MOD_LINEAR;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct drm_plane_funcs dcss_plane_funcs = {
|
||||
.update_plane = drm_atomic_helper_update_plane,
|
||||
.disable_plane = drm_atomic_helper_disable_plane,
|
||||
.destroy = dcss_plane_destroy,
|
||||
.reset = drm_atomic_helper_plane_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
|
||||
.format_mod_supported = dcss_plane_format_mod_supported,
|
||||
};
|
||||
|
||||
static bool dcss_plane_can_rotate(const struct drm_format_info *format,
|
||||
bool mod_present, u64 modifier,
|
||||
unsigned int rotation)
|
||||
{
|
||||
bool linear_format = !mod_present ||
|
||||
(mod_present && modifier == DRM_FORMAT_MOD_LINEAR);
|
||||
u32 supported_rotation = DRM_MODE_ROTATE_0;
|
||||
|
||||
if (!format->is_yuv && linear_format)
|
||||
supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 |
|
||||
DRM_MODE_REFLECT_MASK;
|
||||
else if (!format->is_yuv &&
|
||||
modifier == DRM_FORMAT_MOD_VIVANTE_TILED)
|
||||
supported_rotation = DRM_MODE_ROTATE_MASK |
|
||||
DRM_MODE_REFLECT_MASK;
|
||||
else if (format->is_yuv && linear_format &&
|
||||
(format->format == DRM_FORMAT_NV12 ||
|
||||
format->format == DRM_FORMAT_NV21))
|
||||
supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 |
|
||||
DRM_MODE_REFLECT_MASK;
|
||||
|
||||
return !!(rotation & supported_rotation);
|
||||
}
|
||||
|
||||
static bool dcss_plane_is_source_size_allowed(u16 src_w, u16 src_h, u32 pix_fmt)
|
||||
{
|
||||
if (src_w < 64 &&
|
||||
(pix_fmt == DRM_FORMAT_NV12 || pix_fmt == DRM_FORMAT_NV21))
|
||||
return false;
|
||||
else if (src_w < 32 &&
|
||||
(pix_fmt == DRM_FORMAT_UYVY || pix_fmt == DRM_FORMAT_VYUY ||
|
||||
pix_fmt == DRM_FORMAT_YUYV || pix_fmt == DRM_FORMAT_YVYU))
|
||||
return false;
|
||||
|
||||
return src_w >= 16 && src_h >= 8;
|
||||
}
|
||||
|
||||
static int dcss_plane_atomic_check(struct drm_plane *plane,
|
||||
struct drm_plane_state *state)
|
||||
{
|
||||
struct dcss_plane *dcss_plane = to_dcss_plane(plane);
|
||||
struct dcss_dev *dcss = plane->dev->dev_private;
|
||||
struct drm_framebuffer *fb = state->fb;
|
||||
bool is_primary_plane = plane->type == DRM_PLANE_TYPE_PRIMARY;
|
||||
struct drm_gem_cma_object *cma_obj;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
int hdisplay, vdisplay;
|
||||
int min, max;
|
||||
int ret;
|
||||
|
||||
if (!fb || !state->crtc)
|
||||
return 0;
|
||||
|
||||
cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
|
||||
WARN_ON(!cma_obj);
|
||||
|
||||
crtc_state = drm_atomic_get_existing_crtc_state(state->state,
|
||||
state->crtc);
|
||||
|
||||
hdisplay = crtc_state->adjusted_mode.hdisplay;
|
||||
vdisplay = crtc_state->adjusted_mode.vdisplay;
|
||||
|
||||
if (!dcss_plane_is_source_size_allowed(state->src_w >> 16,
|
||||
state->src_h >> 16,
|
||||
fb->format->format)) {
|
||||
DRM_DEBUG_KMS("Source plane size is not allowed!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dcss_scaler_get_min_max_ratios(dcss->scaler, dcss_plane->ch_num,
|
||||
&min, &max);
|
||||
|
||||
ret = drm_atomic_helper_check_plane_state(state, crtc_state,
|
||||
min, max, !is_primary_plane,
|
||||
false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!state->visible)
|
||||
return 0;
|
||||
|
||||
if (!dcss_plane_can_rotate(fb->format,
|
||||
!!(fb->flags & DRM_MODE_FB_MODIFIERS),
|
||||
fb->modifier,
|
||||
state->rotation)) {
|
||||
DRM_DEBUG_KMS("requested rotation is not allowed!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((state->crtc_x < 0 || state->crtc_y < 0 ||
|
||||
state->crtc_x + state->crtc_w > hdisplay ||
|
||||
state->crtc_y + state->crtc_h > vdisplay) &&
|
||||
!dcss_plane_fb_is_linear(fb)) {
|
||||
DRM_DEBUG_KMS("requested cropping operation is not allowed!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((fb->flags & DRM_MODE_FB_MODIFIERS) &&
|
||||
!plane->funcs->format_mod_supported(plane,
|
||||
fb->format->format,
|
||||
fb->modifier)) {
|
||||
DRM_DEBUG_KMS("Invalid modifier: %llx", fb->modifier);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dcss_plane_atomic_set_base(struct dcss_plane *dcss_plane)
|
||||
{
|
||||
struct drm_plane *plane = &dcss_plane->base;
|
||||
struct drm_plane_state *state = plane->state;
|
||||
struct dcss_dev *dcss = plane->dev->dev_private;
|
||||
struct drm_framebuffer *fb = state->fb;
|
||||
const struct drm_format_info *format = fb->format;
|
||||
struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
|
||||
unsigned long p1_ba = 0, p2_ba = 0;
|
||||
|
||||
if (!format->is_yuv ||
|
||||
format->format == DRM_FORMAT_NV12 ||
|
||||
format->format == DRM_FORMAT_NV21)
|
||||
p1_ba = cma_obj->paddr + fb->offsets[0] +
|
||||
fb->pitches[0] * (state->src.y1 >> 16) +
|
||||
format->char_per_block[0] * (state->src.x1 >> 16);
|
||||
else if (format->format == DRM_FORMAT_UYVY ||
|
||||
format->format == DRM_FORMAT_VYUY ||
|
||||
format->format == DRM_FORMAT_YUYV ||
|
||||
format->format == DRM_FORMAT_YVYU)
|
||||
p1_ba = cma_obj->paddr + fb->offsets[0] +
|
||||
fb->pitches[0] * (state->src.y1 >> 16) +
|
||||
2 * format->char_per_block[0] * (state->src.x1 >> 17);
|
||||
|
||||
if (format->format == DRM_FORMAT_NV12 ||
|
||||
format->format == DRM_FORMAT_NV21)
|
||||
p2_ba = cma_obj->paddr + fb->offsets[1] +
|
||||
(((fb->pitches[1] >> 1) * (state->src.y1 >> 17) +
|
||||
(state->src.x1 >> 17)) << 1);
|
||||
|
||||
dcss_dpr_addr_set(dcss->dpr, dcss_plane->ch_num, p1_ba, p2_ba,
|
||||
fb->pitches[0]);
|
||||
}
|
||||
|
||||
static bool dcss_plane_needs_setup(struct drm_plane_state *state,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
struct drm_framebuffer *fb = state->fb;
|
||||
struct drm_framebuffer *old_fb = old_state->fb;
|
||||
|
||||
return state->crtc_x != old_state->crtc_x ||
|
||||
state->crtc_y != old_state->crtc_y ||
|
||||
state->crtc_w != old_state->crtc_w ||
|
||||
state->crtc_h != old_state->crtc_h ||
|
||||
state->src_x != old_state->src_x ||
|
||||
state->src_y != old_state->src_y ||
|
||||
state->src_w != old_state->src_w ||
|
||||
state->src_h != old_state->src_h ||
|
||||
fb->format->format != old_fb->format->format ||
|
||||
fb->modifier != old_fb->modifier ||
|
||||
state->rotation != old_state->rotation;
|
||||
}
|
||||
|
||||
static void dcss_plane_atomic_update(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
struct drm_plane_state *state = plane->state;
|
||||
struct dcss_plane *dcss_plane = to_dcss_plane(plane);
|
||||
struct dcss_dev *dcss = plane->dev->dev_private;
|
||||
struct drm_framebuffer *fb = state->fb;
|
||||
u32 pixel_format;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
bool modifiers_present;
|
||||
u32 src_w, src_h, dst_w, dst_h;
|
||||
struct drm_rect src, dst;
|
||||
bool enable = true;
|
||||
|
||||
if (!fb || !state->crtc || !state->visible)
|
||||
return;
|
||||
|
||||
pixel_format = state->fb->format->format;
|
||||
crtc_state = state->crtc->state;
|
||||
modifiers_present = !!(fb->flags & DRM_MODE_FB_MODIFIERS);
|
||||
|
||||
if (old_state->fb && !drm_atomic_crtc_needs_modeset(crtc_state) &&
|
||||
!dcss_plane_needs_setup(state, old_state)) {
|
||||
dcss_plane_atomic_set_base(dcss_plane);
|
||||
return;
|
||||
}
|
||||
|
||||
src = plane->state->src;
|
||||
dst = plane->state->dst;
|
||||
|
||||
/*
|
||||
* The width and height after clipping.
|
||||
*/
|
||||
src_w = drm_rect_width(&src) >> 16;
|
||||
src_h = drm_rect_height(&src) >> 16;
|
||||
dst_w = drm_rect_width(&dst);
|
||||
dst_h = drm_rect_height(&dst);
|
||||
|
||||
if (plane->type == DRM_PLANE_TYPE_OVERLAY &&
|
||||
modifiers_present && fb->modifier == DRM_FORMAT_MOD_LINEAR)
|
||||
modifiers_present = false;
|
||||
|
||||
dcss_dpr_format_set(dcss->dpr, dcss_plane->ch_num, state->fb->format,
|
||||
modifiers_present ? fb->modifier :
|
||||
DRM_FORMAT_MOD_LINEAR);
|
||||
dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, src_w, src_h);
|
||||
dcss_dpr_set_rotation(dcss->dpr, dcss_plane->ch_num,
|
||||
state->rotation);
|
||||
|
||||
dcss_plane_atomic_set_base(dcss_plane);
|
||||
|
||||
dcss_scaler_setup(dcss->scaler, dcss_plane->ch_num,
|
||||
state->fb->format, src_w, src_h,
|
||||
dst_w, dst_h,
|
||||
drm_mode_vrefresh(&crtc_state->mode));
|
||||
|
||||
dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num,
|
||||
dst.x1, dst.y1, dst_w, dst_h);
|
||||
dcss_dtg_plane_alpha_set(dcss->dtg, dcss_plane->ch_num,
|
||||
fb->format, state->alpha >> 8);
|
||||
|
||||
if (!dcss_plane->ch_num && (state->alpha >> 8) == 0)
|
||||
enable = false;
|
||||
|
||||
dcss_dpr_enable(dcss->dpr, dcss_plane->ch_num, enable);
|
||||
dcss_scaler_ch_enable(dcss->scaler, dcss_plane->ch_num, enable);
|
||||
|
||||
if (!enable)
|
||||
dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num,
|
||||
0, 0, 0, 0);
|
||||
|
||||
dcss_dtg_ch_enable(dcss->dtg, dcss_plane->ch_num, enable);
|
||||
}
|
||||
|
||||
static void dcss_plane_atomic_disable(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
struct dcss_plane *dcss_plane = to_dcss_plane(plane);
|
||||
struct dcss_dev *dcss = plane->dev->dev_private;
|
||||
|
||||
dcss_dpr_enable(dcss->dpr, dcss_plane->ch_num, false);
|
||||
dcss_scaler_ch_enable(dcss->scaler, dcss_plane->ch_num, false);
|
||||
dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num, 0, 0, 0, 0);
|
||||
dcss_dtg_ch_enable(dcss->dtg, dcss_plane->ch_num, false);
|
||||
}
|
||||
|
||||
static const struct drm_plane_helper_funcs dcss_plane_helper_funcs = {
|
||||
.prepare_fb = drm_gem_fb_prepare_fb,
|
||||
.atomic_check = dcss_plane_atomic_check,
|
||||
.atomic_update = dcss_plane_atomic_update,
|
||||
.atomic_disable = dcss_plane_atomic_disable,
|
||||
};
|
||||
|
||||
struct dcss_plane *dcss_plane_init(struct drm_device *drm,
|
||||
unsigned int possible_crtcs,
|
||||
enum drm_plane_type type,
|
||||
unsigned int zpos)
|
||||
{
|
||||
struct dcss_plane *dcss_plane;
|
||||
const u64 *format_modifiers = dcss_video_format_modifiers;
|
||||
int ret;
|
||||
|
||||
if (zpos > 2)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
dcss_plane = kzalloc(sizeof(*dcss_plane), GFP_KERNEL);
|
||||
if (!dcss_plane) {
|
||||
DRM_ERROR("failed to allocate plane\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
if (type == DRM_PLANE_TYPE_PRIMARY)
|
||||
format_modifiers = dcss_graphics_format_modifiers;
|
||||
|
||||
ret = drm_universal_plane_init(drm, &dcss_plane->base, possible_crtcs,
|
||||
&dcss_plane_funcs, dcss_common_formats,
|
||||
ARRAY_SIZE(dcss_common_formats),
|
||||
format_modifiers, type, NULL);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to initialize plane\n");
|
||||
kfree(dcss_plane);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
drm_plane_helper_add(&dcss_plane->base, &dcss_plane_helper_funcs);
|
||||
|
||||
ret = drm_plane_create_zpos_immutable_property(&dcss_plane->base, zpos);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
drm_plane_create_rotation_property(&dcss_plane->base,
|
||||
DRM_MODE_ROTATE_0,
|
||||
DRM_MODE_ROTATE_0 |
|
||||
DRM_MODE_ROTATE_90 |
|
||||
DRM_MODE_ROTATE_180 |
|
||||
DRM_MODE_ROTATE_270 |
|
||||
DRM_MODE_REFLECT_X |
|
||||
DRM_MODE_REFLECT_Y);
|
||||
|
||||
dcss_plane->ch_num = zpos;
|
||||
|
||||
return dcss_plane;
|
||||
}
|
||||
826
drivers/gpu/drm/imx/dcss/dcss-scaler.c
Normal file
826
drivers/gpu/drm/imx/dcss/dcss-scaler.c
Normal file
File diff suppressed because it is too large
Load Diff
180
drivers/gpu/drm/imx/dcss/dcss-ss.c
Normal file
180
drivers/gpu/drm/imx/dcss/dcss-ss.c
Normal file
@@ -0,0 +1,180 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user