Commit 4db01043 authored by Guido Gunther's avatar Guido Gunther Committed by Angus Ainslie (Purism)

gpu: imx: Add our stripped down DCSS driver

Signed-off-by: Guido Gunther's avatarGuido Günther <guido.gunther@puri.sm>
parent e796f766
......@@ -3,5 +3,5 @@
# taken to initialize them in the correct order. Link order is the only way
# to ensure this currently.
obj-$(CONFIG_TEGRA_HOST1X) += host1x/
obj-y += drm/ vga/
obj-y += drm/ vga/ imx/
obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
......@@ -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"
......@@ -10,3 +10,5 @@ obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o
obj-$(CONFIG_DRM_IMX_IPUV3) += imx-ipuv3-crtc.o
obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o
obj-$(CONFIG_DRM_IMX_DCSS) += dcss/
config DRM_IMX_DCSS
tristate
depends on DRM_IMX
depends on IMX_DCSS_CORE
default y if DRM_IMX=y
default m if DRM_IMX=m
ccflags-y += -Idrivers/gpu/drm/imx
imx-dcss-crtc-objs := dcss-crtc.o dcss-plane.o dcss-kms.o
obj-$(CONFIG_DRM_IMX_DCSS) += imx-dcss-crtc.o
This diff is collapsed.
#ifndef _DCSS_CRTC_H
#include <linux/hdmi.h>
#include <video/imx-dcss.h>
void dcss_crtc_setup_opipe(struct drm_crtc *crtc, struct drm_connector *conn,
u32 colorimetry, u32 eotf,
enum hdmi_quantization_range qr);
int dcss_crtc_get_opipe_cfg(struct drm_crtc *crtc,
struct dcss_hdr10_pipe_cfg *opipe_cfg);
#endif
/*
* Copyright 2017 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <linux/dma-buf.h>
#include <linux/reservation.h>
#include "imx-drm.h"
#include "dcss-crtc.h"
static void dcss_drm_output_poll_changed(struct drm_device *drm)
{
struct imx_drm_device *imxdrm = drm->dev_private;
drm_fbdev_cma_hotplug_event(imxdrm->fbhelper);
}
static int dcss_drm_atomic_check(struct drm_device *drm,
struct drm_atomic_state *state)
{
int ret;
ret = drm_atomic_helper_check_modeset(drm, state);
if (ret)
return ret;
ret = drm_atomic_helper_check_planes(drm, state);
if (ret)
return ret;
/*
* Check modeset again in case crtc_state->mode_changed is
* updated in plane's ->atomic_check callback.
*/
return drm_atomic_helper_check_modeset(drm, state);
}
static int dcss_drm_atomic_commit(struct drm_device *drm,
struct drm_atomic_state *state,
bool nonblock)
{
struct drm_plane_state *plane_state;
struct drm_plane *plane;
struct dma_buf *dma_buf;
int i;
/*
* If the plane fb has an dma-buf attached, fish out the exclusive
* fence for the atomic helper to wait on.
*/
for_each_new_plane_in_state(state, plane, plane_state, i) {
if ((plane->state->fb != plane_state->fb) && plane_state->fb) {
dma_buf = drm_fb_cma_get_gem_obj(plane_state->fb,
0)->base.dma_buf;
if (!dma_buf)
continue;
plane_state->fence =
reservation_object_get_excl_rcu(dma_buf->resv);
}
}
return drm_atomic_helper_commit(drm, state, nonblock);
}
static void dcss_kms_setup_output_pipe(struct drm_atomic_state *state)
{
struct drm_crtc *crtc;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_display_info *di;
int i;
for_each_new_connector_in_state(state, connector, conn_state, i) {
if (!connector->state->best_encoder)
continue;
if (!connector->state->crtc->state->active ||
!drm_atomic_crtc_needs_modeset(connector->state->crtc->state))
continue;
crtc = connector->state->crtc;
di = &connector->display_info;
dcss_crtc_setup_opipe(crtc, connector, G_REC601_PAL,
NL_REC709,
HDMI_QUANTIZATION_RANGE_FULL);
}
}
static void dcss_drm_atomic_commit_tail(struct drm_atomic_state *state)
{
struct drm_device *dev = state->dev;
drm_atomic_helper_commit_modeset_disables(dev, state);
dcss_kms_setup_output_pipe(state);
drm_atomic_helper_commit_modeset_enables(dev, state);
drm_atomic_helper_commit_planes(dev, state,
DRM_PLANE_COMMIT_ACTIVE_ONLY);
drm_atomic_helper_commit_hw_done(state);
drm_atomic_helper_wait_for_vblanks(dev, state);
drm_atomic_helper_cleanup_planes(dev, state);
}
const struct drm_mode_config_funcs dcss_drm_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.output_poll_changed = dcss_drm_output_poll_changed,
.atomic_check = dcss_drm_atomic_check,
.atomic_commit = dcss_drm_atomic_commit,
};
struct drm_mode_config_helper_funcs dcss_drm_mode_config_helpers = {
.atomic_commit_tail = dcss_drm_atomic_commit_tail,
};
/*
* Copyright 2017 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef _DCSS_KMS_H_
#define _DCSS_KMS_H_
extern const struct drm_mode_config_funcs dcss_drm_mode_config_funcs;
extern struct drm_mode_config_helper_funcs dcss_drm_mode_config_helpers;
#endif
This diff is collapsed.
#ifndef __DCSS_PLANE_H__
#define __DCSS_PLANE_H__
#include <drm/drm_crtc.h>
struct dcss_plane {
struct drm_plane base;
struct dcss_soc *dcss;
int alpha_val;
struct drm_property *alpha_prop;
int use_global_val;
struct drm_property *use_global_prop;
uint64_t dtrc_table_ofs_val;
struct drm_property *dtrc_table_ofs_prop;
int ch_num;
};
struct dcss_plane *dcss_plane_init(struct drm_device *drm,
struct dcss_soc *dcss,
unsigned int possible_crtcs,
enum drm_plane_type type,
unsigned int zpos);
#endif /* __DCSS_PLANE_H__ */
source drivers/gpu/imx/dcss/Kconfig
obj-$(CONFIG_IMX_DCSS_CORE) += dcss/
config IMX_DCSS_CORE
tristate "i.MX DCSS core support"
depends on (ARCH_MXC || ARCH_MULTIPLATFORM || COMPILE_TEST)
depends on RESET_CONTROLLER
select IMX_IRQSTEER
help
Choose this if you have a Freescale i.MX8MQ system and want to use the
Display Controller Sub System. This option only enables DCSS base
support.
obj-$(CONFIG_IMX_DCSS_CORE) += imx-dcss-core.o
imx-dcss-core-objs := dcss-common.o dcss-blkctl.o dcss-ctxld.o \
dcss-dpr.o dcss-dtg.o dcss-ss.o dcss-hdr10.o \
dcss-scaler.o dcss-dtrc.o dcss-dec400d.o dcss-wrscl.o \
dcss-rdsrc.o
/*
* Copyright 2017 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/device.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/seq_file.h>
#include <soc/imx8/soc.h>
#include "dcss-prv.h"
#include <video/imx-dcss.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(3)
#define HDMI_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)
#define B0_SILICON_ID 0x20
static struct dcss_debug_reg blkctl_debug_reg[] = {
DCSS_DBG_REG(DCSS_BLKCTL_RESET_CTRL),
DCSS_DBG_REG(DCSS_BLKCTL_CONTROL0),
};
struct dcss_blkctl_priv {
struct dcss_soc *dcss;
void __iomem *base_reg;
bool hdmi_output;
u32 clk_setting;
};
#ifdef CONFIG_DEBUG_FS
void dcss_blkctl_dump_regs(struct seq_file *s, void *data)
{
struct dcss_soc *dcss = data;
int j;
seq_puts(s, ">> Dumping BLKCTL:\n");
for (j = 0; j < ARRAY_SIZE(blkctl_debug_reg); j++)
seq_printf(s, "%-35s(0x%04x) -> 0x%08x\n",
blkctl_debug_reg[j].name,
blkctl_debug_reg[j].ofs,
dcss_readl(dcss->blkctl_priv->base_reg +
blkctl_debug_reg[j].ofs));
}
#endif
static void dcss_blkctl_clk_reset(struct dcss_blkctl_priv *blkctl,
u32 assert, u32 deassert)
{
if (assert)
dcss_clr(assert, blkctl->base_reg + DCSS_BLKCTL_RESET_CTRL);
if (deassert)
dcss_set(deassert, blkctl->base_reg + DCSS_BLKCTL_RESET_CTRL);
}
void dcss_blkctl_cfg(struct dcss_soc *dcss)
{
struct dcss_blkctl_priv *blkctl = dcss->blkctl_priv;
if (blkctl->hdmi_output)
dcss_writel((blkctl->clk_setting ^ HDMI_MIPI_CLK_SEL),
blkctl->base_reg + DCSS_BLKCTL_CONTROL0);
else
dcss_writel((blkctl->clk_setting ^ HDMI_MIPI_CLK_SEL) |
DISPMIX_PIXCLK_SEL,
blkctl->base_reg + DCSS_BLKCTL_CONTROL0);
/* deassert clock domains resets */
dcss_blkctl_clk_reset(blkctl, 0, 0xffffff);
}
int dcss_blkctl_init(struct dcss_soc *dcss, unsigned long blkctl_base)
{
struct device_node *node = dcss->dev->of_node;
int len;
const char *disp_dev;
struct dcss_blkctl_priv *blkctl;
blkctl = devm_kzalloc(dcss->dev, sizeof(*blkctl), GFP_KERNEL);
if (!blkctl)
return -ENOMEM;
blkctl->base_reg = devm_ioremap(dcss->dev, blkctl_base, SZ_4K);
if (!blkctl->base_reg) {
dev_err(dcss->dev, "unable to remap BLK CTRL base\n");
return -ENOMEM;
}
blkctl->dcss = dcss;
dcss->blkctl_priv = blkctl;
disp_dev = of_get_property(node, "disp-dev", &len);
if (!disp_dev || !strncmp(disp_dev, "hdmi_disp", 9))
blkctl->hdmi_output = true;
if (imx8_get_soc_revision() == B0_SILICON_ID)
blkctl->clk_setting = HDMI_MIPI_CLK_SEL;
dcss_blkctl_cfg(dcss);
return 0;
}
void dcss_blkctl_exit(struct dcss_soc *dcss)
{
/* assert clock domains resets */
dcss_blkctl_clk_reset(dcss->blkctl_priv,
B_CLK_RESETN | APB_CLK_RESETN | P_CLK_RESETN |
HDMI_RESETN | RTR_CLK_RESETN, 0);
}
/* disabled only by cold reset/reboot */
void dcss_blkctl_hdmi_secure_src_en(struct dcss_soc *dcss)
{
struct dcss_blkctl_priv *blkctl = dcss->blkctl_priv;
dcss_set(HDMI_SRC_SECURE_EN, blkctl->base_reg + DCSS_BLKCTL_CONTROL0);
}
EXPORT_SYMBOL(dcss_blkctl_hdmi_secure_src_en);
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2017 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <linux/device.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <drm/drm_fourcc.h>
#include <video/imx-dcss.h>
#include "dcss-prv.h"
#define USE_CTXLD 1
/* DEC400D registers offsets */
#define DEC400D_READCONFIG_BASE 0x800
#define DEC400D_READCONFIG(i) (DEC400D_READCONFIG_BASE + ((i) << 2))
#define COMPRESSION_ENABLE_BIT 0
#define COMPRESSION_FORMAT_BIT 3
#define COMPRESSION_ALIGN_MODE_BIT 16
#define TILE_ALIGN_MODE_BIT 22
#define TILE_MODE_BIT 25
#define DEC400D_READBUFFERBASE0 0x900
#define DEC400D_READCACHEBASE0 0x980
#define DEC400D_CONTROL 0xB00
#define DISABLE_COMPRESSION_BIT 1
#define SHADOW_TRIGGER_BIT 29
struct dcss_dec400d_priv {
struct dcss_soc *dcss;
void __iomem *dec400d_reg;
uint32_t dec400d_reg_base;
uint64_t modifier[4];
uint32_t pixel_format;
uint32_t ctx_id;
bool bypass; /* bypass or decompress */
};
static void dcss_dec400d_write(struct dcss_dec400d_priv *dec400d,
uint32_t value,
uint32_t offset)
{
#if !USE_CTXLD
dcss_writel(value, dec400d->dec400d_reg + offset);
#else
dcss_ctxld_write(dec400d->dcss, dec400d->ctx_id,
value, dec400d->dec400d_reg_base + offset);
#endif
}
int dcss_dec400d_init(struct dcss_soc *dcss, unsigned long dec400d_base)
{
struct dcss_dec400d_priv *dec400d;
dec400d = devm_kzalloc(dcss->dev, sizeof(*dec400d), GFP_KERNEL);
if (!dec400d)
return -ENOMEM;
dcss->dec400d_priv = dec400d;
dec400d->dcss = dcss;
dec400d->dec400d_reg = devm_ioremap(dcss->dev, dec400d_base, SZ_4K);
if (!dec400d->dec400d_reg) {
dev_err(dcss->dev, "dec400d: unable to remap dec400d base\n");
return -ENOMEM;
}
dec400d->dec400d_reg_base = dec400d_base;
#if USE_CTXLD
dec400d->ctx_id = CTX_SB_HP;
#endif
return 0;
}
void dcss_dec400d_exit(struct dcss_soc *dcss)
{
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
if (!IS_ERR(dec400d)) {
devm_kfree(dcss->dev, dec400d);
dcss->dec400d_priv = NULL;
}
}
void dcss_dec400d_set_format_mod(struct dcss_soc *dcss,
uint32_t fourcc,
uint32_t mod_idx,
uint64_t modifier)
{
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
if (mod_idx > 3) {
WARN_ON(1);
return;
}
if (mod_idx == 0)
dec400d->pixel_format = fourcc;
dec400d->modifier[mod_idx] = modifier;
}
EXPORT_SYMBOL(dcss_dec400d_set_format_mod);
void dcss_dec400d_bypass(struct dcss_soc *dcss)
{
uint32_t control;
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
dcss_dec400d_read_config(dcss, 0, false);
control = dcss_readl(dec400d->dec400d_reg + DEC400D_CONTROL);
pr_debug("%s: dec400d control = %#x\n", __func__, control);
control |= 0x1 << DISABLE_COMPRESSION_BIT;
dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
/* Trigger shadow registers */
control |= 0x1 << SHADOW_TRIGGER_BIT;
dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
dec400d->bypass = true;
}
EXPORT_SYMBOL(dcss_dec400d_bypass);
void dcss_dec400d_shadow_trig(struct dcss_soc *dcss)
{
uint32_t control;
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
/* do nothing */
if (dec400d->bypass == true)
return;
control = dcss_readl(dec400d->dec400d_reg + DEC400D_CONTROL);
/* Trigger shadow registers */
control |= 0x1 << SHADOW_TRIGGER_BIT;
dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
}
EXPORT_SYMBOL(dcss_dec400d_shadow_trig);
void dcss_dec400d_addr_set(struct dcss_soc *dcss,
uint32_t baddr,
uint32_t caddr)
{
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
/* set frame buffer base addr */
dcss_dec400d_write(dec400d, baddr, DEC400D_READBUFFERBASE0);
/* set tile status cache addr */
dcss_dec400d_write(dec400d, caddr, DEC400D_READCACHEBASE0);
dec400d->bypass = false;
}
EXPORT_SYMBOL(dcss_dec400d_addr_set);
void dcss_dec400d_read_config(struct dcss_soc *dcss,
uint32_t read_id,
bool compress_en)
{
uint32_t read_config = 0x0;
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
/* TODO: using 'read_id' 0 by default */
if (read_id) {
WARN_ON(1);
return;
}
if (compress_en == false)
goto config;
switch (dec400d->pixel_format) {
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XRGB8888:
read_config |= 0x0 << COMPRESSION_FORMAT_BIT;
break;
default:
/* TODO: not support yet */
WARN_ON(1);
return;
}
/* ALIGN32_BYTE */
read_config |= 0x2 << COMPRESSION_ALIGN_MODE_BIT;
/* TILE1_ALIGN */
read_config |= 0x0 << TILE_ALIGN_MODE_BIT;
/* TILE8x4 */
read_config |= 0x3 << TILE_MODE_BIT;
/* Compression Enable */
read_config |= 0x1 << COMPRESSION_ENABLE_BIT;
config:
dcss_dec400d_write(dec400d, read_config, DEC400D_READCONFIG(read_id));
}
EXPORT_SYMBOL(dcss_dec400d_read_config);
void dcss_dec400d_enable(struct dcss_soc *dcss)
{
uint32_t control;
struct dcss_dec400d_priv *dec400d = dcss->dec400d_priv;
if (dec400d->bypass)
return;
control = dcss_readl(dec400d->dec400d_reg + DEC400D_CONTROL);
/* enable compression */
control &= ~(0x1 << DISABLE_COMPRESSION_BIT);
dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
/* Trigger shadow registers */
control |= 0x1 << SHADOW_TRIGGER_BIT;
dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
}
EXPORT_SYMBOL(dcss_dec400d_enable);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#ifndef __DCSS_PRV_H__
#define __DCSS_PRV_H__
#include <linux/pm_qos.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)
#define dcss_update(v, m, c) writel((readl(c) & ~(m)) | (v), (c))
#define DCSS_DBG_REG(reg) {.name = #reg, .ofs = reg}
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_soc;
struct dcss_devtype;
struct dcss_soc {
struct device *dev;
const struct dcss_devtype *devtype;
u32 start_addr;
struct dcss_blkctl_priv *blkctl_priv;
struct dcss_ctxld_priv *ctxld_priv;
struct dcss_dpr_priv *dpr_priv;
struct dcss_dtg_priv *dtg_priv;
struct dcss_ss_priv *ss_priv;
struct dcss_hdr10_priv *hdr10_priv;
struct dcss_scaler_priv *scaler_priv;
struct dcss_dtrc_priv *dtrc_priv;
struct dcss_dec400d_priv *dec400d_priv;
struct dcss_wrscl_priv *wrscl_priv;
struct dcss_rdsrc_priv *rdsrc_priv;
struct clk *apb_clk;
struct clk *axi_clk;
struct clk *pdiv_clk;
struct clk *pout_clk;
struct clk *rtrm_clk;
struct clk *dtrc_clk;
void (*dcss_disable_callback)(void *data);
bool bus_freq_req;
bool clks_on;
struct pm_qos_request pm_qos_req;
};
/* BLKCTL */
int dcss_blkctl_init(struct dcss_soc *dcss, unsigned long blkctl_base);
void dcss_blkctl_cfg(struct dcss_soc *dcss);
void dcss_blkctl_exit(struct dcss_soc *dcss);
/* CTXLD */
int dcss_ctxld_init(struct dcss_soc *dcss, unsigned long ctxld_base);
void dcss_ctxld_hw_cfg(struct dcss_soc *dcss);
void dcss_ctxld_exit(struct dcss_soc *dcss);
void dcss_ctxld_write(struct dcss_soc *dcss, u32 ctx_id, u32 val, u32 reg_idx);
void dcss_ctxld_update(struct dcss_soc *dcss, u32 ctx_id, u32 val, u32 mask,
u32 reg_idx);
void dcss_ctxld_dump(struct seq_file *s, void *data);
int dcss_ctxld_resume(struct dcss_soc *dcss);
int dcss_ctxld_suspend(struct dcss_soc *dcss);
void dcss_ctxld_write_irqsafe(struct dcss_soc *dcss, u32 ctx_id, u32 val,