summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/rockchip
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /drivers/gpu/drm/rockchip
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/rockchip')
-rw-r--r--drivers/gpu/drm/rockchip/Kconfig90
-rw-r--r--drivers/gpu/drm/rockchip/Makefile19
-rw-r--r--drivers/gpu/drm/rockchip/analogix_dp-rockchip.c480
-rw-r--r--drivers/gpu/drm/rockchip/cdn-dp-core.c1237
-rw-r--r--drivers/gpu/drm/rockchip/cdn-dp-core.h105
-rw-r--r--drivers/gpu/drm/rockchip/cdn-dp-reg.c960
-rw-r--r--drivers/gpu/drm/rockchip/cdn-dp-reg.h474
-rw-r--r--drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c1296
-rw-r--r--drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c614
-rw-r--r--drivers/gpu/drm/rockchip/inno_hdmi.c935
-rw-r--r--drivers/gpu/drm/rockchip/inno_hdmi.h354
-rw-r--r--drivers/gpu/drm/rockchip/rk3066_hdmi.c870
-rw-r--r--drivers/gpu/drm/rockchip/rk3066_hdmi.h229
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_drv.c517
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_drv.h70
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_fb.c137
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_fb.h17
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c163
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h24
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_gem.c545
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_gem.h53
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_vop.c2199
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_vop.h413
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_lvds.c747
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_lvds.h123
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_rgb.c164
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_rgb.h24
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_vop_reg.c1102
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_vop_reg.h1031
29 files changed, 14992 insertions, 0 deletions
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
new file mode 100644
index 000000000..310aa1546
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_ROCKCHIP
+ tristate "DRM Support for Rockchip"
+ depends on DRM && ROCKCHIP_IOMMU
+ select DRM_GEM_CMA_HELPER
+ select DRM_KMS_HELPER
+ select DRM_PANEL
+ select VIDEOMODE_HELPERS
+ select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
+ select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
+ select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
+ select DRM_RGB if ROCKCHIP_RGB
+ select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC
+ help
+ Choose this option if you have a Rockchip soc chipset.
+ This driver provides kernel mode setting and buffer
+ management to userspace. This driver does not provide
+ 2D or 3D acceleration; acceleration is performed by other
+ IP found on the SoC.
+
+if DRM_ROCKCHIP
+
+config ROCKCHIP_ANALOGIX_DP
+ bool "Rockchip specific extensions for Analogix DP driver"
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the Analogix Core DP driver. If you want to enable DP
+ on RK3288 or RK3399 based SoC, you should select this option.
+
+config ROCKCHIP_CDN_DP
+ bool "Rockchip cdn DP"
+ depends on EXTCON=y || (EXTCON=m && DRM_ROCKCHIP=m)
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the cdn DP driver. If you want to enable Dp on
+ RK3399 based SoC, you should select this
+ option.
+
+config ROCKCHIP_DW_HDMI
+ bool "Rockchip specific extensions for Synopsys DW HDMI"
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the Synopsys DesignWare HDMI driver. If you want to
+ enable HDMI on RK3288 or RK3399 based SoC, you should select
+ this option.
+
+config ROCKCHIP_DW_MIPI_DSI
+ bool "Rockchip specific extensions for Synopsys DW MIPI DSI"
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the Synopsys DesignWare HDMI driver. If you want to
+ enable MIPI DSI on RK3288 or RK3399 based SoC, you should
+ select this option.
+
+config ROCKCHIP_INNO_HDMI
+ bool "Rockchip specific extensions for Innosilicon HDMI"
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the Innosilicon HDMI driver. If you want to enable
+ HDMI on RK3036 based SoC, you should select this option.
+
+config ROCKCHIP_LVDS
+ bool "Rockchip LVDS support"
+ depends on DRM_ROCKCHIP
+ depends on PINCTRL && OF
+ help
+ Choose this option to enable support for Rockchip LVDS controllers.
+ Rockchip rk3288 SoC has LVDS TX Controller can be used, and it
+ support LVDS, rgb, dual LVDS output mode. say Y to enable its
+ driver.
+
+config ROCKCHIP_RGB
+ bool "Rockchip RGB support"
+ depends on DRM_ROCKCHIP
+ depends on PINCTRL
+ help
+ Choose this option to enable support for Rockchip RGB output.
+ Some Rockchip CRTCs, like rv1108, can directly output parallel
+ and serial RGB format to panel or connect to a conversion chip.
+ say Y to enable its driver.
+
+config ROCKCHIP_RK3066_HDMI
+ bool "Rockchip specific extensions for RK3066 HDMI"
+ depends on DRM_ROCKCHIP
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the RK3066 HDMI driver. If you want to enable
+ HDMI on RK3066 based SoC, you should select this option.
+endif
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
new file mode 100644
index 000000000..17a9e7eb2
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the drm device driver. This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
+ rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o
+rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
+
+rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
+rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
+rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
+rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
+rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
+rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
+rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
+rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o
+
+obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
new file mode 100644
index 000000000..512581698
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Rockchip SoC DP (Display Port) interface driver.
+ *
+ * Copyright (C) Fuzhou Rockchip Electronics Co., Ltd.
+ * Author: Andy Yan <andy.yan@rock-chips.com>
+ * Yakir Yang <ykk@rock-chips.com>
+ * Jeff Chen <jeff.chen@rock-chips.com>
+ */
+
+#include <linux/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/bridge/analogix_dp.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+#define RK3288_GRF_SOC_CON6 0x25c
+#define RK3288_EDP_LCDC_SEL BIT(5)
+#define RK3399_GRF_SOC_CON20 0x6250
+#define RK3399_EDP_LCDC_SEL BIT(5)
+
+#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)
+
+#define PSR_WAIT_LINE_FLAG_TIMEOUT_MS 100
+
+#define to_dp(nm) container_of(nm, struct rockchip_dp_device, nm)
+
+/**
+ * struct rockchip_dp_chip_data - splite the grf setting of kind of chips
+ * @lcdsel_grf_reg: grf register offset of lcdc select
+ * @lcdsel_big: reg value of selecting vop big for eDP
+ * @lcdsel_lit: reg value of selecting vop little for eDP
+ * @chip_type: specific chip type
+ */
+struct rockchip_dp_chip_data {
+ u32 lcdsel_grf_reg;
+ u32 lcdsel_big;
+ u32 lcdsel_lit;
+ u32 chip_type;
+};
+
+struct rockchip_dp_device {
+ struct drm_device *drm_dev;
+ struct device *dev;
+ struct drm_encoder encoder;
+ struct drm_display_mode mode;
+
+ struct clk *pclk;
+ struct clk *grfclk;
+ struct regmap *grf;
+ struct reset_control *rst;
+
+ const struct rockchip_dp_chip_data *data;
+
+ struct analogix_dp_device *adp;
+ struct analogix_dp_plat_data plat_data;
+};
+
+static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
+{
+ reset_control_assert(dp->rst);
+ usleep_range(10, 20);
+ reset_control_deassert(dp->rst);
+
+ return 0;
+}
+
+static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
+{
+ struct rockchip_dp_device *dp = to_dp(plat_data);
+ int ret;
+
+ ret = clk_prepare_enable(dp->pclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "failed to enable pclk %d\n", ret);
+ return ret;
+ }
+
+ ret = rockchip_dp_pre_init(dp);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "failed to dp pre init %d\n", ret);
+ clk_disable_unprepare(dp->pclk);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
+{
+ struct rockchip_dp_device *dp = to_dp(plat_data);
+
+ clk_disable_unprepare(dp->pclk);
+
+ return 0;
+}
+
+static int rockchip_dp_get_modes(struct analogix_dp_plat_data *plat_data,
+ struct drm_connector *connector)
+{
+ struct drm_display_info *di = &connector->display_info;
+ /* VOP couldn't output YUV video format for eDP rightly */
+ u32 mask = DRM_COLOR_FORMAT_YCRCB444 | DRM_COLOR_FORMAT_YCRCB422;
+
+ if ((di->color_formats & mask)) {
+ DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n");
+ di->color_formats &= ~mask;
+ di->color_formats |= DRM_COLOR_FORMAT_RGB444;
+ di->bpc = 8;
+ }
+
+ return 0;
+}
+
+static bool
+rockchip_dp_drm_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* do nothing */
+ return true;
+}
+
+static void rockchip_dp_drm_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted)
+{
+ /* do nothing */
+}
+
+static
+struct drm_crtc *rockchip_dp_drm_get_new_crtc(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector *connector;
+ struct drm_connector_state *conn_state;
+
+ connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
+ if (!connector)
+ return NULL;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!conn_state)
+ return NULL;
+
+ return conn_state->crtc;
+}
+
+static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct rockchip_dp_device *dp = to_dp(encoder);
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *old_crtc_state;
+ int ret;
+ u32 val;
+
+ crtc = rockchip_dp_drm_get_new_crtc(encoder, state);
+ if (!crtc)
+ return;
+
+ old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
+ /* Coming back from self refresh, nothing to do */
+ if (old_crtc_state && old_crtc_state->self_refresh_active)
+ return;
+
+ ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
+ if (ret < 0)
+ return;
+
+ if (ret)
+ val = dp->data->lcdsel_lit;
+ else
+ val = dp->data->lcdsel_big;
+
+ DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG");
+
+ ret = clk_prepare_enable(dp->grfclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "failed to enable grfclk %d\n", ret);
+ return;
+ }
+
+ ret = regmap_write(dp->grf, dp->data->lcdsel_grf_reg, val);
+ if (ret != 0)
+ DRM_DEV_ERROR(dp->dev, "Could not write to GRF: %d\n", ret);
+
+ clk_disable_unprepare(dp->grfclk);
+}
+
+static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct rockchip_dp_device *dp = to_dp(encoder);
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *new_crtc_state = NULL;
+ int ret;
+
+ crtc = rockchip_dp_drm_get_new_crtc(encoder, state);
+ /* No crtc means we're doing a full shutdown */
+ if (!crtc)
+ return;
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ /* If we're not entering self-refresh, no need to wait for vact */
+ if (!new_crtc_state || !new_crtc_state->self_refresh_active)
+ return;
+
+ ret = rockchip_drm_wait_vact_end(crtc, PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "line flag irq timed out\n");
+}
+
+static int
+rockchip_dp_drm_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+ struct drm_display_info *di = &conn_state->connector->display_info;
+
+ /*
+ * The hardware IC designed that VOP must output the RGB10 video
+ * format to eDP controller, and if eDP panel only support RGB8,
+ * then eDP controller should cut down the video data, not via VOP
+ * controller, that's why we need to hardcode the VOP output mode
+ * to RGA10 here.
+ */
+
+ s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+ s->output_type = DRM_MODE_CONNECTOR_eDP;
+ s->output_bpc = di->bpc;
+
+ return 0;
+}
+
+static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = {
+ .mode_fixup = rockchip_dp_drm_encoder_mode_fixup,
+ .mode_set = rockchip_dp_drm_encoder_mode_set,
+ .atomic_enable = rockchip_dp_drm_encoder_enable,
+ .atomic_disable = rockchip_dp_drm_encoder_disable,
+ .atomic_check = rockchip_dp_drm_encoder_atomic_check,
+};
+
+static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
+{
+ struct device *dev = dp->dev;
+ struct device_node *np = dev->of_node;
+
+ dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(dp->grf)) {
+ DRM_DEV_ERROR(dev, "failed to get rockchip,grf property\n");
+ return PTR_ERR(dp->grf);
+ }
+
+ dp->grfclk = devm_clk_get(dev, "grf");
+ if (PTR_ERR(dp->grfclk) == -ENOENT) {
+ dp->grfclk = NULL;
+ } else if (PTR_ERR(dp->grfclk) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (IS_ERR(dp->grfclk)) {
+ DRM_DEV_ERROR(dev, "failed to get grf clock\n");
+ return PTR_ERR(dp->grfclk);
+ }
+
+ dp->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(dp->pclk)) {
+ DRM_DEV_ERROR(dev, "failed to get pclk property\n");
+ return PTR_ERR(dp->pclk);
+ }
+
+ dp->rst = devm_reset_control_get(dev, "dp");
+ if (IS_ERR(dp->rst)) {
+ DRM_DEV_ERROR(dev, "failed to get dp reset control\n");
+ return PTR_ERR(dp->rst);
+ }
+
+ return 0;
+}
+
+static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp)
+{
+ struct drm_encoder *encoder = &dp->encoder;
+ struct drm_device *drm_dev = dp->drm_dev;
+ struct device *dev = dp->dev;
+ int ret;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+ dev->of_node);
+ DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder,
+ DRM_MODE_ENCODER_TMDS);
+ if (ret) {
+ DRM_ERROR("failed to initialize encoder with drm\n");
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &rockchip_dp_encoder_helper_funcs);
+
+ return 0;
+}
+
+static int rockchip_dp_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rockchip_dp_device *dp = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ int ret;
+
+ dp->drm_dev = drm_dev;
+
+ ret = rockchip_dp_drm_create_encoder(dp);
+ if (ret) {
+ DRM_ERROR("failed to create drm encoder\n");
+ return ret;
+ }
+
+ dp->plat_data.encoder = &dp->encoder;
+
+ ret = analogix_dp_bind(dp->adp, drm_dev);
+ if (ret)
+ goto err_cleanup_encoder;
+
+ return 0;
+err_cleanup_encoder:
+ dp->encoder.funcs->destroy(&dp->encoder);
+ return ret;
+}
+
+static void rockchip_dp_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rockchip_dp_device *dp = dev_get_drvdata(dev);
+
+ analogix_dp_unbind(dp->adp);
+ dp->encoder.funcs->destroy(&dp->encoder);
+}
+
+static const struct component_ops rockchip_dp_component_ops = {
+ .bind = rockchip_dp_bind,
+ .unbind = rockchip_dp_unbind,
+};
+
+static int rockchip_dp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct rockchip_dp_chip_data *dp_data;
+ struct drm_panel *panel = NULL;
+ struct rockchip_dp_device *dp;
+ int ret;
+
+ dp_data = of_device_get_match_data(dev);
+ if (!dp_data)
+ return -ENODEV;
+
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
+ if (ret < 0)
+ return ret;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ dp->dev = dev;
+ dp->adp = ERR_PTR(-ENODEV);
+ dp->data = dp_data;
+ dp->plat_data.panel = panel;
+ dp->plat_data.dev_type = dp->data->chip_type;
+ dp->plat_data.power_on_start = rockchip_dp_poweron_start;
+ dp->plat_data.power_off = rockchip_dp_powerdown;
+ dp->plat_data.get_modes = rockchip_dp_get_modes;
+
+ ret = rockchip_dp_of_probe(dp);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, dp);
+
+ dp->adp = analogix_dp_probe(dev, &dp->plat_data);
+ if (IS_ERR(dp->adp))
+ return PTR_ERR(dp->adp);
+
+ ret = component_add(dev, &rockchip_dp_component_ops);
+ if (ret)
+ goto err_dp_remove;
+
+ return 0;
+
+err_dp_remove:
+ analogix_dp_remove(dp->adp);
+ return ret;
+}
+
+static int rockchip_dp_remove(struct platform_device *pdev)
+{
+ struct rockchip_dp_device *dp = platform_get_drvdata(pdev);
+
+ component_del(&pdev->dev, &rockchip_dp_component_ops);
+ analogix_dp_remove(dp->adp);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rockchip_dp_suspend(struct device *dev)
+{
+ struct rockchip_dp_device *dp = dev_get_drvdata(dev);
+
+ if (IS_ERR(dp->adp))
+ return 0;
+
+ return analogix_dp_suspend(dp->adp);
+}
+
+static int rockchip_dp_resume(struct device *dev)
+{
+ struct rockchip_dp_device *dp = dev_get_drvdata(dev);
+
+ if (IS_ERR(dp->adp))
+ return 0;
+
+ return analogix_dp_resume(dp->adp);
+}
+#endif
+
+static const struct dev_pm_ops rockchip_dp_pm_ops = {
+#ifdef CONFIG_PM_SLEEP
+ .suspend_late = rockchip_dp_suspend,
+ .resume_early = rockchip_dp_resume,
+#endif
+};
+
+static const struct rockchip_dp_chip_data rk3399_edp = {
+ .lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3399_EDP_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3399_EDP_LCDC_SEL, RK3399_EDP_LCDC_SEL),
+ .chip_type = RK3399_EDP,
+};
+
+static const struct rockchip_dp_chip_data rk3288_dp = {
+ .lcdsel_grf_reg = RK3288_GRF_SOC_CON6,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3288_EDP_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3288_EDP_LCDC_SEL, RK3288_EDP_LCDC_SEL),
+ .chip_type = RK3288_DP,
+};
+
+static const struct of_device_id rockchip_dp_dt_ids[] = {
+ {.compatible = "rockchip,rk3288-dp", .data = &rk3288_dp },
+ {.compatible = "rockchip,rk3399-edp", .data = &rk3399_edp },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rockchip_dp_dt_ids);
+
+struct platform_driver rockchip_dp_driver = {
+ .probe = rockchip_dp_probe,
+ .remove = rockchip_dp_remove,
+ .driver = {
+ .name = "rockchip-dp",
+ .pm = &rockchip_dp_pm_ops,
+ .of_match_table = of_match_ptr(rockchip_dp_dt_ids),
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
new file mode 100644
index 000000000..53cad1003
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -0,0 +1,1237 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/extcon.h>
+#include <linux/firmware.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <sound/hdmi-codec.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+#include "rockchip_drm_vop.h"
+
+#define connector_to_dp(c) \
+ container_of(c, struct cdn_dp_device, connector)
+
+#define encoder_to_dp(c) \
+ container_of(c, struct cdn_dp_device, encoder)
+
+#define GRF_SOC_CON9 0x6224
+#define DP_SEL_VOP_LIT BIT(12)
+#define GRF_SOC_CON26 0x6268
+#define DPTX_HPD_SEL (3 << 12)
+#define DPTX_HPD_DEL (2 << 12)
+#define DPTX_HPD_SEL_MASK (3 << 28)
+
+#define CDN_FW_TIMEOUT_MS (64 * 1000)
+#define CDN_DPCD_TIMEOUT_MS 5000
+#define CDN_DP_FIRMWARE "rockchip/dptx.bin"
+
+struct cdn_dp_data {
+ u8 max_phy;
+};
+
+struct cdn_dp_data rk3399_cdn_dp = {
+ .max_phy = 2,
+};
+
+static const struct of_device_id cdn_dp_dt_ids[] = {
+ { .compatible = "rockchip,rk3399-cdn-dp",
+ .data = (void *)&rk3399_cdn_dp },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
+
+static int cdn_dp_grf_write(struct cdn_dp_device *dp,
+ unsigned int reg, unsigned int val)
+{
+ int ret;
+
+ ret = clk_prepare_enable(dp->grf_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to prepare_enable grf clock\n");
+ return ret;
+ }
+
+ ret = regmap_write(dp->grf, reg, val);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Could not write to GRF: %d\n", ret);
+ clk_disable_unprepare(dp->grf_clk);
+ return ret;
+ }
+
+ clk_disable_unprepare(dp->grf_clk);
+
+ return 0;
+}
+
+static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
+{
+ int ret;
+ unsigned long rate;
+
+ ret = clk_prepare_enable(dp->pclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "cannot enable dp pclk %d\n", ret);
+ goto err_pclk;
+ }
+
+ ret = clk_prepare_enable(dp->core_clk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "cannot enable core_clk %d\n", ret);
+ goto err_core_clk;
+ }
+
+ ret = pm_runtime_get_sync(dp->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "cannot get pm runtime %d\n", ret);
+ goto err_pm_runtime_get;
+ }
+
+ reset_control_assert(dp->core_rst);
+ reset_control_assert(dp->dptx_rst);
+ reset_control_assert(dp->apb_rst);
+ reset_control_deassert(dp->core_rst);
+ reset_control_deassert(dp->dptx_rst);
+ reset_control_deassert(dp->apb_rst);
+
+ rate = clk_get_rate(dp->core_clk);
+ if (!rate) {
+ DRM_DEV_ERROR(dp->dev, "get clk rate failed\n");
+ ret = -EINVAL;
+ goto err_set_rate;
+ }
+
+ cdn_dp_set_fw_clk(dp, rate);
+ cdn_dp_clock_reset(dp);
+
+ return 0;
+
+err_set_rate:
+ pm_runtime_put(dp->dev);
+err_pm_runtime_get:
+ clk_disable_unprepare(dp->core_clk);
+err_core_clk:
+ clk_disable_unprepare(dp->pclk);
+err_pclk:
+ return ret;
+}
+
+static void cdn_dp_clk_disable(struct cdn_dp_device *dp)
+{
+ pm_runtime_put_sync(dp->dev);
+ clk_disable_unprepare(dp->pclk);
+ clk_disable_unprepare(dp->core_clk);
+}
+
+static int cdn_dp_get_port_lanes(struct cdn_dp_port *port)
+{
+ struct extcon_dev *edev = port->extcon;
+ union extcon_property_value property;
+ int dptx;
+ u8 lanes;
+
+ dptx = extcon_get_state(edev, EXTCON_DISP_DP);
+ if (dptx > 0) {
+ extcon_get_property(edev, EXTCON_DISP_DP,
+ EXTCON_PROP_USB_SS, &property);
+ if (property.intval)
+ lanes = 2;
+ else
+ lanes = 4;
+ } else {
+ lanes = 0;
+ }
+
+ return lanes;
+}
+
+static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count)
+{
+ int ret;
+ u8 value;
+
+ *sink_count = 0;
+ ret = cdn_dp_dpcd_read(dp, DP_SINK_COUNT, &value, 1);
+ if (ret)
+ return ret;
+
+ *sink_count = DP_GET_SINK_COUNT(value);
+ return 0;
+}
+
+static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp)
+{
+ struct cdn_dp_port *port;
+ int i, lanes;
+
+ for (i = 0; i < dp->ports; i++) {
+ port = dp->port[i];
+ lanes = cdn_dp_get_port_lanes(port);
+ if (lanes)
+ return port;
+ }
+ return NULL;
+}
+
+static bool cdn_dp_check_sink_connection(struct cdn_dp_device *dp)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(CDN_DPCD_TIMEOUT_MS);
+ struct cdn_dp_port *port;
+ u8 sink_count = 0;
+
+ if (dp->active_port < 0 || dp->active_port >= dp->ports) {
+ DRM_DEV_ERROR(dp->dev, "active_port is wrong!\n");
+ return false;
+ }
+
+ port = dp->port[dp->active_port];
+
+ /*
+ * Attempt to read sink count, retry in case the sink may not be ready.
+ *
+ * Sinks are *supposed* to come up within 1ms from an off state, but
+ * some docks need more time to power up.
+ */
+ while (time_before(jiffies, timeout)) {
+ if (!extcon_get_state(port->extcon, EXTCON_DISP_DP))
+ return false;
+
+ if (!cdn_dp_get_sink_count(dp, &sink_count))
+ return sink_count ? true : false;
+
+ usleep_range(5000, 10000);
+ }
+
+ DRM_DEV_ERROR(dp->dev, "Get sink capability timed out\n");
+ return false;
+}
+
+static enum drm_connector_status
+cdn_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct cdn_dp_device *dp = connector_to_dp(connector);
+ enum drm_connector_status status = connector_status_disconnected;
+
+ mutex_lock(&dp->lock);
+ if (dp->connected)
+ status = connector_status_connected;
+ mutex_unlock(&dp->lock);
+
+ return status;
+}
+
+static void cdn_dp_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs cdn_dp_atomic_connector_funcs = {
+ .detect = cdn_dp_connector_detect,
+ .destroy = cdn_dp_connector_destroy,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int cdn_dp_connector_get_modes(struct drm_connector *connector)
+{
+ struct cdn_dp_device *dp = connector_to_dp(connector);
+ struct edid *edid;
+ int ret = 0;
+
+ mutex_lock(&dp->lock);
+ edid = dp->edid;
+ if (edid) {
+ DRM_DEV_DEBUG_KMS(dp->dev, "got edid: width[%d] x height[%d]\n",
+ edid->width_cm, edid->height_cm);
+
+ dp->sink_has_audio = drm_detect_monitor_audio(edid);
+ ret = drm_add_edid_modes(connector, edid);
+ if (ret)
+ drm_connector_update_edid_property(connector,
+ edid);
+ }
+ mutex_unlock(&dp->lock);
+
+ return ret;
+}
+
+static enum drm_mode_status
+cdn_dp_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct cdn_dp_device *dp = connector_to_dp(connector);
+ struct drm_display_info *display_info = &dp->connector.display_info;
+ u32 requested, actual, rate, sink_max, source_max = 0;
+ u8 lanes, bpc;
+
+ /* If DP is disconnected, every mode is invalid */
+ if (!dp->connected)
+ return MODE_BAD;
+
+ switch (display_info->bpc) {
+ case 10:
+ bpc = 10;
+ break;
+ case 6:
+ bpc = 6;
+ break;
+ default:
+ bpc = 8;
+ break;
+ }
+
+ requested = mode->clock * bpc * 3 / 1000;
+
+ source_max = dp->lanes;
+ sink_max = drm_dp_max_lane_count(dp->dpcd);
+ lanes = min(source_max, sink_max);
+
+ source_max = drm_dp_bw_code_to_link_rate(CDN_DP_MAX_LINK_RATE);
+ sink_max = drm_dp_max_link_rate(dp->dpcd);
+ rate = min(source_max, sink_max);
+
+ actual = rate * lanes / 100;
+
+ /* efficiency is about 0.8 */
+ actual = actual * 8 / 10;
+
+ if (requested > actual) {
+ DRM_DEV_DEBUG_KMS(dp->dev,
+ "requested=%d, actual=%d, clock=%d\n",
+ requested, actual, mode->clock);
+ return MODE_CLOCK_HIGH;
+ }
+
+ return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs cdn_dp_connector_helper_funcs = {
+ .get_modes = cdn_dp_connector_get_modes,
+ .mode_valid = cdn_dp_connector_mode_valid,
+};
+
+static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
+{
+ int ret;
+ const u32 *iram_data, *dram_data;
+ const struct firmware *fw = dp->fw;
+ const struct cdn_firmware_header *hdr;
+
+ hdr = (struct cdn_firmware_header *)fw->data;
+ if (fw->size != le32_to_cpu(hdr->size_bytes)) {
+ DRM_DEV_ERROR(dp->dev, "firmware is invalid\n");
+ return -EINVAL;
+ }
+
+ iram_data = (const u32 *)(fw->data + hdr->header_size);
+ dram_data = (const u32 *)(fw->data + hdr->header_size + hdr->iram_size);
+
+ ret = cdn_dp_load_firmware(dp, iram_data, hdr->iram_size,
+ dram_data, hdr->dram_size);
+ if (ret)
+ return ret;
+
+ ret = cdn_dp_set_firmware_active(dp, true);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "active ucpu failed: %d\n", ret);
+ return ret;
+ }
+
+ return cdn_dp_event_config(dp);
+}
+
+static int cdn_dp_get_sink_capability(struct cdn_dp_device *dp)
+{
+ int ret;
+
+ if (!cdn_dp_check_sink_connection(dp))
+ return -ENODEV;
+
+ ret = cdn_dp_dpcd_read(dp, DP_DPCD_REV, dp->dpcd,
+ DP_RECEIVER_CAP_SIZE);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to get caps %d\n", ret);
+ return ret;
+ }
+
+ kfree(dp->edid);
+ dp->edid = drm_do_get_edid(&dp->connector,
+ cdn_dp_get_edid_block, dp);
+ return 0;
+}
+
+static int cdn_dp_enable_phy(struct cdn_dp_device *dp, struct cdn_dp_port *port)
+{
+ union extcon_property_value property;
+ int ret;
+
+ if (!port->phy_enabled) {
+ ret = phy_power_on(port->phy);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "phy power on failed: %d\n",
+ ret);
+ goto err_phy;
+ }
+ port->phy_enabled = true;
+ }
+
+ ret = cdn_dp_grf_write(dp, GRF_SOC_CON26,
+ DPTX_HPD_SEL_MASK | DPTX_HPD_SEL);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to write HPD_SEL %d\n", ret);
+ goto err_power_on;
+ }
+
+ ret = cdn_dp_get_hpd_status(dp);
+ if (ret <= 0) {
+ if (!ret)
+ DRM_DEV_ERROR(dp->dev, "hpd does not exist\n");
+ goto err_power_on;
+ }
+
+ ret = extcon_get_property(port->extcon, EXTCON_DISP_DP,
+ EXTCON_PROP_USB_TYPEC_POLARITY, &property);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "get property failed\n");
+ goto err_power_on;
+ }
+
+ port->lanes = cdn_dp_get_port_lanes(port);
+ ret = cdn_dp_set_host_cap(dp, port->lanes, property.intval);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "set host capabilities failed: %d\n",
+ ret);
+ goto err_power_on;
+ }
+
+ dp->active_port = port->id;
+ return 0;
+
+err_power_on:
+ if (phy_power_off(port->phy))
+ DRM_DEV_ERROR(dp->dev, "phy power off failed: %d", ret);
+ else
+ port->phy_enabled = false;
+
+err_phy:
+ cdn_dp_grf_write(dp, GRF_SOC_CON26,
+ DPTX_HPD_SEL_MASK | DPTX_HPD_DEL);
+ return ret;
+}
+
+static int cdn_dp_disable_phy(struct cdn_dp_device *dp,
+ struct cdn_dp_port *port)
+{
+ int ret;
+
+ if (port->phy_enabled) {
+ ret = phy_power_off(port->phy);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "phy power off failed: %d", ret);
+ return ret;
+ }
+ }
+
+ port->phy_enabled = false;
+ port->lanes = 0;
+ dp->active_port = -1;
+ return 0;
+}
+
+static int cdn_dp_disable(struct cdn_dp_device *dp)
+{
+ int ret, i;
+
+ if (!dp->active)
+ return 0;
+
+ for (i = 0; i < dp->ports; i++)
+ cdn_dp_disable_phy(dp, dp->port[i]);
+
+ ret = cdn_dp_grf_write(dp, GRF_SOC_CON26,
+ DPTX_HPD_SEL_MASK | DPTX_HPD_DEL);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to clear hpd sel %d\n",
+ ret);
+ return ret;
+ }
+
+ cdn_dp_set_firmware_active(dp, false);
+ cdn_dp_clk_disable(dp);
+ dp->active = false;
+ dp->max_lanes = 0;
+ dp->max_rate = 0;
+ if (!dp->connected) {
+ kfree(dp->edid);
+ dp->edid = NULL;
+ }
+
+ return 0;
+}
+
+static int cdn_dp_enable(struct cdn_dp_device *dp)
+{
+ int ret, i, lanes;
+ struct cdn_dp_port *port;
+
+ port = cdn_dp_connected_port(dp);
+ if (!port) {
+ DRM_DEV_ERROR(dp->dev,
+ "Can't enable without connection\n");
+ return -ENODEV;
+ }
+
+ if (dp->active)
+ return 0;
+
+ ret = cdn_dp_clk_enable(dp);
+ if (ret)
+ return ret;
+
+ ret = cdn_dp_firmware_init(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "firmware init failed: %d", ret);
+ goto err_clk_disable;
+ }
+
+ /* only enable the port that connected with downstream device */
+ for (i = port->id; i < dp->ports; i++) {
+ port = dp->port[i];
+ lanes = cdn_dp_get_port_lanes(port);
+ if (lanes) {
+ ret = cdn_dp_enable_phy(dp, port);
+ if (ret)
+ continue;
+
+ ret = cdn_dp_get_sink_capability(dp);
+ if (ret) {
+ cdn_dp_disable_phy(dp, port);
+ } else {
+ dp->active = true;
+ dp->lanes = port->lanes;
+ return 0;
+ }
+ }
+ }
+
+err_clk_disable:
+ cdn_dp_clk_disable(dp);
+ return ret;
+}
+
+static void cdn_dp_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted)
+{
+ struct cdn_dp_device *dp = encoder_to_dp(encoder);
+ struct drm_display_info *display_info = &dp->connector.display_info;
+ struct video_info *video = &dp->video_info;
+
+ switch (display_info->bpc) {
+ case 10:
+ video->color_depth = 10;
+ break;
+ case 6:
+ video->color_depth = 6;
+ break;
+ default:
+ video->color_depth = 8;
+ break;
+ }
+
+ video->color_fmt = PXL_RGB;
+ video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+ video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+ drm_mode_copy(&dp->mode, adjusted);
+}
+
+static bool cdn_dp_check_link_status(struct cdn_dp_device *dp)
+{
+ u8 link_status[DP_LINK_STATUS_SIZE];
+ struct cdn_dp_port *port = cdn_dp_connected_port(dp);
+ u8 sink_lanes = drm_dp_max_lane_count(dp->dpcd);
+
+ if (!port || !dp->max_rate || !dp->max_lanes)
+ return false;
+
+ if (cdn_dp_dpcd_read(dp, DP_LANE0_1_STATUS, link_status,
+ DP_LINK_STATUS_SIZE)) {
+ DRM_ERROR("Failed to get link status\n");
+ return false;
+ }
+
+ /* if link training is requested we should perform it always */
+ return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes));
+}
+
+static void cdn_dp_encoder_enable(struct drm_encoder *encoder)
+{
+ struct cdn_dp_device *dp = encoder_to_dp(encoder);
+ int ret, val;
+
+ ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "Could not get vop id, %d", ret);
+ return;
+ }
+
+ DRM_DEV_DEBUG_KMS(dp->dev, "vop %s output to cdn-dp\n",
+ (ret) ? "LIT" : "BIG");
+ if (ret)
+ val = DP_SEL_VOP_LIT | (DP_SEL_VOP_LIT << 16);
+ else
+ val = DP_SEL_VOP_LIT << 16;
+
+ ret = cdn_dp_grf_write(dp, GRF_SOC_CON9, val);
+ if (ret)
+ return;
+
+ mutex_lock(&dp->lock);
+
+ ret = cdn_dp_enable(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to enable encoder %d\n",
+ ret);
+ goto out;
+ }
+ if (!cdn_dp_check_link_status(dp)) {
+ ret = cdn_dp_train_link(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed link train %d\n", ret);
+ goto out;
+ }
+ }
+
+ ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_IDLE);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to idle video %d\n", ret);
+ goto out;
+ }
+
+ ret = cdn_dp_config_video(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to config video %d\n", ret);
+ goto out;
+ }
+
+ ret = cdn_dp_set_video_status(dp, CONTROL_VIDEO_VALID);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to valid video %d\n", ret);
+ goto out;
+ }
+out:
+ mutex_unlock(&dp->lock);
+}
+
+static void cdn_dp_encoder_disable(struct drm_encoder *encoder)
+{
+ struct cdn_dp_device *dp = encoder_to_dp(encoder);
+ int ret;
+
+ mutex_lock(&dp->lock);
+ if (dp->active) {
+ ret = cdn_dp_disable(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to disable encoder %d\n",
+ ret);
+ }
+ }
+ mutex_unlock(&dp->lock);
+
+ /*
+ * In the following 2 cases, we need to run the event_work to re-enable
+ * the DP:
+ * 1. If there is not just one port device is connected, and remove one
+ * device from a port, the DP will be disabled here, at this case,
+ * run the event_work to re-open DP for the other port.
+ * 2. If re-training or re-config failed, the DP will be disabled here.
+ * run the event_work to re-connect it.
+ */
+ if (!dp->connected && cdn_dp_connected_port(dp))
+ schedule_work(&dp->event_work);
+}
+
+static int cdn_dp_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+
+ s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+ s->output_type = DRM_MODE_CONNECTOR_DisplayPort;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs cdn_dp_encoder_helper_funcs = {
+ .mode_set = cdn_dp_encoder_mode_set,
+ .enable = cdn_dp_encoder_enable,
+ .disable = cdn_dp_encoder_disable,
+ .atomic_check = cdn_dp_encoder_atomic_check,
+};
+
+static int cdn_dp_parse_dt(struct cdn_dp_device *dp)
+{
+ struct device *dev = dp->dev;
+ struct device_node *np = dev->of_node;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct resource *res;
+
+ dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(dp->grf)) {
+ DRM_DEV_ERROR(dev, "cdn-dp needs rockchip,grf property\n");
+ return PTR_ERR(dp->grf);
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dp->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dp->regs)) {
+ DRM_DEV_ERROR(dev, "ioremap reg failed\n");
+ return PTR_ERR(dp->regs);
+ }
+
+ dp->core_clk = devm_clk_get(dev, "core-clk");
+ if (IS_ERR(dp->core_clk)) {
+ DRM_DEV_ERROR(dev, "cannot get core_clk_dp\n");
+ return PTR_ERR(dp->core_clk);
+ }
+
+ dp->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(dp->pclk)) {
+ DRM_DEV_ERROR(dev, "cannot get pclk\n");
+ return PTR_ERR(dp->pclk);
+ }
+
+ dp->spdif_clk = devm_clk_get(dev, "spdif");
+ if (IS_ERR(dp->spdif_clk)) {
+ DRM_DEV_ERROR(dev, "cannot get spdif_clk\n");
+ return PTR_ERR(dp->spdif_clk);
+ }
+
+ dp->grf_clk = devm_clk_get(dev, "grf");
+ if (IS_ERR(dp->grf_clk)) {
+ DRM_DEV_ERROR(dev, "cannot get grf clk\n");
+ return PTR_ERR(dp->grf_clk);
+ }
+
+ dp->spdif_rst = devm_reset_control_get(dev, "spdif");
+ if (IS_ERR(dp->spdif_rst)) {
+ DRM_DEV_ERROR(dev, "no spdif reset control found\n");
+ return PTR_ERR(dp->spdif_rst);
+ }
+
+ dp->dptx_rst = devm_reset_control_get(dev, "dptx");
+ if (IS_ERR(dp->dptx_rst)) {
+ DRM_DEV_ERROR(dev, "no uphy reset control found\n");
+ return PTR_ERR(dp->dptx_rst);
+ }
+
+ dp->core_rst = devm_reset_control_get(dev, "core");
+ if (IS_ERR(dp->core_rst)) {
+ DRM_DEV_ERROR(dev, "no core reset control found\n");
+ return PTR_ERR(dp->core_rst);
+ }
+
+ dp->apb_rst = devm_reset_control_get(dev, "apb");
+ if (IS_ERR(dp->apb_rst)) {
+ DRM_DEV_ERROR(dev, "no apb reset control found\n");
+ return PTR_ERR(dp->apb_rst);
+ }
+
+ return 0;
+}
+
+static int cdn_dp_audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ struct audio_info audio = {
+ .sample_width = params->sample_width,
+ .sample_rate = params->sample_rate,
+ .channels = params->channels,
+ };
+ int ret;
+
+ mutex_lock(&dp->lock);
+ if (!dp->active) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ switch (daifmt->fmt) {
+ case HDMI_I2S:
+ audio.format = AFMT_I2S;
+ break;
+ case HDMI_SPDIF:
+ audio.format = AFMT_SPDIF;
+ break;
+ default:
+ DRM_DEV_ERROR(dev, "Invalid format %d\n", daifmt->fmt);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = cdn_dp_audio_config(dp, &audio);
+ if (!ret)
+ dp->audio_info = audio;
+
+out:
+ mutex_unlock(&dp->lock);
+ return ret;
+}
+
+static void cdn_dp_audio_shutdown(struct device *dev, void *data)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&dp->lock);
+ if (!dp->active)
+ goto out;
+
+ ret = cdn_dp_audio_stop(dp, &dp->audio_info);
+ if (!ret)
+ dp->audio_info.format = AFMT_UNUSED;
+out:
+ mutex_unlock(&dp->lock);
+}
+
+static int cdn_dp_audio_mute_stream(struct device *dev, void *data,
+ bool enable, int direction)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&dp->lock);
+ if (!dp->active) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ ret = cdn_dp_audio_mute(dp, enable);
+
+out:
+ mutex_unlock(&dp->lock);
+ return ret;
+}
+
+static int cdn_dp_audio_get_eld(struct device *dev, void *data,
+ u8 *buf, size_t len)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+
+ memcpy(buf, dp->connector.eld, min(sizeof(dp->connector.eld), len));
+
+ return 0;
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+ .hw_params = cdn_dp_audio_hw_params,
+ .audio_shutdown = cdn_dp_audio_shutdown,
+ .mute_stream = cdn_dp_audio_mute_stream,
+ .get_eld = cdn_dp_audio_get_eld,
+ .no_capture_mute = 1,
+};
+
+static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
+ struct device *dev)
+{
+ struct hdmi_codec_pdata codec_data = {
+ .i2s = 1,
+ .spdif = 1,
+ .ops = &audio_codec_ops,
+ .max_i2s_channels = 8,
+ };
+
+ dp->audio_pdev = platform_device_register_data(
+ dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
+ &codec_data, sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(dp->audio_pdev);
+}
+
+static int cdn_dp_request_firmware(struct cdn_dp_device *dp)
+{
+ int ret;
+ unsigned long timeout = jiffies + msecs_to_jiffies(CDN_FW_TIMEOUT_MS);
+ unsigned long sleep = 1000;
+
+ WARN_ON(!mutex_is_locked(&dp->lock));
+
+ if (dp->fw_loaded)
+ return 0;
+
+ /* Drop the lock before getting the firmware to avoid blocking boot */
+ mutex_unlock(&dp->lock);
+
+ while (time_before(jiffies, timeout)) {
+ ret = request_firmware(&dp->fw, CDN_DP_FIRMWARE, dp->dev);
+ if (ret == -ENOENT) {
+ msleep(sleep);
+ sleep *= 2;
+ continue;
+ } else if (ret) {
+ DRM_DEV_ERROR(dp->dev,
+ "failed to request firmware: %d\n", ret);
+ goto out;
+ }
+
+ dp->fw_loaded = true;
+ ret = 0;
+ goto out;
+ }
+
+ DRM_DEV_ERROR(dp->dev, "Timed out trying to load firmware\n");
+ ret = -ETIMEDOUT;
+out:
+ mutex_lock(&dp->lock);
+ return ret;
+}
+
+static void cdn_dp_pd_event_work(struct work_struct *work)
+{
+ struct cdn_dp_device *dp = container_of(work, struct cdn_dp_device,
+ event_work);
+ struct drm_connector *connector = &dp->connector;
+ enum drm_connector_status old_status;
+
+ int ret;
+
+ mutex_lock(&dp->lock);
+
+ if (dp->suspended)
+ goto out;
+
+ ret = cdn_dp_request_firmware(dp);
+ if (ret)
+ goto out;
+
+ dp->connected = true;
+
+ /* Not connected, notify userspace to disable the block */
+ if (!cdn_dp_connected_port(dp)) {
+ DRM_DEV_INFO(dp->dev, "Not connected. Disabling cdn\n");
+ dp->connected = false;
+
+ /* Connected but not enabled, enable the block */
+ } else if (!dp->active) {
+ DRM_DEV_INFO(dp->dev, "Connected, not enabled. Enabling cdn\n");
+ ret = cdn_dp_enable(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Enable dp failed %d\n", ret);
+ dp->connected = false;
+ }
+
+ /* Enabled and connected to a dongle without a sink, notify userspace */
+ } else if (!cdn_dp_check_sink_connection(dp)) {
+ DRM_DEV_INFO(dp->dev, "Connected without sink. Assert hpd\n");
+ dp->connected = false;
+
+ /* Enabled and connected with a sink, re-train if requested */
+ } else if (!cdn_dp_check_link_status(dp)) {
+ unsigned int rate = dp->max_rate;
+ unsigned int lanes = dp->max_lanes;
+ struct drm_display_mode *mode = &dp->mode;
+
+ DRM_DEV_INFO(dp->dev, "Connected with sink. Re-train link\n");
+ ret = cdn_dp_train_link(dp);
+ if (ret) {
+ dp->connected = false;
+ DRM_DEV_ERROR(dp->dev, "Train link failed %d\n", ret);
+ goto out;
+ }
+
+ /* If training result is changed, update the video config */
+ if (mode->clock &&
+ (rate != dp->max_rate || lanes != dp->max_lanes)) {
+ ret = cdn_dp_config_video(dp);
+ if (ret) {
+ dp->connected = false;
+ DRM_DEV_ERROR(dp->dev,
+ "Failed to config video %d\n",
+ ret);
+ }
+ }
+ }
+
+out:
+ mutex_unlock(&dp->lock);
+
+ old_status = connector->status;
+ connector->status = connector->funcs->detect(connector, false);
+ if (old_status != connector->status)
+ drm_kms_helper_hotplug_event(dp->drm_dev);
+}
+
+static int cdn_dp_pd_event(struct notifier_block *nb,
+ unsigned long event, void *priv)
+{
+ struct cdn_dp_port *port = container_of(nb, struct cdn_dp_port,
+ event_nb);
+ struct cdn_dp_device *dp = port->dp;
+
+ /*
+ * It would be nice to be able to just do the work inline right here.
+ * However, we need to make a bunch of calls that might sleep in order
+ * to turn on the block/phy, so use a worker instead.
+ */
+ schedule_work(&dp->event_work);
+
+ return NOTIFY_DONE;
+}
+
+static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+ struct cdn_dp_port *port;
+ struct drm_device *drm_dev = data;
+ int ret, i;
+
+ ret = cdn_dp_parse_dt(dp);
+ if (ret < 0)
+ return ret;
+
+ dp->drm_dev = drm_dev;
+ dp->connected = false;
+ dp->active = false;
+ dp->active_port = -1;
+ dp->fw_loaded = false;
+
+ INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
+
+ encoder = &dp->encoder;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+ dev->of_node);
+ DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder,
+ DRM_MODE_ENCODER_TMDS);
+ if (ret) {
+ DRM_ERROR("failed to initialize encoder with drm\n");
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &cdn_dp_encoder_helper_funcs);
+
+ connector = &dp->connector;
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ connector->dpms = DRM_MODE_DPMS_OFF;
+
+ ret = drm_connector_init(drm_dev, connector,
+ &cdn_dp_atomic_connector_funcs,
+ DRM_MODE_CONNECTOR_DisplayPort);
+ if (ret) {
+ DRM_ERROR("failed to initialize connector with drm\n");
+ goto err_free_encoder;
+ }
+
+ drm_connector_helper_add(connector, &cdn_dp_connector_helper_funcs);
+
+ ret = drm_connector_attach_encoder(connector, encoder);
+ if (ret) {
+ DRM_ERROR("failed to attach connector and encoder\n");
+ goto err_free_connector;
+ }
+
+ for (i = 0; i < dp->ports; i++) {
+ port = dp->port[i];
+
+ port->event_nb.notifier_call = cdn_dp_pd_event;
+ ret = devm_extcon_register_notifier(dp->dev, port->extcon,
+ EXTCON_DISP_DP,
+ &port->event_nb);
+ if (ret) {
+ DRM_DEV_ERROR(dev,
+ "register EXTCON_DISP_DP notifier err\n");
+ goto err_free_connector;
+ }
+ }
+
+ pm_runtime_enable(dev);
+
+ schedule_work(&dp->event_work);
+
+ return 0;
+
+err_free_connector:
+ drm_connector_cleanup(connector);
+err_free_encoder:
+ drm_encoder_cleanup(encoder);
+ return ret;
+}
+
+static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ struct drm_encoder *encoder = &dp->encoder;
+ struct drm_connector *connector = &dp->connector;
+
+ cancel_work_sync(&dp->event_work);
+ cdn_dp_encoder_disable(encoder);
+ encoder->funcs->destroy(encoder);
+ connector->funcs->destroy(connector);
+
+ pm_runtime_disable(dev);
+ if (dp->fw_loaded)
+ release_firmware(dp->fw);
+ kfree(dp->edid);
+ dp->edid = NULL;
+}
+
+static const struct component_ops cdn_dp_component_ops = {
+ .bind = cdn_dp_bind,
+ .unbind = cdn_dp_unbind,
+};
+
+static int cdn_dp_suspend(struct device *dev)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&dp->lock);
+ if (dp->active)
+ ret = cdn_dp_disable(dp);
+ dp->suspended = true;
+ mutex_unlock(&dp->lock);
+
+ return ret;
+}
+
+static __maybe_unused int cdn_dp_resume(struct device *dev)
+{
+ struct cdn_dp_device *dp = dev_get_drvdata(dev);
+
+ mutex_lock(&dp->lock);
+ dp->suspended = false;
+ if (dp->fw_loaded)
+ schedule_work(&dp->event_work);
+ mutex_unlock(&dp->lock);
+
+ return 0;
+}
+
+static int cdn_dp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct cdn_dp_data *dp_data;
+ struct cdn_dp_port *port;
+ struct cdn_dp_device *dp;
+ struct extcon_dev *extcon;
+ struct phy *phy;
+ int ret;
+ int i;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+ dp->dev = dev;
+
+ match = of_match_node(cdn_dp_dt_ids, pdev->dev.of_node);
+ dp_data = (struct cdn_dp_data *)match->data;
+
+ for (i = 0; i < dp_data->max_phy; i++) {
+ extcon = extcon_get_edev_by_phandle(dev, i);
+ phy = devm_of_phy_get_by_index(dev, dev->of_node, i);
+
+ if (PTR_ERR(extcon) == -EPROBE_DEFER ||
+ PTR_ERR(phy) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (IS_ERR(extcon) || IS_ERR(phy))
+ continue;
+
+ port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->extcon = extcon;
+ port->phy = phy;
+ port->dp = dp;
+ port->id = i;
+ dp->port[dp->ports++] = port;
+ }
+
+ if (!dp->ports) {
+ DRM_DEV_ERROR(dev, "missing extcon or phy\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&dp->lock);
+ dev_set_drvdata(dev, dp);
+
+ ret = cdn_dp_audio_codec_init(dp, dev);
+ if (ret)
+ return ret;
+
+ ret = component_add(dev, &cdn_dp_component_ops);
+ if (ret)
+ goto err_audio_deinit;
+
+ return 0;
+
+err_audio_deinit:
+ platform_device_unregister(dp->audio_pdev);
+ return ret;
+}
+
+static int cdn_dp_remove(struct platform_device *pdev)
+{
+ struct cdn_dp_device *dp = platform_get_drvdata(pdev);
+
+ platform_device_unregister(dp->audio_pdev);
+ cdn_dp_suspend(dp->dev);
+ component_del(&pdev->dev, &cdn_dp_component_ops);
+
+ return 0;
+}
+
+static void cdn_dp_shutdown(struct platform_device *pdev)
+{
+ struct cdn_dp_device *dp = platform_get_drvdata(pdev);
+
+ cdn_dp_suspend(dp->dev);
+}
+
+static const struct dev_pm_ops cdn_dp_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(cdn_dp_suspend,
+ cdn_dp_resume)
+};
+
+struct platform_driver cdn_dp_driver = {
+ .probe = cdn_dp_probe,
+ .remove = cdn_dp_remove,
+ .shutdown = cdn_dp_shutdown,
+ .driver = {
+ .name = "cdn-dp",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(cdn_dp_dt_ids),
+ .pm = &cdn_dp_pm_ops,
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
new file mode 100644
index 000000000..81ac9b658
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2016 Chris Zhong <zyw@rock-chips.com>
+ * Copyright (C) 2016 ROCKCHIP, Inc.
+ */
+
+#ifndef _CDN_DP_CORE_H
+#define _CDN_DP_CORE_H
+
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include "rockchip_drm_drv.h"
+
+#define MAX_PHY 2
+
+enum audio_format {
+ AFMT_I2S = 0,
+ AFMT_SPDIF = 1,
+ AFMT_UNUSED,
+};
+
+struct audio_info {
+ enum audio_format format;
+ int sample_rate;
+ int channels;
+ int sample_width;
+};
+
+enum vic_pxl_encoding_format {
+ PXL_RGB = 0x1,
+ YCBCR_4_4_4 = 0x2,
+ YCBCR_4_2_2 = 0x4,
+ YCBCR_4_2_0 = 0x8,
+ Y_ONLY = 0x10,
+};
+
+struct video_info {
+ bool h_sync_polarity;
+ bool v_sync_polarity;
+ bool interlaced;
+ int color_depth;
+ enum vic_pxl_encoding_format color_fmt;
+};
+
+struct cdn_firmware_header {
+ u32 size_bytes; /* size of the entire header+image(s) in bytes */
+ u32 header_size; /* size of just the header in bytes */
+ u32 iram_size; /* size of iram */
+ u32 dram_size; /* size of dram */
+};
+
+struct cdn_dp_port {
+ struct cdn_dp_device *dp;
+ struct notifier_block event_nb;
+ struct extcon_dev *extcon;
+ struct phy *phy;
+ u8 lanes;
+ bool phy_enabled;
+ u8 id;
+};
+
+struct cdn_dp_device {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct drm_display_mode mode;
+ struct platform_device *audio_pdev;
+ struct work_struct event_work;
+ struct edid *edid;
+
+ struct mutex lock;
+ bool connected;
+ bool active;
+ bool suspended;
+
+ const struct firmware *fw; /* cdn dp firmware */
+ unsigned int fw_version; /* cdn fw version */
+ bool fw_loaded;
+
+ void __iomem *regs;
+ struct regmap *grf;
+ struct clk *core_clk;
+ struct clk *pclk;
+ struct clk *spdif_clk;
+ struct clk *grf_clk;
+ struct reset_control *spdif_rst;
+ struct reset_control *dptx_rst;
+ struct reset_control *apb_rst;
+ struct reset_control *core_rst;
+ struct audio_info audio_info;
+ struct video_info video_info;
+ struct cdn_dp_port *port[MAX_PHY];
+ u8 ports;
+ u8 max_lanes;
+ unsigned int max_rate;
+ u8 lanes;
+ int active_port;
+
+ u8 dpcd[DP_RECEIVER_CAP_SIZE];
+ bool sink_has_audio;
+};
+#endif /* _CDN_DP_CORE_H */
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.c b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
new file mode 100644
index 000000000..33fb4d05c
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.c
@@ -0,0 +1,960 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/reset.h>
+
+#include "cdn-dp-core.h"
+#include "cdn-dp-reg.h"
+
+#define CDN_DP_SPDIF_CLK 200000000
+#define FW_ALIVE_TIMEOUT_US 1000000
+#define MAILBOX_RETRY_US 1000
+#define MAILBOX_TIMEOUT_US 5000000
+#define LINK_TRAINING_RETRY_MS 20
+#define LINK_TRAINING_TIMEOUT_MS 500
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, unsigned long clk)
+{
+ writel(clk / 1000000, dp->regs + SW_CLK_H);
+}
+
+void cdn_dp_clock_reset(struct cdn_dp_device *dp)
+{
+ u32 val;
+
+ val = DPTX_FRMR_DATA_CLK_RSTN_EN |
+ DPTX_FRMR_DATA_CLK_EN |
+ DPTX_PHY_DATA_RSTN_EN |
+ DPTX_PHY_DATA_CLK_EN |
+ DPTX_PHY_CHAR_RSTN_EN |
+ DPTX_PHY_CHAR_CLK_EN |
+ SOURCE_AUX_SYS_CLK_RSTN_EN |
+ SOURCE_AUX_SYS_CLK_EN |
+ DPTX_SYS_CLK_RSTN_EN |
+ DPTX_SYS_CLK_EN |
+ CFG_DPTX_VIF_CLK_RSTN_EN |
+ CFG_DPTX_VIF_CLK_EN;
+ writel(val, dp->regs + SOURCE_DPTX_CAR);
+
+ val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN;
+ writel(val, dp->regs + SOURCE_PHY_CAR);
+
+ val = SOURCE_PKT_SYS_RSTN_EN |
+ SOURCE_PKT_SYS_CLK_EN |
+ SOURCE_PKT_DATA_RSTN_EN |
+ SOURCE_PKT_DATA_CLK_EN;
+ writel(val, dp->regs + SOURCE_PKT_CAR);
+
+ val = SPDIF_CDR_CLK_RSTN_EN |
+ SPDIF_CDR_CLK_EN |
+ SOURCE_AIF_SYS_RSTN_EN |
+ SOURCE_AIF_SYS_CLK_EN |
+ SOURCE_AIF_CLK_RSTN_EN |
+ SOURCE_AIF_CLK_EN;
+ writel(val, dp->regs + SOURCE_AIF_CAR);
+
+ val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN |
+ SOURCE_CIPHER_SYS_CLK_EN |
+ SOURCE_CIPHER_CHAR_CLK_RSTN_EN |
+ SOURCE_CIPHER_CHAR_CLK_EN;
+ writel(val, dp->regs + SOURCE_CIPHER_CAR);
+
+ val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN |
+ SOURCE_CRYPTO_SYS_CLK_EN;
+ writel(val, dp->regs + SOURCE_CRYPTO_CAR);
+
+ /* enable Mailbox and PIF interrupt */
+ writel(0, dp->regs + APB_INT_MASK);
+}
+
+static int cdn_dp_mailbox_read(struct cdn_dp_device *dp)
+{
+ int val, ret;
+
+ ret = readx_poll_timeout(readl, dp->regs + MAILBOX_EMPTY_ADDR,
+ val, !val, MAILBOX_RETRY_US,
+ MAILBOX_TIMEOUT_US);
+ if (ret < 0)
+ return ret;
+
+ return readl(dp->regs + MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int cdp_dp_mailbox_write(struct cdn_dp_device *dp, u8 val)
+{
+ int ret, full;
+
+ ret = readx_poll_timeout(readl, dp->regs + MAILBOX_FULL_ADDR,
+ full, !full, MAILBOX_RETRY_US,
+ MAILBOX_TIMEOUT_US);
+ if (ret < 0)
+ return ret;
+
+ writel(val, dp->regs + MAILBOX0_WR_DATA);
+
+ return 0;
+}
+
+static int cdn_dp_mailbox_validate_receive(struct cdn_dp_device *dp,
+ u8 module_id, u8 opcode,
+ u16 req_size)
+{
+ u32 mbox_size, i;
+ u8 header[4];
+ int ret;
+
+ /* read the header of the message */
+ for (i = 0; i < 4; i++) {
+ ret = cdn_dp_mailbox_read(dp);
+ if (ret < 0)
+ return ret;
+
+ header[i] = ret;
+ }
+
+ mbox_size = (header[2] << 8) | header[3];
+
+ if (opcode != header[0] || module_id != header[1] ||
+ req_size != mbox_size) {
+ /*
+ * If the message in mailbox is not what we want, we need to
+ * clear the mailbox by reading its contents.
+ */
+ for (i = 0; i < mbox_size; i++)
+ if (cdn_dp_mailbox_read(dp) < 0)
+ break;
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cdn_dp_mailbox_read_receive(struct cdn_dp_device *dp,
+ u8 *buff, u16 buff_size)
+{
+ u32 i;
+ int ret;
+
+ for (i = 0; i < buff_size; i++) {
+ ret = cdn_dp_mailbox_read(dp);
+ if (ret < 0)
+ return ret;
+
+ buff[i] = ret;
+ }
+
+ return 0;
+}
+
+static int cdn_dp_mailbox_send(struct cdn_dp_device *dp, u8 module_id,
+ u8 opcode, u16 size, u8 *message)
+{
+ u8 header[4];
+ int ret, i;
+
+ header[0] = opcode;
+ header[1] = module_id;
+ header[2] = (size >> 8) & 0xff;
+ header[3] = size & 0xff;
+
+ for (i = 0; i < 4; i++) {
+ ret = cdp_dp_mailbox_write(dp, header[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < size; i++) {
+ ret = cdp_dp_mailbox_write(dp, message[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cdn_dp_reg_write(struct cdn_dp_device *dp, u16 addr, u32 val)
+{
+ u8 msg[6];
+
+ msg[0] = (addr >> 8) & 0xff;
+ msg[1] = addr & 0xff;
+ msg[2] = (val >> 24) & 0xff;
+ msg[3] = (val >> 16) & 0xff;
+ msg[4] = (val >> 8) & 0xff;
+ msg[5] = val & 0xff;
+ return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_REGISTER,
+ sizeof(msg), msg);
+}
+
+static int cdn_dp_reg_write_bit(struct cdn_dp_device *dp, u16 addr,
+ u8 start_bit, u8 bits_no, u32 val)
+{
+ u8 field[8];
+
+ field[0] = (addr >> 8) & 0xff;
+ field[1] = addr & 0xff;
+ field[2] = start_bit;
+ field[3] = bits_no;
+ field[4] = (val >> 24) & 0xff;
+ field[5] = (val >> 16) & 0xff;
+ field[6] = (val >> 8) & 0xff;
+ field[7] = val & 0xff;
+
+ return cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_FIELD,
+ sizeof(field), field);
+}
+
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len)
+{
+ u8 msg[5], reg[5];
+ int ret;
+
+ msg[0] = (len >> 8) & 0xff;
+ msg[1] = len & 0xff;
+ msg[2] = (addr >> 16) & 0xff;
+ msg[3] = (addr >> 8) & 0xff;
+ msg[4] = addr & 0xff;
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_DPCD,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_dpcd_read;
+
+ ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_DPCD,
+ sizeof(reg) + len);
+ if (ret)
+ goto err_dpcd_read;
+
+ ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+ if (ret)
+ goto err_dpcd_read;
+
+ ret = cdn_dp_mailbox_read_receive(dp, data, len);
+
+err_dpcd_read:
+ return ret;
+}
+
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value)
+{
+ u8 msg[6], reg[5];
+ int ret;
+
+ msg[0] = 0;
+ msg[1] = 1;
+ msg[2] = (addr >> 16) & 0xff;
+ msg[3] = (addr >> 8) & 0xff;
+ msg[4] = addr & 0xff;
+ msg[5] = value;
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_WRITE_DPCD,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_dpcd_write;
+
+ ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+ DPTX_WRITE_DPCD, sizeof(reg));
+ if (ret)
+ goto err_dpcd_write;
+
+ ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+ if (ret)
+ goto err_dpcd_write;
+
+ if (addr != (reg[2] << 16 | reg[3] << 8 | reg[4]))
+ ret = -EINVAL;
+
+err_dpcd_write:
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "dpcd write failed: %d\n", ret);
+ return ret;
+}
+
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+ u32 i_size, const u32 *d_mem, u32 d_size)
+{
+ u32 reg;
+ int i, ret;
+
+ /* reset ucpu before load firmware*/
+ writel(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
+ dp->regs + APB_CTRL);
+
+ for (i = 0; i < i_size; i += 4)
+ writel(*i_mem++, dp->regs + ADDR_IMEM + i);
+
+ for (i = 0; i < d_size; i += 4)
+ writel(*d_mem++, dp->regs + ADDR_DMEM + i);
+
+ /* un-reset ucpu */
+ writel(0, dp->regs + APB_CTRL);
+
+ /* check the keep alive register to make sure fw working */
+ ret = readx_poll_timeout(readl, dp->regs + KEEP_ALIVE,
+ reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dp->dev, "failed to loaded the FW reg = %x\n",
+ reg);
+ return -EINVAL;
+ }
+
+ reg = readl(dp->regs + VER_L) & 0xff;
+ dp->fw_version = reg;
+ reg = readl(dp->regs + VER_H) & 0xff;
+ dp->fw_version |= reg << 8;
+ reg = readl(dp->regs + VER_LIB_L_ADDR) & 0xff;
+ dp->fw_version |= reg << 16;
+ reg = readl(dp->regs + VER_LIB_H_ADDR) & 0xff;
+ dp->fw_version |= reg << 24;
+
+ DRM_DEV_DEBUG(dp->dev, "firmware version: %x\n", dp->fw_version);
+
+ return 0;
+}
+
+int cdn_dp_set_firmware_active(struct cdn_dp_device *dp, bool enable)
+{
+ u8 msg[5];
+ int ret, i;
+
+ msg[0] = GENERAL_MAIN_CONTROL;
+ msg[1] = MB_MODULE_ID_GENERAL;
+ msg[2] = 0;
+ msg[3] = 1;
+ msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
+
+ for (i = 0; i < sizeof(msg); i++) {
+ ret = cdp_dp_mailbox_write(dp, msg[i]);
+ if (ret)
+ goto err_set_firmware_active;
+ }
+
+ /* read the firmware state */
+ for (i = 0; i < sizeof(msg); i++) {
+ ret = cdn_dp_mailbox_read(dp);
+ if (ret < 0)
+ goto err_set_firmware_active;
+
+ msg[i] = ret;
+ }
+
+ ret = 0;
+
+err_set_firmware_active:
+ if (ret < 0)
+ DRM_DEV_ERROR(dp->dev, "set firmware active failed\n");
+ return ret;
+}
+
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes, bool flip)
+{
+ u8 msg[8];
+ int ret;
+
+ msg[0] = CDN_DP_MAX_LINK_RATE;
+ msg[1] = lanes | SCRAMBLER_EN;
+ msg[2] = VOLTAGE_LEVEL_2;
+ msg[3] = PRE_EMPHASIS_LEVEL_3;
+ msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
+ msg[5] = FAST_LT_NOT_SUPPORT;
+ msg[6] = flip ? LANE_MAPPING_FLIPPED : LANE_MAPPING_NORMAL;
+ msg[7] = ENHANCED;
+
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+ DPTX_SET_HOST_CAPABILITIES,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_set_host_cap;
+
+ ret = cdn_dp_reg_write(dp, DP_AUX_SWAP_INVERSION_CONTROL,
+ AUX_HOST_INVERT);
+
+err_set_host_cap:
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "set host cap failed: %d\n", ret);
+ return ret;
+}
+
+int cdn_dp_event_config(struct cdn_dp_device *dp)
+{
+ u8 msg[5];
+ int ret;
+
+ memset(msg, 0, sizeof(msg));
+
+ msg[0] = DPTX_EVENT_ENABLE_HPD | DPTX_EVENT_ENABLE_TRAINING;
+
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_ENABLE_EVENT,
+ sizeof(msg), msg);
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "set event config failed: %d\n", ret);
+
+ return ret;
+}
+
+u32 cdn_dp_get_event(struct cdn_dp_device *dp)
+{
+ return readl(dp->regs + SW_EVENTS0);
+}
+
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp)
+{
+ u8 status;
+ int ret;
+
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_HPD_STATE,
+ 0, NULL);
+ if (ret)
+ goto err_get_hpd;
+
+ ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+ DPTX_HPD_STATE, sizeof(status));
+ if (ret)
+ goto err_get_hpd;
+
+ ret = cdn_dp_mailbox_read_receive(dp, &status, sizeof(status));
+ if (ret)
+ goto err_get_hpd;
+
+ return status;
+
+err_get_hpd:
+ DRM_DEV_ERROR(dp->dev, "get hpd status failed: %d\n", ret);
+ return ret;
+}
+
+int cdn_dp_get_edid_block(void *data, u8 *edid,
+ unsigned int block, size_t length)
+{
+ struct cdn_dp_device *dp = data;
+ u8 msg[2], reg[2], i;
+ int ret;
+
+ for (i = 0; i < 4; i++) {
+ msg[0] = block / 2;
+ msg[1] = block % 2;
+
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
+ sizeof(msg), msg);
+ if (ret)
+ continue;
+
+ ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+ DPTX_GET_EDID,
+ sizeof(reg) + length);
+ if (ret)
+ continue;
+
+ ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+ if (ret)
+ continue;
+
+ ret = cdn_dp_mailbox_read_receive(dp, edid, length);
+ if (ret)
+ continue;
+
+ if (reg[0] == length && reg[1] == block / 2)
+ break;
+ }
+
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "get block[%d] edid failed: %d\n", block,
+ ret);
+
+ return ret;
+}
+
+static int cdn_dp_training_start(struct cdn_dp_device *dp)
+{
+ unsigned long timeout;
+ u8 msg, event[2];
+ int ret;
+
+ msg = LINK_TRAINING_RUN;
+
+ /* start training */
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_TRAINING_CONTROL,
+ sizeof(msg), &msg);
+ if (ret)
+ goto err_training_start;
+
+ timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
+ while (time_before(jiffies, timeout)) {
+ msleep(LINK_TRAINING_RETRY_MS);
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_EVENT, 0, NULL);
+ if (ret)
+ goto err_training_start;
+
+ ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_EVENT,
+ sizeof(event));
+ if (ret)
+ goto err_training_start;
+
+ ret = cdn_dp_mailbox_read_receive(dp, event, sizeof(event));
+ if (ret)
+ goto err_training_start;
+
+ if (event[1] & EQ_PHASE_FINISHED)
+ return 0;
+ }
+
+ ret = -ETIMEDOUT;
+
+err_training_start:
+ DRM_DEV_ERROR(dp->dev, "training failed: %d\n", ret);
+ return ret;
+}
+
+static int cdn_dp_get_training_status(struct cdn_dp_device *dp)
+{
+ u8 status[10];
+ int ret;
+
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_READ_LINK_STAT,
+ 0, NULL);
+ if (ret)
+ goto err_get_training_status;
+
+ ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_LINK_STAT,
+ sizeof(status));
+ if (ret)
+ goto err_get_training_status;
+
+ ret = cdn_dp_mailbox_read_receive(dp, status, sizeof(status));
+ if (ret)
+ goto err_get_training_status;
+
+ dp->max_rate = drm_dp_bw_code_to_link_rate(status[0]);
+ dp->max_lanes = status[1];
+
+err_get_training_status:
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "get training status failed: %d\n", ret);
+ return ret;
+}
+
+int cdn_dp_train_link(struct cdn_dp_device *dp)
+{
+ int ret;
+
+ ret = cdn_dp_training_start(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to start training %d\n", ret);
+ return ret;
+ }
+
+ ret = cdn_dp_get_training_status(dp);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "Failed to get training stat %d\n", ret);
+ return ret;
+ }
+
+ DRM_DEV_DEBUG_KMS(dp->dev, "rate:0x%x, lanes:%d\n", dp->max_rate,
+ dp->max_lanes);
+ return ret;
+}
+
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active)
+{
+ u8 msg;
+ int ret;
+
+ msg = !!active;
+
+ ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_SET_VIDEO,
+ sizeof(msg), &msg);
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "set video status failed: %d\n", ret);
+
+ return ret;
+}
+
+static int cdn_dp_get_msa_misc(struct video_info *video,
+ struct drm_display_mode *mode)
+{
+ u32 msa_misc;
+ u8 val[2] = {0};
+
+ switch (video->color_fmt) {
+ case PXL_RGB:
+ case Y_ONLY:
+ val[0] = 0;
+ break;
+ /* set YUV default color space conversion to BT601 */
+ case YCBCR_4_4_4:
+ val[0] = 6 + BT_601 * 8;
+ break;
+ case YCBCR_4_2_2:
+ val[0] = 5 + BT_601 * 8;
+ break;
+ case YCBCR_4_2_0:
+ val[0] = 5;
+ break;
+ }
+
+ switch (video->color_depth) {
+ case 6:
+ val[1] = 0;
+ break;
+ case 8:
+ val[1] = 1;
+ break;
+ case 10:
+ val[1] = 2;
+ break;
+ case 12:
+ val[1] = 3;
+ break;
+ case 16:
+ val[1] = 4;
+ break;
+ }
+
+ msa_misc = 2 * val[0] + 32 * val[1] +
+ ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
+
+ return msa_misc;
+}
+
+int cdn_dp_config_video(struct cdn_dp_device *dp)
+{
+ struct video_info *video = &dp->video_info;
+ struct drm_display_mode *mode = &dp->mode;
+ u64 symbol;
+ u32 val, link_rate, rem;
+ u8 bit_per_pix, tu_size_reg = TU_SIZE;
+ int ret;
+
+ bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
+ (video->color_depth * 2) : (video->color_depth * 3);
+
+ link_rate = dp->max_rate / 1000;
+
+ ret = cdn_dp_reg_write(dp, BND_HSYNC2VSYNC, VIF_BYPASS_INTERLACE);
+ if (ret)
+ goto err_config_video;
+
+ ret = cdn_dp_reg_write(dp, HSYNC2VSYNC_POL_CTRL, 0);
+ if (ret)
+ goto err_config_video;
+
+ /*
+ * get a best tu_size and valid symbol:
+ * 1. chose Lclk freq(162Mhz, 270Mhz, 540Mhz), set TU to 32
+ * 2. calculate VS(valid symbol) = TU * Pclk * Bpp / (Lclk * Lanes)
+ * 3. if VS > *.85 or VS < *.1 or VS < 2 or TU < VS + 4, then set
+ * TU += 2 and repeat 2nd step.
+ */
+ do {
+ tu_size_reg += 2;
+ symbol = (u64)tu_size_reg * mode->clock * bit_per_pix;
+ do_div(symbol, dp->max_lanes * link_rate * 8);
+ rem = do_div(symbol, 1000);
+ if (tu_size_reg > 64) {
+ ret = -EINVAL;
+ DRM_DEV_ERROR(dp->dev,
+ "tu error, clk:%d, lanes:%d, rate:%d\n",
+ mode->clock, dp->max_lanes, link_rate);
+ goto err_config_video;
+ }
+ } while ((symbol <= 1) || (tu_size_reg - symbol < 4) ||
+ (rem > 850) || (rem < 100));
+
+ val = symbol + (tu_size_reg << 8);
+ val |= TU_CNT_RST_EN;
+ ret = cdn_dp_reg_write(dp, DP_FRAMER_TU, val);
+ if (ret)
+ goto err_config_video;
+
+ /* set the FIFO Buffer size */
+ val = div_u64(mode->clock * (symbol + 1), 1000) + link_rate;
+ val /= (dp->max_lanes * link_rate);
+ val = div_u64(8 * (symbol + 1), bit_per_pix) - val;
+ val += 2;
+ ret = cdn_dp_reg_write(dp, DP_VC_TABLE(15), val);
+
+ switch (video->color_depth) {
+ case 6:
+ val = BCS_6;
+ break;
+ case 8:
+ val = BCS_8;
+ break;
+ case 10:
+ val = BCS_10;
+ break;
+ case 12:
+ val = BCS_12;
+ break;
+ case 16:
+ val = BCS_16;
+ break;
+ }
+
+ val += video->color_fmt << 8;
+ ret = cdn_dp_reg_write(dp, DP_FRAMER_PXL_REPR, val);
+ if (ret)
+ goto err_config_video;
+
+ val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
+ val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
+ ret = cdn_dp_reg_write(dp, DP_FRAMER_SP, val);
+ if (ret)
+ goto err_config_video;
+
+ val = (mode->hsync_start - mode->hdisplay) << 16;
+ val |= mode->htotal - mode->hsync_end;
+ ret = cdn_dp_reg_write(dp, DP_FRONT_BACK_PORCH, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->hdisplay * bit_per_pix / 8;
+ ret = cdn_dp_reg_write(dp, DP_BYTE_COUNT, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
+ ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_0, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->hsync_end - mode->hsync_start;
+ val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
+ ret = cdn_dp_reg_write(dp, MSA_HORIZONTAL_1, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vtotal;
+ val |= (mode->vtotal - mode->vsync_start) << 16;
+ ret = cdn_dp_reg_write(dp, MSA_VERTICAL_0, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vsync_end - mode->vsync_start;
+ val |= (mode->vdisplay << 16) | (video->v_sync_polarity << 15);
+ ret = cdn_dp_reg_write(dp, MSA_VERTICAL_1, val);
+ if (ret)
+ goto err_config_video;
+
+ val = cdn_dp_get_msa_misc(video, mode);
+ ret = cdn_dp_reg_write(dp, MSA_MISC, val);
+ if (ret)
+ goto err_config_video;
+
+ ret = cdn_dp_reg_write(dp, STREAM_CONFIG, 1);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->hsync_end - mode->hsync_start;
+ val |= mode->hdisplay << 16;
+ ret = cdn_dp_reg_write(dp, DP_HORIZONTAL, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vdisplay;
+ val |= (mode->vtotal - mode->vsync_start) << 16;
+ ret = cdn_dp_reg_write(dp, DP_VERTICAL_0, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vtotal;
+ ret = cdn_dp_reg_write(dp, DP_VERTICAL_1, val);
+ if (ret)
+ goto err_config_video;
+
+ ret = cdn_dp_reg_write_bit(dp, DP_VB_ID, 2, 1, 0);
+
+err_config_video:
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "config video failed: %d\n", ret);
+ return ret;
+}
+
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+ int ret;
+
+ ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, 0);
+ if (ret) {
+ DRM_DEV_ERROR(dp->dev, "audio stop failed: %d\n", ret);
+ return ret;
+ }
+
+ writel(0, dp->regs + SPDIF_CTRL_ADDR);
+
+ /* clearn the audio config and reset */
+ writel(0, dp->regs + AUDIO_SRC_CNTL);
+ writel(0, dp->regs + AUDIO_SRC_CNFG);
+ writel(AUDIO_SW_RST, dp->regs + AUDIO_SRC_CNTL);
+ writel(0, dp->regs + AUDIO_SRC_CNTL);
+
+ /* reset smpl2pckt component */
+ writel(0, dp->regs + SMPL2PKT_CNTL);
+ writel(AUDIO_SW_RST, dp->regs + SMPL2PKT_CNTL);
+ writel(0, dp->regs + SMPL2PKT_CNTL);
+
+ /* reset FIFO */
+ writel(AUDIO_SW_RST, dp->regs + FIFO_CNTL);
+ writel(0, dp->regs + FIFO_CNTL);
+
+ if (audio->format == AFMT_SPDIF)
+ clk_disable_unprepare(dp->spdif_clk);
+
+ return 0;
+}
+
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable)
+{
+ int ret;
+
+ ret = cdn_dp_reg_write_bit(dp, DP_VB_ID, 4, 1, enable);
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "audio mute failed: %d\n", ret);
+
+ return ret;
+}
+
+static void cdn_dp_audio_config_i2s(struct cdn_dp_device *dp,
+ struct audio_info *audio)
+{
+ int sub_pckt_num = 1, i2s_port_en_val = 0xf, i;
+ u32 val;
+
+ if (audio->channels == 2) {
+ if (dp->max_lanes == 1)
+ sub_pckt_num = 2;
+ else
+ sub_pckt_num = 4;
+
+ i2s_port_en_val = 1;
+ } else if (audio->channels == 4) {
+ i2s_port_en_val = 3;
+ }
+
+ writel(0x0, dp->regs + SPDIF_CTRL_ADDR);
+
+ writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+ val = MAX_NUM_CH(audio->channels);
+ val |= NUM_OF_I2S_PORTS(audio->channels);
+ val |= AUDIO_TYPE_LPCM;
+ val |= CFG_SUB_PCKT_NUM(sub_pckt_num);
+ writel(val, dp->regs + SMPL2PKT_CNFG);
+
+ if (audio->sample_width == 16)
+ val = 0;
+ else if (audio->sample_width == 24)
+ val = 1 << 9;
+ else
+ val = 2 << 9;
+
+ val |= AUDIO_CH_NUM(audio->channels);
+ val |= I2S_DEC_PORT_EN(i2s_port_en_val);
+ val |= TRANS_SMPL_WIDTH_32;
+ writel(val, dp->regs + AUDIO_SRC_CNFG);
+
+ for (i = 0; i < (audio->channels + 1) / 2; i++) {
+ if (audio->sample_width == 16)
+ val = (0x02 << 8) | (0x02 << 20);
+ else if (audio->sample_width == 24)
+ val = (0x0b << 8) | (0x0b << 20);
+
+ val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
+ writel(val, dp->regs + STTS_BIT_CH(i));
+ }
+
+ switch (audio->sample_rate) {
+ case 32000:
+ val = SAMPLING_FREQ(3) |
+ ORIGINAL_SAMP_FREQ(0xc);
+ break;
+ case 44100:
+ val = SAMPLING_FREQ(0) |
+ ORIGINAL_SAMP_FREQ(0xf);
+ break;
+ case 48000:
+ val = SAMPLING_FREQ(2) |
+ ORIGINAL_SAMP_FREQ(0xd);
+ break;
+ case 88200:
+ val = SAMPLING_FREQ(8) |
+ ORIGINAL_SAMP_FREQ(0x7);
+ break;
+ case 96000:
+ val = SAMPLING_FREQ(0xa) |
+ ORIGINAL_SAMP_FREQ(5);
+ break;
+ case 176400:
+ val = SAMPLING_FREQ(0xc) |
+ ORIGINAL_SAMP_FREQ(3);
+ break;
+ case 192000:
+ val = SAMPLING_FREQ(0xe) |
+ ORIGINAL_SAMP_FREQ(1);
+ break;
+ }
+ val |= 4;
+ writel(val, dp->regs + COM_CH_STTS_BITS);
+
+ writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
+ writel(I2S_DEC_START, dp->regs + AUDIO_SRC_CNTL);
+}
+
+static void cdn_dp_audio_config_spdif(struct cdn_dp_device *dp)
+{
+ u32 val;
+
+ writel(SYNC_WR_TO_CH_ZERO, dp->regs + FIFO_CNTL);
+
+ val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4);
+ writel(val, dp->regs + SMPL2PKT_CNFG);
+ writel(SMPL2PKT_EN, dp->regs + SMPL2PKT_CNTL);
+
+ val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+ writel(val, dp->regs + SPDIF_CTRL_ADDR);
+
+ clk_prepare_enable(dp->spdif_clk);
+ clk_set_rate(dp->spdif_clk, CDN_DP_SPDIF_CLK);
+}
+
+int cdn_dp_audio_config(struct cdn_dp_device *dp, struct audio_info *audio)
+{
+ int ret;
+
+ /* reset the spdif clk before config */
+ if (audio->format == AFMT_SPDIF) {
+ reset_control_assert(dp->spdif_rst);
+ reset_control_deassert(dp->spdif_rst);
+ }
+
+ ret = cdn_dp_reg_write(dp, CM_LANE_CTRL, LANE_REF_CYC);
+ if (ret)
+ goto err_audio_config;
+
+ ret = cdn_dp_reg_write(dp, CM_CTRL, 0);
+ if (ret)
+ goto err_audio_config;
+
+ if (audio->format == AFMT_I2S)
+ cdn_dp_audio_config_i2s(dp, audio);
+ else if (audio->format == AFMT_SPDIF)
+ cdn_dp_audio_config_spdif(dp);
+
+ ret = cdn_dp_reg_write(dp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN);
+
+err_audio_config:
+ if (ret)
+ DRM_DEV_ERROR(dp->dev, "audio config failed: %d\n", ret);
+ return ret;
+}
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-reg.h b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
new file mode 100644
index 000000000..441248b7a
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/cdn-dp-reg.h
@@ -0,0 +1,474 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ */
+
+#ifndef _CDN_DP_REG_H
+#define _CDN_DP_REG_H
+
+#include <linux/bitops.h>
+
+#define ADDR_IMEM 0x10000
+#define ADDR_DMEM 0x20000
+
+/* APB CFG addr */
+#define APB_CTRL 0
+#define XT_INT_CTRL 0x04
+#define MAILBOX_FULL_ADDR 0x08
+#define MAILBOX_EMPTY_ADDR 0x0c
+#define MAILBOX0_WR_DATA 0x10
+#define MAILBOX0_RD_DATA 0x14
+#define KEEP_ALIVE 0x18
+#define VER_L 0x1c
+#define VER_H 0x20
+#define VER_LIB_L_ADDR 0x24
+#define VER_LIB_H_ADDR 0x28
+#define SW_DEBUG_L 0x2c
+#define SW_DEBUG_H 0x30
+#define MAILBOX_INT_MASK 0x34
+#define MAILBOX_INT_STATUS 0x38
+#define SW_CLK_L 0x3c
+#define SW_CLK_H 0x40
+#define SW_EVENTS0 0x44
+#define SW_EVENTS1 0x48
+#define SW_EVENTS2 0x4c
+#define SW_EVENTS3 0x50
+#define XT_OCD_CTRL 0x60
+#define APB_INT_MASK 0x6c
+#define APB_STATUS_MASK 0x70
+
+/* audio decoder addr */
+#define AUDIO_SRC_CNTL 0x30000
+#define AUDIO_SRC_CNFG 0x30004
+#define COM_CH_STTS_BITS 0x30008
+#define STTS_BIT_CH(x) (0x3000c + ((x) << 2))
+#define SPDIF_CTRL_ADDR 0x3004c
+#define SPDIF_CH1_CS_3100_ADDR 0x30050
+#define SPDIF_CH1_CS_6332_ADDR 0x30054
+#define SPDIF_CH1_CS_9564_ADDR 0x30058
+#define SPDIF_CH1_CS_12796_ADDR 0x3005c
+#define SPDIF_CH1_CS_159128_ADDR 0x30060
+#define SPDIF_CH1_CS_191160_ADDR 0x30064
+#define SPDIF_CH2_CS_3100_ADDR 0x30068
+#define SPDIF_CH2_CS_6332_ADDR 0x3006c
+#define SPDIF_CH2_CS_9564_ADDR 0x30070
+#define SPDIF_CH2_CS_12796_ADDR 0x30074
+#define SPDIF_CH2_CS_159128_ADDR 0x30078
+#define SPDIF_CH2_CS_191160_ADDR 0x3007c
+#define SMPL2PKT_CNTL 0x30080
+#define SMPL2PKT_CNFG 0x30084
+#define FIFO_CNTL 0x30088
+#define FIFO_STTS 0x3008c
+
+/* source pif addr */
+#define SOURCE_PIF_WR_ADDR 0x30800
+#define SOURCE_PIF_WR_REQ 0x30804
+#define SOURCE_PIF_RD_ADDR 0x30808
+#define SOURCE_PIF_RD_REQ 0x3080c
+#define SOURCE_PIF_DATA_WR 0x30810
+#define SOURCE_PIF_DATA_RD 0x30814
+#define SOURCE_PIF_FIFO1_FLUSH 0x30818
+#define SOURCE_PIF_FIFO2_FLUSH 0x3081c
+#define SOURCE_PIF_STATUS 0x30820
+#define SOURCE_PIF_INTERRUPT_SOURCE 0x30824
+#define SOURCE_PIF_INTERRUPT_MASK 0x30828
+#define SOURCE_PIF_PKT_ALLOC_REG 0x3082c
+#define SOURCE_PIF_PKT_ALLOC_WR_EN 0x30830
+#define SOURCE_PIF_SW_RESET 0x30834
+
+/* bellow registers need access by mailbox */
+/* source car addr */
+#define SOURCE_HDTX_CAR 0x0900
+#define SOURCE_DPTX_CAR 0x0904
+#define SOURCE_PHY_CAR 0x0908
+#define SOURCE_CEC_CAR 0x090c
+#define SOURCE_CBUS_CAR 0x0910
+#define SOURCE_PKT_CAR 0x0918
+#define SOURCE_AIF_CAR 0x091c
+#define SOURCE_CIPHER_CAR 0x0920
+#define SOURCE_CRYPTO_CAR 0x0924
+
+/* clock meters addr */
+#define CM_CTRL 0x0a00
+#define CM_I2S_CTRL 0x0a04
+#define CM_SPDIF_CTRL 0x0a08
+#define CM_VID_CTRL 0x0a0c
+#define CM_LANE_CTRL 0x0a10
+#define I2S_NM_STABLE 0x0a14
+#define I2S_NCTS_STABLE 0x0a18
+#define SPDIF_NM_STABLE 0x0a1c
+#define SPDIF_NCTS_STABLE 0x0a20
+#define NMVID_MEAS_STABLE 0x0a24
+#define I2S_MEAS 0x0a40
+#define SPDIF_MEAS 0x0a80
+#define NMVID_MEAS 0x0ac0
+
+/* source vif addr */
+#define BND_HSYNC2VSYNC 0x0b00
+#define HSYNC2VSYNC_F1_L1 0x0b04
+#define HSYNC2VSYNC_F2_L1 0x0b08
+#define HSYNC2VSYNC_STATUS 0x0b0c
+#define HSYNC2VSYNC_POL_CTRL 0x0b10
+
+/* dptx phy addr */
+#define DP_TX_PHY_CONFIG_REG 0x2000
+#define DP_TX_PHY_SW_RESET 0x2004
+#define DP_TX_PHY_SCRAMBLER_SEED 0x2008
+#define DP_TX_PHY_TRAINING_01_04 0x200c
+#define DP_TX_PHY_TRAINING_05_08 0x2010
+#define DP_TX_PHY_TRAINING_09_10 0x2014
+#define TEST_COR 0x23fc
+
+/* dptx hpd addr */
+#define HPD_IRQ_DET_MIN_TIMER 0x2100
+#define HPD_IRQ_DET_MAX_TIMER 0x2104
+#define HPD_UNPLGED_DET_MIN_TIMER 0x2108
+#define HPD_STABLE_TIMER 0x210c
+#define HPD_FILTER_TIMER 0x2110
+#define HPD_EVENT_MASK 0x211c
+#define HPD_EVENT_DET 0x2120
+
+/* dpyx framer addr */
+#define DP_FRAMER_GLOBAL_CONFIG 0x2200
+#define DP_SW_RESET 0x2204
+#define DP_FRAMER_TU 0x2208
+#define DP_FRAMER_PXL_REPR 0x220c
+#define DP_FRAMER_SP 0x2210
+#define AUDIO_PACK_CONTROL 0x2214
+#define DP_VC_TABLE(x) (0x2218 + ((x) << 2))
+#define DP_VB_ID 0x2258
+#define DP_MTPH_LVP_CONTROL 0x225c
+#define DP_MTPH_SYMBOL_VALUES 0x2260
+#define DP_MTPH_ECF_CONTROL 0x2264
+#define DP_MTPH_ACT_CONTROL 0x2268
+#define DP_MTPH_STATUS 0x226c
+#define DP_INTERRUPT_SOURCE 0x2270
+#define DP_INTERRUPT_MASK 0x2274
+#define DP_FRONT_BACK_PORCH 0x2278
+#define DP_BYTE_COUNT 0x227c
+
+/* dptx stream addr */
+#define MSA_HORIZONTAL_0 0x2280
+#define MSA_HORIZONTAL_1 0x2284
+#define MSA_VERTICAL_0 0x2288
+#define MSA_VERTICAL_1 0x228c
+#define MSA_MISC 0x2290
+#define STREAM_CONFIG 0x2294
+#define AUDIO_PACK_STATUS 0x2298
+#define VIF_STATUS 0x229c
+#define PCK_STUFF_STATUS_0 0x22a0
+#define PCK_STUFF_STATUS_1 0x22a4
+#define INFO_PACK_STATUS 0x22a8
+#define RATE_GOVERNOR_STATUS 0x22ac
+#define DP_HORIZONTAL 0x22b0
+#define DP_VERTICAL_0 0x22b4
+#define DP_VERTICAL_1 0x22b8
+#define DP_BLOCK_SDP 0x22bc
+
+/* dptx glbl addr */
+#define DPTX_LANE_EN 0x2300
+#define DPTX_ENHNCD 0x2304
+#define DPTX_INT_MASK 0x2308
+#define DPTX_INT_STATUS 0x230c
+
+/* dp aux addr */
+#define DP_AUX_HOST_CONTROL 0x2800
+#define DP_AUX_INTERRUPT_SOURCE 0x2804
+#define DP_AUX_INTERRUPT_MASK 0x2808
+#define DP_AUX_SWAP_INVERSION_CONTROL 0x280c
+#define DP_AUX_SEND_NACK_TRANSACTION 0x2810
+#define DP_AUX_CLEAR_RX 0x2814
+#define DP_AUX_CLEAR_TX 0x2818
+#define DP_AUX_TIMER_STOP 0x281c
+#define DP_AUX_TIMER_CLEAR 0x2820
+#define DP_AUX_RESET_SW 0x2824
+#define DP_AUX_DIVIDE_2M 0x2828
+#define DP_AUX_TX_PREACHARGE_LENGTH 0x282c
+#define DP_AUX_FREQUENCY_1M_MAX 0x2830
+#define DP_AUX_FREQUENCY_1M_MIN 0x2834
+#define DP_AUX_RX_PRE_MIN 0x2838
+#define DP_AUX_RX_PRE_MAX 0x283c
+#define DP_AUX_TIMER_PRESET 0x2840
+#define DP_AUX_NACK_FORMAT 0x2844
+#define DP_AUX_TX_DATA 0x2848
+#define DP_AUX_RX_DATA 0x284c
+#define DP_AUX_TX_STATUS 0x2850
+#define DP_AUX_RX_STATUS 0x2854
+#define DP_AUX_RX_CYCLE_COUNTER 0x2858
+#define DP_AUX_MAIN_STATES 0x285c
+#define DP_AUX_MAIN_TIMER 0x2860
+#define DP_AUX_AFE_OUT 0x2864
+
+/* crypto addr */
+#define CRYPTO_HDCP_REVISION 0x5800
+#define HDCP_CRYPTO_CONFIG 0x5804
+#define CRYPTO_INTERRUPT_SOURCE 0x5808
+#define CRYPTO_INTERRUPT_MASK 0x580c
+#define CRYPTO22_CONFIG 0x5818
+#define CRYPTO22_STATUS 0x581c
+#define SHA_256_DATA_IN 0x583c
+#define SHA_256_DATA_OUT_(x) (0x5850 + ((x) << 2))
+#define AES_32_KEY_(x) (0x5870 + ((x) << 2))
+#define AES_32_DATA_IN 0x5880
+#define AES_32_DATA_OUT_(x) (0x5884 + ((x) << 2))
+#define CRYPTO14_CONFIG 0x58a0
+#define CRYPTO14_STATUS 0x58a4
+#define CRYPTO14_PRNM_OUT 0x58a8
+#define CRYPTO14_KM_0 0x58ac
+#define CRYPTO14_KM_1 0x58b0
+#define CRYPTO14_AN_0 0x58b4
+#define CRYPTO14_AN_1 0x58b8
+#define CRYPTO14_YOUR_KSV_0 0x58bc
+#define CRYPTO14_YOUR_KSV_1 0x58c0
+#define CRYPTO14_MI_0 0x58c4
+#define CRYPTO14_MI_1 0x58c8
+#define CRYPTO14_TI_0 0x58cc
+#define CRYPTO14_KI_0 0x58d0
+#define CRYPTO14_KI_1 0x58d4
+#define CRYPTO14_BLOCKS_NUM 0x58d8
+#define CRYPTO14_KEY_MEM_DATA_0 0x58dc
+#define CRYPTO14_KEY_MEM_DATA_1 0x58e0
+#define CRYPTO14_SHA1_MSG_DATA 0x58e4
+#define CRYPTO14_SHA1_V_VALUE_(x) (0x58e8 + ((x) << 2))
+#define TRNG_CTRL 0x58fc
+#define TRNG_DATA_RDY 0x5900
+#define TRNG_DATA 0x5904
+
+/* cipher addr */
+#define HDCP_REVISION 0x60000
+#define INTERRUPT_SOURCE 0x60004
+#define INTERRUPT_MASK 0x60008
+#define HDCP_CIPHER_CONFIG 0x6000c
+#define AES_128_KEY_0 0x60010
+#define AES_128_KEY_1 0x60014
+#define AES_128_KEY_2 0x60018
+#define AES_128_KEY_3 0x6001c
+#define AES_128_RANDOM_0 0x60020
+#define AES_128_RANDOM_1 0x60024
+#define CIPHER14_KM_0 0x60028
+#define CIPHER14_KM_1 0x6002c
+#define CIPHER14_STATUS 0x60030
+#define CIPHER14_RI_PJ_STATUS 0x60034
+#define CIPHER_MODE 0x60038
+#define CIPHER14_AN_0 0x6003c
+#define CIPHER14_AN_1 0x60040
+#define CIPHER22_AUTH 0x60044
+#define CIPHER14_R0_DP_STATUS 0x60048
+#define CIPHER14_BOOTSTRAP 0x6004c
+
+#define DPTX_FRMR_DATA_CLK_RSTN_EN BIT(11)
+#define DPTX_FRMR_DATA_CLK_EN BIT(10)
+#define DPTX_PHY_DATA_RSTN_EN BIT(9)
+#define DPTX_PHY_DATA_CLK_EN BIT(8)
+#define DPTX_PHY_CHAR_RSTN_EN BIT(7)
+#define DPTX_PHY_CHAR_CLK_EN BIT(6)
+#define SOURCE_AUX_SYS_CLK_RSTN_EN BIT(5)
+#define SOURCE_AUX_SYS_CLK_EN BIT(4)
+#define DPTX_SYS_CLK_RSTN_EN BIT(3)
+#define DPTX_SYS_CLK_EN BIT(2)
+#define CFG_DPTX_VIF_CLK_RSTN_EN BIT(1)
+#define CFG_DPTX_VIF_CLK_EN BIT(0)
+
+#define SOURCE_PHY_RSTN_EN BIT(1)
+#define SOURCE_PHY_CLK_EN BIT(0)
+
+#define SOURCE_PKT_SYS_RSTN_EN BIT(3)
+#define SOURCE_PKT_SYS_CLK_EN BIT(2)
+#define SOURCE_PKT_DATA_RSTN_EN BIT(1)
+#define SOURCE_PKT_DATA_CLK_EN BIT(0)
+
+#define SPDIF_CDR_CLK_RSTN_EN BIT(5)
+#define SPDIF_CDR_CLK_EN BIT(4)
+#define SOURCE_AIF_SYS_RSTN_EN BIT(3)
+#define SOURCE_AIF_SYS_CLK_EN BIT(2)
+#define SOURCE_AIF_CLK_RSTN_EN BIT(1)
+#define SOURCE_AIF_CLK_EN BIT(0)
+
+#define SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN BIT(3)
+#define SOURCE_CIPHER_SYS_CLK_EN BIT(2)
+#define SOURCE_CIPHER_CHAR_CLK_RSTN_EN BIT(1)
+#define SOURCE_CIPHER_CHAR_CLK_EN BIT(0)
+
+#define SOURCE_CRYPTO_SYS_CLK_RSTN_EN BIT(1)
+#define SOURCE_CRYPTO_SYS_CLK_EN BIT(0)
+
+#define APB_IRAM_PATH BIT(2)
+#define APB_DRAM_PATH BIT(1)
+#define APB_XT_RESET BIT(0)
+
+#define MAILBOX_INT_MASK_BIT BIT(1)
+#define PIF_INT_MASK_BIT BIT(0)
+#define ALL_INT_MASK 3
+
+/* mailbox */
+#define MB_OPCODE_ID 0
+#define MB_MODULE_ID 1
+#define MB_SIZE_MSB_ID 2
+#define MB_SIZE_LSB_ID 3
+#define MB_DATA_ID 4
+
+#define MB_MODULE_ID_DP_TX 0x01
+#define MB_MODULE_ID_HDCP_TX 0x07
+#define MB_MODULE_ID_HDCP_RX 0x08
+#define MB_MODULE_ID_HDCP_GENERAL 0x09
+#define MB_MODULE_ID_GENERAL 0x0a
+
+/* general opcode */
+#define GENERAL_MAIN_CONTROL 0x01
+#define GENERAL_TEST_ECHO 0x02
+#define GENERAL_BUS_SETTINGS 0x03
+#define GENERAL_TEST_ACCESS 0x04
+
+#define DPTX_SET_POWER_MNG 0x00
+#define DPTX_SET_HOST_CAPABILITIES 0x01
+#define DPTX_GET_EDID 0x02
+#define DPTX_READ_DPCD 0x03
+#define DPTX_WRITE_DPCD 0x04
+#define DPTX_ENABLE_EVENT 0x05
+#define DPTX_WRITE_REGISTER 0x06
+#define DPTX_READ_REGISTER 0x07
+#define DPTX_WRITE_FIELD 0x08
+#define DPTX_TRAINING_CONTROL 0x09
+#define DPTX_READ_EVENT 0x0a
+#define DPTX_READ_LINK_STAT 0x0b
+#define DPTX_SET_VIDEO 0x0c
+#define DPTX_SET_AUDIO 0x0d
+#define DPTX_GET_LAST_AUX_STAUS 0x0e
+#define DPTX_SET_LINK_BREAK_POINT 0x0f
+#define DPTX_FORCE_LANES 0x10
+#define DPTX_HPD_STATE 0x11
+
+#define FW_STANDBY 0
+#define FW_ACTIVE 1
+
+#define DPTX_EVENT_ENABLE_HPD BIT(0)
+#define DPTX_EVENT_ENABLE_TRAINING BIT(1)
+
+#define LINK_TRAINING_NOT_ACTIVE 0
+#define LINK_TRAINING_RUN 1
+#define LINK_TRAINING_RESTART 2
+
+#define CONTROL_VIDEO_IDLE 0
+#define CONTROL_VIDEO_VALID 1
+
+#define TU_CNT_RST_EN BIT(15)
+#define VIF_BYPASS_INTERLACE BIT(13)
+#define INTERLACE_FMT_DET BIT(12)
+#define INTERLACE_DTCT_WIN 0x20
+
+#define DP_FRAMER_SP_INTERLACE_EN BIT(2)
+#define DP_FRAMER_SP_HSP BIT(1)
+#define DP_FRAMER_SP_VSP BIT(0)
+
+/* capability */
+#define AUX_HOST_INVERT 3
+#define FAST_LT_SUPPORT 1
+#define FAST_LT_NOT_SUPPORT 0
+#define LANE_MAPPING_NORMAL 0x1b
+#define LANE_MAPPING_FLIPPED 0xe4
+#define ENHANCED 1
+#define SCRAMBLER_EN BIT(4)
+
+#define FULL_LT_STARTED BIT(0)
+#define FASE_LT_STARTED BIT(1)
+#define CLK_RECOVERY_FINISHED BIT(2)
+#define EQ_PHASE_FINISHED BIT(3)
+#define FASE_LT_START_FINISHED BIT(4)
+#define CLK_RECOVERY_FAILED BIT(5)
+#define EQ_PHASE_FAILED BIT(6)
+#define FASE_LT_FAILED BIT(7)
+
+#define DPTX_HPD_EVENT BIT(0)
+#define DPTX_TRAINING_EVENT BIT(1)
+#define HDCP_TX_STATUS_EVENT BIT(4)
+#define HDCP2_TX_IS_KM_STORED_EVENT BIT(5)
+#define HDCP2_TX_STORE_KM_EVENT BIT(6)
+#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT BIT(7)
+
+#define TU_SIZE 30
+#define CDN_DP_MAX_LINK_RATE DP_LINK_BW_5_4
+
+/* audio */
+#define AUDIO_PACK_EN BIT(8)
+#define SAMPLING_FREQ(x) (((x) & 0xf) << 16)
+#define ORIGINAL_SAMP_FREQ(x) (((x) & 0xf) << 24)
+#define SYNC_WR_TO_CH_ZERO BIT(1)
+#define I2S_DEC_START BIT(1)
+#define AUDIO_SW_RST BIT(0)
+#define SMPL2PKT_EN BIT(1)
+#define MAX_NUM_CH(x) (((x) & 0x1f) - 1)
+#define NUM_OF_I2S_PORTS(x) ((((x) / 2 - 1) & 0x3) << 5)
+#define AUDIO_TYPE_LPCM (2 << 7)
+#define CFG_SUB_PCKT_NUM(x) ((((x) - 1) & 0x7) << 11)
+#define AUDIO_CH_NUM(x) ((((x) - 1) & 0x1f) << 2)
+#define TRANS_SMPL_WIDTH_16 0
+#define TRANS_SMPL_WIDTH_24 BIT(11)
+#define TRANS_SMPL_WIDTH_32 (2 << 11)
+#define I2S_DEC_PORT_EN(x) (((x) & 0xf) << 17)
+#define SPDIF_ENABLE BIT(21)
+#define SPDIF_AVG_SEL BIT(20)
+#define SPDIF_JITTER_BYPASS BIT(19)
+#define SPDIF_FIFO_MID_RANGE(x) (((x) & 0xff) << 11)
+#define SPDIF_JITTER_THRSH(x) (((x) & 0xff) << 3)
+#define SPDIF_JITTER_AVG_WIN(x) ((x) & 0x7)
+
+/* Reference cycles when using lane clock as reference */
+#define LANE_REF_CYC 0x8000
+
+enum voltage_swing_level {
+ VOLTAGE_LEVEL_0,
+ VOLTAGE_LEVEL_1,
+ VOLTAGE_LEVEL_2,
+ VOLTAGE_LEVEL_3,
+};
+
+enum pre_emphasis_level {
+ PRE_EMPHASIS_LEVEL_0,
+ PRE_EMPHASIS_LEVEL_1,
+ PRE_EMPHASIS_LEVEL_2,
+ PRE_EMPHASIS_LEVEL_3,
+};
+
+enum pattern_set {
+ PTS1 = BIT(0),
+ PTS2 = BIT(1),
+ PTS3 = BIT(2),
+ PTS4 = BIT(3),
+ DP_NONE = BIT(4)
+};
+
+enum vic_color_depth {
+ BCS_6 = 0x1,
+ BCS_8 = 0x2,
+ BCS_10 = 0x4,
+ BCS_12 = 0x8,
+ BCS_16 = 0x10,
+};
+
+enum vic_bt_type {
+ BT_601 = 0x0,
+ BT_709 = 0x1,
+};
+
+void cdn_dp_clock_reset(struct cdn_dp_device *dp);
+
+void cdn_dp_set_fw_clk(struct cdn_dp_device *dp, unsigned long clk);
+int cdn_dp_load_firmware(struct cdn_dp_device *dp, const u32 *i_mem,
+ u32 i_size, const u32 *d_mem, u32 d_size);
+int cdn_dp_set_firmware_active(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_set_host_cap(struct cdn_dp_device *dp, u8 lanes, bool flip);
+int cdn_dp_event_config(struct cdn_dp_device *dp);
+u32 cdn_dp_get_event(struct cdn_dp_device *dp);
+int cdn_dp_get_hpd_status(struct cdn_dp_device *dp);
+int cdn_dp_dpcd_write(struct cdn_dp_device *dp, u32 addr, u8 value);
+int cdn_dp_dpcd_read(struct cdn_dp_device *dp, u32 addr, u8 *data, u16 len);
+int cdn_dp_get_edid_block(void *dp, u8 *edid,
+ unsigned int block, size_t length);
+int cdn_dp_train_link(struct cdn_dp_device *dp);
+int cdn_dp_set_video_status(struct cdn_dp_device *dp, int active);
+int cdn_dp_config_video(struct cdn_dp_device *dp);
+int cdn_dp_audio_stop(struct cdn_dp_device *dp, struct audio_info *audio);
+int cdn_dp_audio_mute(struct cdn_dp_device *dp, bool enable);
+int cdn_dp_audio_config(struct cdn_dp_device *dp, struct audio_info *audio);
+#endif /* _CDN_DP_REG_H */
diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
new file mode 100644
index 000000000..c51be1c9c
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
@@ -0,0 +1,1296 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:
+ * Chris Zhong <zyw@rock-chips.com>
+ * Nickey Yang <nickey.yang@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/math64.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/bridge/dw_mipi_dsi.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+#define DSI_PHY_RSTZ 0xa0
+#define PHY_DISFORCEPLL 0
+#define PHY_ENFORCEPLL BIT(3)
+#define PHY_DISABLECLK 0
+#define PHY_ENABLECLK BIT(2)
+#define PHY_RSTZ 0
+#define PHY_UNRSTZ BIT(1)
+#define PHY_SHUTDOWNZ 0
+#define PHY_UNSHUTDOWNZ BIT(0)
+
+#define DSI_PHY_IF_CFG 0xa4
+#define N_LANES(n) ((((n) - 1) & 0x3) << 0)
+#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8)
+
+#define DSI_PHY_STATUS 0xb0
+#define LOCK BIT(0)
+#define STOP_STATE_CLK_LANE BIT(2)
+
+#define DSI_PHY_TST_CTRL0 0xb4
+#define PHY_TESTCLK BIT(1)
+#define PHY_UNTESTCLK 0
+#define PHY_TESTCLR BIT(0)
+#define PHY_UNTESTCLR 0
+
+#define DSI_PHY_TST_CTRL1 0xb8
+#define PHY_TESTEN BIT(16)
+#define PHY_UNTESTEN 0
+#define PHY_TESTDOUT(n) (((n) & 0xff) << 8)
+#define PHY_TESTDIN(n) (((n) & 0xff) << 0)
+
+#define DSI_INT_ST0 0xbc
+#define DSI_INT_ST1 0xc0
+#define DSI_INT_MSK0 0xc4
+#define DSI_INT_MSK1 0xc8
+
+#define PHY_STATUS_TIMEOUT_US 10000
+#define CMD_PKT_STATUS_TIMEOUT_US 20000
+
+#define BYPASS_VCO_RANGE BIT(7)
+#define VCO_RANGE_CON_SEL(val) (((val) & 0x7) << 3)
+#define VCO_IN_CAP_CON_DEFAULT (0x0 << 1)
+#define VCO_IN_CAP_CON_LOW (0x1 << 1)
+#define VCO_IN_CAP_CON_HIGH (0x2 << 1)
+#define REF_BIAS_CUR_SEL BIT(0)
+
+#define CP_CURRENT_3UA 0x1
+#define CP_CURRENT_4_5UA 0x2
+#define CP_CURRENT_7_5UA 0x6
+#define CP_CURRENT_6UA 0x9
+#define CP_CURRENT_12UA 0xb
+#define CP_CURRENT_SEL(val) ((val) & 0xf)
+#define CP_PROGRAM_EN BIT(7)
+
+#define LPF_RESISTORS_15_5KOHM 0x1
+#define LPF_RESISTORS_13KOHM 0x2
+#define LPF_RESISTORS_11_5KOHM 0x4
+#define LPF_RESISTORS_10_5KOHM 0x8
+#define LPF_RESISTORS_8KOHM 0x10
+#define LPF_PROGRAM_EN BIT(6)
+#define LPF_RESISTORS_SEL(val) ((val) & 0x3f)
+
+#define HSFREQRANGE_SEL(val) (((val) & 0x3f) << 1)
+
+#define INPUT_DIVIDER(val) (((val) - 1) & 0x7f)
+#define LOW_PROGRAM_EN 0
+#define HIGH_PROGRAM_EN BIT(7)
+#define LOOP_DIV_LOW_SEL(val) (((val) - 1) & 0x1f)
+#define LOOP_DIV_HIGH_SEL(val) ((((val) - 1) >> 5) & 0xf)
+#define PLL_LOOP_DIV_EN BIT(5)
+#define PLL_INPUT_DIV_EN BIT(4)
+
+#define POWER_CONTROL BIT(6)
+#define INTERNAL_REG_CURRENT BIT(3)
+#define BIAS_BLOCK_ON BIT(2)
+#define BANDGAP_ON BIT(0)
+
+#define TER_RESISTOR_HIGH BIT(7)
+#define TER_RESISTOR_LOW 0
+#define LEVEL_SHIFTERS_ON BIT(6)
+#define TER_CAL_DONE BIT(5)
+#define SETRD_MAX (0x7 << 2)
+#define POWER_MANAGE BIT(1)
+#define TER_RESISTORS_ON BIT(0)
+
+#define BIASEXTR_SEL(val) ((val) & 0x7)
+#define BANDGAP_SEL(val) ((val) & 0x7)
+#define TLP_PROGRAM_EN BIT(7)
+#define THS_PRE_PROGRAM_EN BIT(7)
+#define THS_ZERO_PROGRAM_EN BIT(6)
+
+#define PLL_BIAS_CUR_SEL_CAP_VCO_CONTROL 0x10
+#define PLL_CP_CONTROL_PLL_LOCK_BYPASS 0x11
+#define PLL_LPF_AND_CP_CONTROL 0x12
+#define PLL_INPUT_DIVIDER_RATIO 0x17
+#define PLL_LOOP_DIVIDER_RATIO 0x18
+#define PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL 0x19
+#define BANDGAP_AND_BIAS_CONTROL 0x20
+#define TERMINATION_RESISTER_CONTROL 0x21
+#define AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY 0x22
+#define HS_RX_CONTROL_OF_LANE_0 0x44
+#define HS_TX_CLOCK_LANE_REQUEST_STATE_TIME_CONTROL 0x60
+#define HS_TX_CLOCK_LANE_PREPARE_STATE_TIME_CONTROL 0x61
+#define HS_TX_CLOCK_LANE_HS_ZERO_STATE_TIME_CONTROL 0x62
+#define HS_TX_CLOCK_LANE_TRAIL_STATE_TIME_CONTROL 0x63
+#define HS_TX_CLOCK_LANE_EXIT_STATE_TIME_CONTROL 0x64
+#define HS_TX_CLOCK_LANE_POST_TIME_CONTROL 0x65
+#define HS_TX_DATA_LANE_REQUEST_STATE_TIME_CONTROL 0x70
+#define HS_TX_DATA_LANE_PREPARE_STATE_TIME_CONTROL 0x71
+#define HS_TX_DATA_LANE_HS_ZERO_STATE_TIME_CONTROL 0x72
+#define HS_TX_DATA_LANE_TRAIL_STATE_TIME_CONTROL 0x73
+#define HS_TX_DATA_LANE_EXIT_STATE_TIME_CONTROL 0x74
+
+#define DW_MIPI_NEEDS_PHY_CFG_CLK BIT(0)
+#define DW_MIPI_NEEDS_GRF_CLK BIT(1)
+
+#define PX30_GRF_PD_VO_CON1 0x0438
+#define PX30_DSI_FORCETXSTOPMODE (0xf << 7)
+#define PX30_DSI_FORCERXMODE BIT(6)
+#define PX30_DSI_TURNDISABLE BIT(5)
+#define PX30_DSI_LCDC_SEL BIT(0)
+
+#define RK3288_GRF_SOC_CON6 0x025c
+#define RK3288_DSI0_LCDC_SEL BIT(6)
+#define RK3288_DSI1_LCDC_SEL BIT(9)
+
+#define RK3399_GRF_SOC_CON20 0x6250
+#define RK3399_DSI0_LCDC_SEL BIT(0)
+#define RK3399_DSI1_LCDC_SEL BIT(4)
+
+#define RK3399_GRF_SOC_CON22 0x6258
+#define RK3399_DSI0_TURNREQUEST (0xf << 12)
+#define RK3399_DSI0_TURNDISABLE (0xf << 8)
+#define RK3399_DSI0_FORCETXSTOPMODE (0xf << 4)
+#define RK3399_DSI0_FORCERXMODE (0xf << 0)
+
+#define RK3399_GRF_SOC_CON23 0x625c
+#define RK3399_DSI1_TURNDISABLE (0xf << 12)
+#define RK3399_DSI1_FORCETXSTOPMODE (0xf << 8)
+#define RK3399_DSI1_FORCERXMODE (0xf << 4)
+#define RK3399_DSI1_ENABLE (0xf << 0)
+
+#define RK3399_GRF_SOC_CON24 0x6260
+#define RK3399_TXRX_MASTERSLAVEZ BIT(7)
+#define RK3399_TXRX_ENABLECLK BIT(6)
+#define RK3399_TXRX_BASEDIR BIT(5)
+
+#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)
+
+#define to_dsi(nm) container_of(nm, struct dw_mipi_dsi_rockchip, nm)
+
+enum {
+ BANDGAP_97_07,
+ BANDGAP_98_05,
+ BANDGAP_99_02,
+ BANDGAP_100_00,
+ BANDGAP_93_17,
+ BANDGAP_94_15,
+ BANDGAP_95_12,
+ BANDGAP_96_10,
+};
+
+enum {
+ BIASEXTR_87_1,
+ BIASEXTR_91_5,
+ BIASEXTR_95_9,
+ BIASEXTR_100,
+ BIASEXTR_105_94,
+ BIASEXTR_111_88,
+ BIASEXTR_118_8,
+ BIASEXTR_127_7,
+};
+
+struct rockchip_dw_dsi_chip_data {
+ u32 reg;
+
+ u32 lcdsel_grf_reg;
+ u32 lcdsel_big;
+ u32 lcdsel_lit;
+
+ u32 enable_grf_reg;
+ u32 enable;
+
+ u32 lanecfg1_grf_reg;
+ u32 lanecfg1;
+ u32 lanecfg2_grf_reg;
+ u32 lanecfg2;
+
+ unsigned int flags;
+ unsigned int max_data_lanes;
+};
+
+struct dw_mipi_dsi_rockchip {
+ struct device *dev;
+ struct drm_encoder encoder;
+ void __iomem *base;
+
+ struct regmap *grf_regmap;
+ struct clk *pllref_clk;
+ struct clk *grf_clk;
+ struct clk *phy_cfg_clk;
+
+ /* dual-channel */
+ bool is_slave;
+ struct dw_mipi_dsi_rockchip *slave;
+
+ /* optional external dphy */
+ struct phy *phy;
+ union phy_configure_opts phy_opts;
+
+ unsigned int lane_mbps; /* per lane */
+ u16 input_div;
+ u16 feedback_div;
+ u32 format;
+
+ struct dw_mipi_dsi *dmd;
+ const struct rockchip_dw_dsi_chip_data *cdata;
+ struct dw_mipi_dsi_plat_data pdata;
+
+ bool dsi_bound;
+};
+
+struct dphy_pll_parameter_map {
+ unsigned int max_mbps;
+ u8 hsfreqrange;
+ u8 icpctrl;
+ u8 lpfctrl;
+};
+
+/* The table is based on 27MHz DPHY pll reference clock. */
+static const struct dphy_pll_parameter_map dppa_map[] = {
+ { 89, 0x00, CP_CURRENT_3UA, LPF_RESISTORS_13KOHM },
+ { 99, 0x10, CP_CURRENT_3UA, LPF_RESISTORS_13KOHM },
+ { 109, 0x20, CP_CURRENT_3UA, LPF_RESISTORS_13KOHM },
+ { 129, 0x01, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM },
+ { 139, 0x11, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM },
+ { 149, 0x21, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM },
+ { 169, 0x02, CP_CURRENT_6UA, LPF_RESISTORS_13KOHM },
+ { 179, 0x12, CP_CURRENT_6UA, LPF_RESISTORS_13KOHM },
+ { 199, 0x22, CP_CURRENT_6UA, LPF_RESISTORS_13KOHM },
+ { 219, 0x03, CP_CURRENT_4_5UA, LPF_RESISTORS_13KOHM },
+ { 239, 0x13, CP_CURRENT_4_5UA, LPF_RESISTORS_13KOHM },
+ { 249, 0x23, CP_CURRENT_4_5UA, LPF_RESISTORS_13KOHM },
+ { 269, 0x04, CP_CURRENT_6UA, LPF_RESISTORS_11_5KOHM },
+ { 299, 0x14, CP_CURRENT_6UA, LPF_RESISTORS_11_5KOHM },
+ { 329, 0x05, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM },
+ { 359, 0x15, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM },
+ { 399, 0x25, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM },
+ { 449, 0x06, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 499, 0x16, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 549, 0x07, CP_CURRENT_7_5UA, LPF_RESISTORS_10_5KOHM },
+ { 599, 0x17, CP_CURRENT_7_5UA, LPF_RESISTORS_10_5KOHM },
+ { 649, 0x08, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 699, 0x18, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 749, 0x09, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 799, 0x19, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 849, 0x29, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 899, 0x39, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM },
+ { 949, 0x0a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM },
+ { 999, 0x1a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM },
+ {1049, 0x2a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM },
+ {1099, 0x3a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM },
+ {1149, 0x0b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1199, 0x1b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1249, 0x2b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1299, 0x3b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1349, 0x0c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1399, 0x1c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1449, 0x2c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM },
+ {1500, 0x3c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM }
+};
+
+static int max_mbps_to_parameter(unsigned int max_mbps)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dppa_map); i++)
+ if (dppa_map[i].max_mbps >= max_mbps)
+ return i;
+
+ return -EINVAL;
+}
+
+static inline void dsi_write(struct dw_mipi_dsi_rockchip *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct dw_mipi_dsi_rockchip *dsi, u32 reg)
+{
+ return readl(dsi->base + reg);
+}
+
+static inline void dsi_set(struct dw_mipi_dsi_rockchip *dsi, u32 reg, u32 mask)
+{
+ dsi_write(dsi, reg, dsi_read(dsi, reg) | mask);
+}
+
+static inline void dsi_update_bits(struct dw_mipi_dsi_rockchip *dsi, u32 reg,
+ u32 mask, u32 val)
+{
+ dsi_write(dsi, reg, (dsi_read(dsi, reg) & ~mask) | val);
+}
+
+static void dw_mipi_dsi_phy_write(struct dw_mipi_dsi_rockchip *dsi,
+ u8 test_code,
+ u8 test_data)
+{
+ /*
+ * With the falling edge on TESTCLK, the TESTDIN[7:0] signal content
+ * is latched internally as the current test code. Test data is
+ * programmed internally by rising edge on TESTCLK.
+ */
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
+
+ dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN | PHY_TESTDOUT(0) |
+ PHY_TESTDIN(test_code));
+
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
+
+ dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN | PHY_TESTDOUT(0) |
+ PHY_TESTDIN(test_data));
+
+ dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
+}
+
+/**
+ * ns2bc - Nanoseconds to byte clock cycles
+ */
+static inline unsigned int ns2bc(struct dw_mipi_dsi_rockchip *dsi, int ns)
+{
+ return DIV_ROUND_UP(ns * dsi->lane_mbps / 8, 1000);
+}
+
+/**
+ * ns2ui - Nanoseconds to UI time periods
+ */
+static inline unsigned int ns2ui(struct dw_mipi_dsi_rockchip *dsi, int ns)
+{
+ return DIV_ROUND_UP(ns * dsi->lane_mbps, 1000);
+}
+
+static int dw_mipi_dsi_phy_init(void *priv_data)
+{
+ struct dw_mipi_dsi_rockchip *dsi = priv_data;
+ int ret, i, vco;
+
+ if (dsi->phy)
+ return 0;
+
+ /*
+ * Get vco from frequency(lane_mbps)
+ * vco frequency table
+ * 000 - between 80 and 200 MHz
+ * 001 - between 200 and 300 MHz
+ * 010 - between 300 and 500 MHz
+ * 011 - between 500 and 700 MHz
+ * 100 - between 700 and 900 MHz
+ * 101 - between 900 and 1100 MHz
+ * 110 - between 1100 and 1300 MHz
+ * 111 - between 1300 and 1500 MHz
+ */
+ vco = (dsi->lane_mbps < 200) ? 0 : (dsi->lane_mbps + 100) / 200;
+
+ i = max_mbps_to_parameter(dsi->lane_mbps);
+ if (i < 0) {
+ DRM_DEV_ERROR(dsi->dev,
+ "failed to get parameter for %dmbps clock\n",
+ dsi->lane_mbps);
+ return i;
+ }
+
+ ret = clk_prepare_enable(dsi->phy_cfg_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "Failed to enable phy_cfg_clk\n");
+ return ret;
+ }
+
+ dw_mipi_dsi_phy_write(dsi, PLL_BIAS_CUR_SEL_CAP_VCO_CONTROL,
+ BYPASS_VCO_RANGE |
+ VCO_RANGE_CON_SEL(vco) |
+ VCO_IN_CAP_CON_LOW |
+ REF_BIAS_CUR_SEL);
+
+ dw_mipi_dsi_phy_write(dsi, PLL_CP_CONTROL_PLL_LOCK_BYPASS,
+ CP_CURRENT_SEL(dppa_map[i].icpctrl));
+ dw_mipi_dsi_phy_write(dsi, PLL_LPF_AND_CP_CONTROL,
+ CP_PROGRAM_EN | LPF_PROGRAM_EN |
+ LPF_RESISTORS_SEL(dppa_map[i].lpfctrl));
+
+ dw_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_0,
+ HSFREQRANGE_SEL(dppa_map[i].hsfreqrange));
+
+ dw_mipi_dsi_phy_write(dsi, PLL_INPUT_DIVIDER_RATIO,
+ INPUT_DIVIDER(dsi->input_div));
+ dw_mipi_dsi_phy_write(dsi, PLL_LOOP_DIVIDER_RATIO,
+ LOOP_DIV_LOW_SEL(dsi->feedback_div) |
+ LOW_PROGRAM_EN);
+ /*
+ * We need set PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL immediately
+ * to make the configured LSB effective according to IP simulation
+ * and lab test results.
+ * Only in this way can we get correct mipi phy pll frequency.
+ */
+ dw_mipi_dsi_phy_write(dsi, PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL,
+ PLL_LOOP_DIV_EN | PLL_INPUT_DIV_EN);
+ dw_mipi_dsi_phy_write(dsi, PLL_LOOP_DIVIDER_RATIO,
+ LOOP_DIV_HIGH_SEL(dsi->feedback_div) |
+ HIGH_PROGRAM_EN);
+ dw_mipi_dsi_phy_write(dsi, PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL,
+ PLL_LOOP_DIV_EN | PLL_INPUT_DIV_EN);
+
+ dw_mipi_dsi_phy_write(dsi, AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY,
+ LOW_PROGRAM_EN | BIASEXTR_SEL(BIASEXTR_127_7));
+ dw_mipi_dsi_phy_write(dsi, AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY,
+ HIGH_PROGRAM_EN | BANDGAP_SEL(BANDGAP_96_10));
+
+ dw_mipi_dsi_phy_write(dsi, BANDGAP_AND_BIAS_CONTROL,
+ POWER_CONTROL | INTERNAL_REG_CURRENT |
+ BIAS_BLOCK_ON | BANDGAP_ON);
+
+ dw_mipi_dsi_phy_write(dsi, TERMINATION_RESISTER_CONTROL,
+ TER_RESISTOR_LOW | TER_CAL_DONE |
+ SETRD_MAX | TER_RESISTORS_ON);
+ dw_mipi_dsi_phy_write(dsi, TERMINATION_RESISTER_CONTROL,
+ TER_RESISTOR_HIGH | LEVEL_SHIFTERS_ON |
+ SETRD_MAX | POWER_MANAGE |
+ TER_RESISTORS_ON);
+
+ dw_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_REQUEST_STATE_TIME_CONTROL,
+ TLP_PROGRAM_EN | ns2bc(dsi, 500));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_PREPARE_STATE_TIME_CONTROL,
+ THS_PRE_PROGRAM_EN | ns2ui(dsi, 40));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_HS_ZERO_STATE_TIME_CONTROL,
+ THS_ZERO_PROGRAM_EN | ns2bc(dsi, 300));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_TRAIL_STATE_TIME_CONTROL,
+ THS_PRE_PROGRAM_EN | ns2ui(dsi, 100));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_EXIT_STATE_TIME_CONTROL,
+ BIT(5) | ns2bc(dsi, 100));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_POST_TIME_CONTROL,
+ BIT(5) | (ns2bc(dsi, 60) + 7));
+
+ dw_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_REQUEST_STATE_TIME_CONTROL,
+ TLP_PROGRAM_EN | ns2bc(dsi, 500));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_PREPARE_STATE_TIME_CONTROL,
+ THS_PRE_PROGRAM_EN | (ns2ui(dsi, 50) + 20));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_HS_ZERO_STATE_TIME_CONTROL,
+ THS_ZERO_PROGRAM_EN | (ns2bc(dsi, 140) + 2));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_TRAIL_STATE_TIME_CONTROL,
+ THS_PRE_PROGRAM_EN | (ns2ui(dsi, 60) + 8));
+ dw_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_EXIT_STATE_TIME_CONTROL,
+ BIT(5) | ns2bc(dsi, 100));
+
+ clk_disable_unprepare(dsi->phy_cfg_clk);
+
+ return ret;
+}
+
+static void dw_mipi_dsi_phy_power_on(void *priv_data)
+{
+ struct dw_mipi_dsi_rockchip *dsi = priv_data;
+ int ret;
+
+ ret = phy_set_mode(dsi->phy, PHY_MODE_MIPI_DPHY);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "failed to set phy mode: %d\n", ret);
+ return;
+ }
+
+ phy_configure(dsi->phy, &dsi->phy_opts);
+ phy_power_on(dsi->phy);
+}
+
+static void dw_mipi_dsi_phy_power_off(void *priv_data)
+{
+ struct dw_mipi_dsi_rockchip *dsi = priv_data;
+
+ phy_power_off(dsi->phy);
+}
+
+static int
+dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
+ unsigned long mode_flags, u32 lanes, u32 format,
+ unsigned int *lane_mbps)
+{
+ struct dw_mipi_dsi_rockchip *dsi = priv_data;
+ int bpp;
+ unsigned long mpclk, tmp;
+ unsigned int target_mbps = 1000;
+ unsigned int max_mbps = dppa_map[ARRAY_SIZE(dppa_map) - 1].max_mbps;
+ unsigned long best_freq = 0;
+ unsigned long fvco_min, fvco_max, fin, fout;
+ unsigned int min_prediv, max_prediv;
+ unsigned int _prediv, best_prediv;
+ unsigned long _fbdiv, best_fbdiv;
+ unsigned long min_delta = ULONG_MAX;
+
+ dsi->format = format;
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0) {
+ DRM_DEV_ERROR(dsi->dev,
+ "failed to get bpp for pixel format %d\n",
+ dsi->format);
+ return bpp;
+ }
+
+ mpclk = DIV_ROUND_UP(mode->clock, MSEC_PER_SEC);
+ if (mpclk) {
+ /* take 1 / 0.8, since mbps must big than bandwidth of RGB */
+ tmp = mpclk * (bpp / lanes) * 10 / 8;
+ if (tmp < max_mbps)
+ target_mbps = tmp;
+ else
+ DRM_DEV_ERROR(dsi->dev,
+ "DPHY clock frequency is out of range\n");
+ }
+
+ /* for external phy only a the mipi_dphy_config is necessary */
+ if (dsi->phy) {
+ phy_mipi_dphy_get_default_config(mode->clock * 1000 * 10 / 8,
+ bpp, lanes,
+ &dsi->phy_opts.mipi_dphy);
+ dsi->lane_mbps = target_mbps;
+ *lane_mbps = dsi->lane_mbps;
+
+ return 0;
+ }
+
+ fin = clk_get_rate(dsi->pllref_clk);
+ fout = target_mbps * USEC_PER_SEC;
+
+ /* constraint: 5Mhz <= Fref / N <= 40MHz */
+ min_prediv = DIV_ROUND_UP(fin, 40 * USEC_PER_SEC);
+ max_prediv = fin / (5 * USEC_PER_SEC);
+
+ /* constraint: 80MHz <= Fvco <= 1500Mhz */
+ fvco_min = 80 * USEC_PER_SEC;
+ fvco_max = 1500 * USEC_PER_SEC;
+
+ for (_prediv = min_prediv; _prediv <= max_prediv; _prediv++) {
+ u64 tmp;
+ u32 delta;
+ /* Fvco = Fref * M / N */
+ tmp = (u64)fout * _prediv;
+ do_div(tmp, fin);
+ _fbdiv = tmp;
+ /*
+ * Due to the use of a "by 2 pre-scaler," the range of the
+ * feedback multiplication value M is limited to even division
+ * numbers, and m must be greater than 6, not bigger than 512.
+ */
+ if (_fbdiv < 6 || _fbdiv > 512)
+ continue;
+
+ _fbdiv += _fbdiv % 2;
+
+ tmp = (u64)_fbdiv * fin;
+ do_div(tmp, _prediv);
+ if (tmp < fvco_min || tmp > fvco_max)
+ continue;
+
+ delta = abs(fout - tmp);
+ if (delta < min_delta) {
+ best_prediv = _prediv;
+ best_fbdiv = _fbdiv;
+ min_delta = delta;
+ best_freq = tmp;
+ }
+ }
+
+ if (best_freq) {
+ dsi->lane_mbps = DIV_ROUND_UP(best_freq, USEC_PER_SEC);
+ *lane_mbps = dsi->lane_mbps;
+ dsi->input_div = best_prediv;
+ dsi->feedback_div = best_fbdiv;
+ } else {
+ DRM_DEV_ERROR(dsi->dev, "Can not find best_freq for DPHY\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct hstt {
+ unsigned int maxfreq;
+ struct dw_mipi_dsi_dphy_timing timing;
+};
+
+#define HSTT(_maxfreq, _c_lp2hs, _c_hs2lp, _d_lp2hs, _d_hs2lp) \
+{ \
+ .maxfreq = _maxfreq, \
+ .timing = { \
+ .clk_lp2hs = _c_lp2hs, \
+ .clk_hs2lp = _c_hs2lp, \
+ .data_lp2hs = _d_lp2hs, \
+ .data_hs2lp = _d_hs2lp, \
+ } \
+}
+
+/* Table A-3 High-Speed Transition Times */
+struct hstt hstt_table[] = {
+ HSTT( 90, 32, 20, 26, 13),
+ HSTT( 100, 35, 23, 28, 14),
+ HSTT( 110, 32, 22, 26, 13),
+ HSTT( 130, 31, 20, 27, 13),
+ HSTT( 140, 33, 22, 26, 14),
+ HSTT( 150, 33, 21, 26, 14),
+ HSTT( 170, 32, 20, 27, 13),
+ HSTT( 180, 36, 23, 30, 15),
+ HSTT( 200, 40, 22, 33, 15),
+ HSTT( 220, 40, 22, 33, 15),
+ HSTT( 240, 44, 24, 36, 16),
+ HSTT( 250, 48, 24, 38, 17),
+ HSTT( 270, 48, 24, 38, 17),
+ HSTT( 300, 50, 27, 41, 18),
+ HSTT( 330, 56, 28, 45, 18),
+ HSTT( 360, 59, 28, 48, 19),
+ HSTT( 400, 61, 30, 50, 20),
+ HSTT( 450, 67, 31, 55, 21),
+ HSTT( 500, 73, 31, 59, 22),
+ HSTT( 550, 79, 36, 63, 24),
+ HSTT( 600, 83, 37, 68, 25),
+ HSTT( 650, 90, 38, 73, 27),
+ HSTT( 700, 95, 40, 77, 28),
+ HSTT( 750, 102, 40, 84, 28),
+ HSTT( 800, 106, 42, 87, 30),
+ HSTT( 850, 113, 44, 93, 31),
+ HSTT( 900, 118, 47, 98, 32),
+ HSTT( 950, 124, 47, 102, 34),
+ HSTT(1000, 130, 49, 107, 35),
+ HSTT(1050, 135, 51, 111, 37),
+ HSTT(1100, 139, 51, 114, 38),
+ HSTT(1150, 146, 54, 120, 40),
+ HSTT(1200, 153, 57, 125, 41),
+ HSTT(1250, 158, 58, 130, 42),
+ HSTT(1300, 163, 58, 135, 44),
+ HSTT(1350, 168, 60, 140, 45),
+ HSTT(1400, 172, 64, 144, 47),
+ HSTT(1450, 176, 65, 148, 48),
+ HSTT(1500, 181, 66, 153, 50)
+};
+
+static int
+dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
+ struct dw_mipi_dsi_dphy_timing *timing)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hstt_table); i++)
+ if (lane_mbps < hstt_table[i].maxfreq)
+ break;
+
+ if (i == ARRAY_SIZE(hstt_table))
+ i--;
+
+ *timing = hstt_table[i].timing;
+
+ return 0;
+}
+
+static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_rockchip_phy_ops = {
+ .init = dw_mipi_dsi_phy_init,
+ .power_on = dw_mipi_dsi_phy_power_on,
+ .power_off = dw_mipi_dsi_phy_power_off,
+ .get_lane_mbps = dw_mipi_dsi_get_lane_mbps,
+ .get_timing = dw_mipi_dsi_phy_get_timing,
+};
+
+static void dw_mipi_dsi_rockchip_config(struct dw_mipi_dsi_rockchip *dsi)
+{
+ if (dsi->cdata->lanecfg1_grf_reg)
+ regmap_write(dsi->grf_regmap, dsi->cdata->lanecfg1_grf_reg,
+ dsi->cdata->lanecfg1);
+
+ if (dsi->cdata->lanecfg2_grf_reg)
+ regmap_write(dsi->grf_regmap, dsi->cdata->lanecfg2_grf_reg,
+ dsi->cdata->lanecfg2);
+
+ if (dsi->cdata->enable_grf_reg)
+ regmap_write(dsi->grf_regmap, dsi->cdata->enable_grf_reg,
+ dsi->cdata->enable);
+}
+
+static void dw_mipi_dsi_rockchip_set_lcdsel(struct dw_mipi_dsi_rockchip *dsi,
+ int mux)
+{
+ regmap_write(dsi->grf_regmap, dsi->cdata->lcdsel_grf_reg,
+ mux ? dsi->cdata->lcdsel_lit : dsi->cdata->lcdsel_big);
+}
+
+static int
+dw_mipi_dsi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+ struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
+
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB888:
+ s->output_mode = ROCKCHIP_OUT_MODE_P888;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ s->output_mode = ROCKCHIP_OUT_MODE_P666;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ s->output_mode = ROCKCHIP_OUT_MODE_P565;
+ break;
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ s->output_type = DRM_MODE_CONNECTOR_DSI;
+ if (dsi->slave)
+ s->output_flags = ROCKCHIP_OUTPUT_DSI_DUAL;
+
+ return 0;
+}
+
+static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
+ int ret, mux;
+
+ mux = drm_of_encoder_active_endpoint_id(dsi->dev->of_node,
+ &dsi->encoder);
+ if (mux < 0)
+ return;
+
+ /*
+ * For the RK3399, the clk of grf must be enabled before writing grf
+ * register. And for RK3288 or other soc, this grf_clk must be NULL,
+ * the clk_prepare_enable return true directly.
+ */
+ ret = clk_prepare_enable(dsi->grf_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret);
+ return;
+ }
+
+ dw_mipi_dsi_rockchip_set_lcdsel(dsi, mux);
+ if (dsi->slave)
+ dw_mipi_dsi_rockchip_set_lcdsel(dsi->slave, mux);
+
+ clk_disable_unprepare(dsi->grf_clk);
+}
+
+static const struct drm_encoder_helper_funcs
+dw_mipi_dsi_encoder_helper_funcs = {
+ .atomic_check = dw_mipi_dsi_encoder_atomic_check,
+ .enable = dw_mipi_dsi_encoder_enable,
+};
+
+static int rockchip_dsi_drm_create_encoder(struct dw_mipi_dsi_rockchip *dsi,
+ struct drm_device *drm_dev)
+{
+ struct drm_encoder *encoder = &dsi->encoder;
+ int ret;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+ dsi->dev->of_node);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
+ if (ret) {
+ DRM_ERROR("Failed to initialize encoder with drm\n");
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &dw_mipi_dsi_encoder_helper_funcs);
+
+ return 0;
+}
+
+static struct device
+*dw_mipi_dsi_rockchip_find_second(struct dw_mipi_dsi_rockchip *dsi)
+{
+ const struct of_device_id *match;
+ struct device_node *node = NULL, *local;
+
+ match = of_match_device(dsi->dev->driver->of_match_table, dsi->dev);
+
+ local = of_graph_get_remote_node(dsi->dev->of_node, 1, 0);
+ if (!local)
+ return NULL;
+
+ while ((node = of_find_compatible_node(node, NULL,
+ match->compatible))) {
+ struct device_node *remote;
+
+ /* found ourself */
+ if (node == dsi->dev->of_node)
+ continue;
+
+ remote = of_graph_get_remote_node(node, 1, 0);
+ if (!remote)
+ continue;
+
+ /* same display device in port1-ep0 for both */
+ if (remote == local) {
+ struct dw_mipi_dsi_rockchip *dsi2;
+ struct platform_device *pdev;
+
+ pdev = of_find_device_by_node(node);
+
+ /*
+ * we have found the second, so will either return it
+ * or return with an error. In any case won't need the
+ * nodes anymore nor continue the loop.
+ */
+ of_node_put(remote);
+ of_node_put(node);
+ of_node_put(local);
+
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ dsi2 = platform_get_drvdata(pdev);
+ if (!dsi2) {
+ platform_device_put(pdev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return &pdev->dev;
+ }
+
+ of_node_put(remote);
+ }
+
+ of_node_put(local);
+
+ return NULL;
+}
+
+static int dw_mipi_dsi_rockchip_bind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct device *second;
+ bool master1, master2;
+ int ret;
+
+ second = dw_mipi_dsi_rockchip_find_second(dsi);
+ if (IS_ERR(second))
+ return PTR_ERR(second);
+
+ if (second) {
+ master1 = of_property_read_bool(dsi->dev->of_node,
+ "clock-master");
+ master2 = of_property_read_bool(second->of_node,
+ "clock-master");
+
+ if (master1 && master2) {
+ DRM_DEV_ERROR(dsi->dev, "only one clock-master allowed\n");
+ return -EINVAL;
+ }
+
+ if (!master1 && !master2) {
+ DRM_DEV_ERROR(dsi->dev, "no clock-master defined\n");
+ return -EINVAL;
+ }
+
+ /* we are the slave in dual-DSI */
+ if (!master1) {
+ dsi->is_slave = true;
+ return 0;
+ }
+
+ dsi->slave = dev_get_drvdata(second);
+ if (!dsi->slave) {
+ DRM_DEV_ERROR(dev, "could not get slaves data\n");
+ return -ENODEV;
+ }
+
+ dsi->slave->is_slave = true;
+ dw_mipi_dsi_set_slave(dsi->dmd, dsi->slave->dmd);
+ put_device(second);
+ }
+
+ pm_runtime_get_sync(dsi->dev);
+ if (dsi->slave)
+ pm_runtime_get_sync(dsi->slave->dev);
+
+ ret = clk_prepare_enable(dsi->pllref_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to enable pllref_clk: %d\n", ret);
+ goto out_pm_runtime;
+ }
+
+ /*
+ * With the GRF clock running, write lane and dual-mode configurations
+ * that won't change immediately. If we waited until enable() to do
+ * this, things like panel preparation would not be able to send
+ * commands over DSI.
+ */
+ ret = clk_prepare_enable(dsi->grf_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret);
+ goto out_pll_clk;
+ }
+
+ dw_mipi_dsi_rockchip_config(dsi);
+ if (dsi->slave)
+ dw_mipi_dsi_rockchip_config(dsi->slave);
+
+ clk_disable_unprepare(dsi->grf_clk);
+
+ ret = rockchip_dsi_drm_create_encoder(dsi, drm_dev);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to create drm encoder\n");
+ goto out_pll_clk;
+ }
+
+ ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to bind: %d\n", ret);
+ goto out_pll_clk;
+ }
+
+ dsi->dsi_bound = true;
+
+ return 0;
+
+out_pll_clk:
+ clk_disable_unprepare(dsi->pllref_clk);
+out_pm_runtime:
+ pm_runtime_put(dsi->dev);
+ if (dsi->slave)
+ pm_runtime_put(dsi->slave->dev);
+
+ return ret;
+}
+
+static void dw_mipi_dsi_rockchip_unbind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
+
+ if (dsi->is_slave)
+ return;
+
+ dsi->dsi_bound = false;
+
+ dw_mipi_dsi_unbind(dsi->dmd);
+
+ clk_disable_unprepare(dsi->pllref_clk);
+
+ pm_runtime_put(dsi->dev);
+ if (dsi->slave)
+ pm_runtime_put(dsi->slave->dev);
+}
+
+static const struct component_ops dw_mipi_dsi_rockchip_ops = {
+ .bind = dw_mipi_dsi_rockchip_bind,
+ .unbind = dw_mipi_dsi_rockchip_unbind,
+};
+
+static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
+ struct mipi_dsi_device *device)
+{
+ struct dw_mipi_dsi_rockchip *dsi = priv_data;
+ struct device *second;
+ int ret;
+
+ ret = component_add(dsi->dev, &dw_mipi_dsi_rockchip_ops);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "Failed to register component: %d\n",
+ ret);
+ return ret;
+ }
+
+ second = dw_mipi_dsi_rockchip_find_second(dsi);
+ if (IS_ERR(second))
+ return PTR_ERR(second);
+ if (second) {
+ ret = component_add(second, &dw_mipi_dsi_rockchip_ops);
+ if (ret) {
+ DRM_DEV_ERROR(second,
+ "Failed to register component: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int dw_mipi_dsi_rockchip_host_detach(void *priv_data,
+ struct mipi_dsi_device *device)
+{
+ struct dw_mipi_dsi_rockchip *dsi = priv_data;
+ struct device *second;
+
+ second = dw_mipi_dsi_rockchip_find_second(dsi);
+ if (second && !IS_ERR(second))
+ component_del(second, &dw_mipi_dsi_rockchip_ops);
+
+ component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops);
+
+ return 0;
+}
+
+static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_rockchip_host_ops = {
+ .attach = dw_mipi_dsi_rockchip_host_attach,
+ .detach = dw_mipi_dsi_rockchip_host_detach,
+};
+
+static int __maybe_unused dw_mipi_dsi_rockchip_resume(struct device *dev)
+{
+ struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev);
+ int ret;
+
+ /*
+ * Re-configure DSI state, if we were previously initialized. We need
+ * to do this before rockchip_drm_drv tries to re-enable() any panels.
+ */
+ if (dsi->dsi_bound) {
+ ret = clk_prepare_enable(dsi->grf_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret);
+ return ret;
+ }
+
+ dw_mipi_dsi_rockchip_config(dsi);
+ if (dsi->slave)
+ dw_mipi_dsi_rockchip_config(dsi->slave);
+
+ clk_disable_unprepare(dsi->grf_clk);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops dw_mipi_dsi_rockchip_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, dw_mipi_dsi_rockchip_resume)
+};
+
+static int dw_mipi_dsi_rockchip_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct dw_mipi_dsi_rockchip *dsi;
+ struct resource *res;
+ const struct rockchip_dw_dsi_chip_data *cdata =
+ of_device_get_match_data(dev);
+ int ret, i;
+
+ dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dsi->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dsi->base)) {
+ DRM_DEV_ERROR(dev, "Unable to get dsi registers\n");
+ return PTR_ERR(dsi->base);
+ }
+
+ i = 0;
+ while (cdata[i].reg) {
+ if (cdata[i].reg == res->start) {
+ dsi->cdata = &cdata[i];
+ break;
+ }
+
+ i++;
+ }
+
+ if (!dsi->cdata) {
+ DRM_DEV_ERROR(dev, "no dsi-config for %s node\n", np->name);
+ return -EINVAL;
+ }
+
+ /* try to get a possible external dphy */
+ dsi->phy = devm_phy_optional_get(dev, "dphy");
+ if (IS_ERR(dsi->phy)) {
+ ret = PTR_ERR(dsi->phy);
+ DRM_DEV_ERROR(dev, "failed to get mipi dphy: %d\n", ret);
+ return ret;
+ }
+
+ dsi->pllref_clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(dsi->pllref_clk)) {
+ if (dsi->phy) {
+ /*
+ * if external phy is present, pll will be
+ * generated there.
+ */
+ dsi->pllref_clk = NULL;
+ } else {
+ ret = PTR_ERR(dsi->pllref_clk);
+ DRM_DEV_ERROR(dev,
+ "Unable to get pll reference clock: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (dsi->cdata->flags & DW_MIPI_NEEDS_PHY_CFG_CLK) {
+ dsi->phy_cfg_clk = devm_clk_get(dev, "phy_cfg");
+ if (IS_ERR(dsi->phy_cfg_clk)) {
+ ret = PTR_ERR(dsi->phy_cfg_clk);
+ DRM_DEV_ERROR(dev,
+ "Unable to get phy_cfg_clk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (dsi->cdata->flags & DW_MIPI_NEEDS_GRF_CLK) {
+ dsi->grf_clk = devm_clk_get(dev, "grf");
+ if (IS_ERR(dsi->grf_clk)) {
+ ret = PTR_ERR(dsi->grf_clk);
+ DRM_DEV_ERROR(dev, "Unable to get grf_clk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ dsi->grf_regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(dsi->grf_regmap)) {
+ DRM_DEV_ERROR(dsi->dev, "Unable to get rockchip,grf\n");
+ return PTR_ERR(dsi->grf_regmap);
+ }
+
+ dsi->dev = dev;
+ dsi->pdata.base = dsi->base;
+ dsi->pdata.max_data_lanes = dsi->cdata->max_data_lanes;
+ dsi->pdata.phy_ops = &dw_mipi_dsi_rockchip_phy_ops;
+ dsi->pdata.host_ops = &dw_mipi_dsi_rockchip_host_ops;
+ dsi->pdata.priv_data = dsi;
+ platform_set_drvdata(pdev, dsi);
+
+ dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata);
+ if (IS_ERR(dsi->dmd)) {
+ ret = PTR_ERR(dsi->dmd);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dev,
+ "Failed to probe dw_mipi_dsi: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dw_mipi_dsi_rockchip_remove(struct platform_device *pdev)
+{
+ struct dw_mipi_dsi_rockchip *dsi = platform_get_drvdata(pdev);
+
+ dw_mipi_dsi_remove(dsi->dmd);
+
+ return 0;
+}
+
+static const struct rockchip_dw_dsi_chip_data px30_chip_data[] = {
+ {
+ .reg = 0xff450000,
+ .lcdsel_grf_reg = PX30_GRF_PD_VO_CON1,
+ .lcdsel_big = HIWORD_UPDATE(0, PX30_DSI_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(PX30_DSI_LCDC_SEL,
+ PX30_DSI_LCDC_SEL),
+
+ .lanecfg1_grf_reg = PX30_GRF_PD_VO_CON1,
+ .lanecfg1 = HIWORD_UPDATE(0, PX30_DSI_TURNDISABLE |
+ PX30_DSI_FORCERXMODE |
+ PX30_DSI_FORCETXSTOPMODE),
+
+ .max_data_lanes = 4,
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_dw_dsi_chip_data rk3288_chip_data[] = {
+ {
+ .reg = 0xff960000,
+ .lcdsel_grf_reg = RK3288_GRF_SOC_CON6,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3288_DSI0_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3288_DSI0_LCDC_SEL, RK3288_DSI0_LCDC_SEL),
+
+ .max_data_lanes = 4,
+ },
+ {
+ .reg = 0xff964000,
+ .lcdsel_grf_reg = RK3288_GRF_SOC_CON6,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3288_DSI1_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3288_DSI1_LCDC_SEL, RK3288_DSI1_LCDC_SEL),
+
+ .max_data_lanes = 4,
+ },
+ { /* sentinel */ }
+};
+
+static const struct rockchip_dw_dsi_chip_data rk3399_chip_data[] = {
+ {
+ .reg = 0xff960000,
+ .lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3399_DSI0_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3399_DSI0_LCDC_SEL,
+ RK3399_DSI0_LCDC_SEL),
+
+ .lanecfg1_grf_reg = RK3399_GRF_SOC_CON22,
+ .lanecfg1 = HIWORD_UPDATE(0, RK3399_DSI0_TURNREQUEST |
+ RK3399_DSI0_TURNDISABLE |
+ RK3399_DSI0_FORCETXSTOPMODE |
+ RK3399_DSI0_FORCERXMODE),
+
+ .flags = DW_MIPI_NEEDS_PHY_CFG_CLK | DW_MIPI_NEEDS_GRF_CLK,
+ .max_data_lanes = 4,
+ },
+ {
+ .reg = 0xff968000,
+ .lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3399_DSI1_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3399_DSI1_LCDC_SEL,
+ RK3399_DSI1_LCDC_SEL),
+
+ .lanecfg1_grf_reg = RK3399_GRF_SOC_CON23,
+ .lanecfg1 = HIWORD_UPDATE(0, RK3399_DSI1_TURNDISABLE |
+ RK3399_DSI1_FORCETXSTOPMODE |
+ RK3399_DSI1_FORCERXMODE |
+ RK3399_DSI1_ENABLE),
+
+ .lanecfg2_grf_reg = RK3399_GRF_SOC_CON24,
+ .lanecfg2 = HIWORD_UPDATE(RK3399_TXRX_MASTERSLAVEZ |
+ RK3399_TXRX_ENABLECLK,
+ RK3399_TXRX_MASTERSLAVEZ |
+ RK3399_TXRX_ENABLECLK |
+ RK3399_TXRX_BASEDIR),
+
+ .enable_grf_reg = RK3399_GRF_SOC_CON23,
+ .enable = HIWORD_UPDATE(RK3399_DSI1_ENABLE, RK3399_DSI1_ENABLE),
+
+ .flags = DW_MIPI_NEEDS_PHY_CFG_CLK | DW_MIPI_NEEDS_GRF_CLK,
+ .max_data_lanes = 4,
+ },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id dw_mipi_dsi_rockchip_dt_ids[] = {
+ {
+ .compatible = "rockchip,px30-mipi-dsi",
+ .data = &px30_chip_data,
+ }, {
+ .compatible = "rockchip,rk3288-mipi-dsi",
+ .data = &rk3288_chip_data,
+ }, {
+ .compatible = "rockchip,rk3399-mipi-dsi",
+ .data = &rk3399_chip_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dw_mipi_dsi_rockchip_dt_ids);
+
+struct platform_driver dw_mipi_dsi_rockchip_driver = {
+ .probe = dw_mipi_dsi_rockchip_probe,
+ .remove = dw_mipi_dsi_rockchip_remove,
+ .driver = {
+ .of_match_table = dw_mipi_dsi_rockchip_dt_ids,
+ .pm = &dw_mipi_dsi_rockchip_pm_ops,
+ .name = "dw-mipi-dsi-rockchip",
+ /*
+ * For dual-DSI display, one DSI pokes at the other DSI's
+ * drvdata in dw_mipi_dsi_rockchip_find_second(). This is not
+ * safe for asynchronous probe.
+ */
+ .probe_type = PROBE_FORCE_SYNCHRONOUS,
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
new file mode 100644
index 000000000..515e6f187
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+#define RK3228_GRF_SOC_CON2 0x0408
+#define RK3228_HDMI_SDAIN_MSK BIT(14)
+#define RK3228_HDMI_SCLIN_MSK BIT(13)
+#define RK3228_GRF_SOC_CON6 0x0418
+#define RK3228_HDMI_HPD_VSEL BIT(6)
+#define RK3228_HDMI_SDA_VSEL BIT(5)
+#define RK3228_HDMI_SCL_VSEL BIT(4)
+
+#define RK3288_GRF_SOC_CON6 0x025C
+#define RK3288_HDMI_LCDC_SEL BIT(4)
+#define RK3328_GRF_SOC_CON2 0x0408
+
+#define RK3328_HDMI_SDAIN_MSK BIT(11)
+#define RK3328_HDMI_SCLIN_MSK BIT(10)
+#define RK3328_HDMI_HPD_IOE BIT(2)
+#define RK3328_GRF_SOC_CON3 0x040c
+/* need to be unset if hdmi or i2c should control voltage */
+#define RK3328_HDMI_SDA5V_GRF BIT(15)
+#define RK3328_HDMI_SCL5V_GRF BIT(14)
+#define RK3328_HDMI_HPD5V_GRF BIT(13)
+#define RK3328_HDMI_CEC5V_GRF BIT(12)
+#define RK3328_GRF_SOC_CON4 0x0410
+#define RK3328_HDMI_HPD_SARADC BIT(13)
+#define RK3328_HDMI_CEC_5V BIT(11)
+#define RK3328_HDMI_SDA_5V BIT(10)
+#define RK3328_HDMI_SCL_5V BIT(9)
+#define RK3328_HDMI_HPD_5V BIT(8)
+
+#define RK3399_GRF_SOC_CON20 0x6250
+#define RK3399_HDMI_LCDC_SEL BIT(6)
+
+#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)
+
+/**
+ * struct rockchip_hdmi_chip_data - splite the grf setting of kind of chips
+ * @lcdsel_grf_reg: grf register offset of lcdc select
+ * @lcdsel_big: reg value of selecting vop big for HDMI
+ * @lcdsel_lit: reg value of selecting vop little for HDMI
+ */
+struct rockchip_hdmi_chip_data {
+ int lcdsel_grf_reg;
+ u32 lcdsel_big;
+ u32 lcdsel_lit;
+};
+
+struct rockchip_hdmi {
+ struct device *dev;
+ struct regmap *regmap;
+ struct drm_encoder encoder;
+ const struct rockchip_hdmi_chip_data *chip_data;
+ struct clk *vpll_clk;
+ struct clk *grf_clk;
+ struct dw_hdmi *hdmi;
+ struct phy *phy;
+};
+
+#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x)
+
+static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
+ {
+ 27000000, {
+ { 0x00b3, 0x0000},
+ { 0x2153, 0x0000},
+ { 0x40f3, 0x0000}
+ },
+ }, {
+ 36000000, {
+ { 0x00b3, 0x0000},
+ { 0x2153, 0x0000},
+ { 0x40f3, 0x0000}
+ },
+ }, {
+ 40000000, {
+ { 0x00b3, 0x0000},
+ { 0x2153, 0x0000},
+ { 0x40f3, 0x0000}
+ },
+ }, {
+ 54000000, {
+ { 0x0072, 0x0001},
+ { 0x2142, 0x0001},
+ { 0x40a2, 0x0001},
+ },
+ }, {
+ 65000000, {
+ { 0x0072, 0x0001},
+ { 0x2142, 0x0001},
+ { 0x40a2, 0x0001},
+ },
+ }, {
+ 66000000, {
+ { 0x013e, 0x0003},
+ { 0x217e, 0x0002},
+ { 0x4061, 0x0002}
+ },
+ }, {
+ 74250000, {
+ { 0x0072, 0x0001},
+ { 0x2145, 0x0002},
+ { 0x4061, 0x0002}
+ },
+ }, {
+ 83500000, {
+ { 0x0072, 0x0001},
+ },
+ }, {
+ 108000000, {
+ { 0x0051, 0x0002},
+ { 0x2145, 0x0002},
+ { 0x4061, 0x0002}
+ },
+ }, {
+ 106500000, {
+ { 0x0051, 0x0002},
+ { 0x2145, 0x0002},
+ { 0x4061, 0x0002}
+ },
+ }, {
+ 146250000, {
+ { 0x0051, 0x0002},
+ { 0x2145, 0x0002},
+ { 0x4061, 0x0002}
+ },
+ }, {
+ 148500000, {
+ { 0x0051, 0x0003},
+ { 0x214c, 0x0003},
+ { 0x4064, 0x0003}
+ },
+ }, {
+ ~0UL, {
+ { 0x00a0, 0x000a },
+ { 0x2001, 0x000f },
+ { 0x4002, 0x000f },
+ },
+ }
+};
+
+static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
+ /* pixelclk bpp8 bpp10 bpp12 */
+ {
+ 40000000, { 0x0018, 0x0018, 0x0018 },
+ }, {
+ 65000000, { 0x0028, 0x0028, 0x0028 },
+ }, {
+ 66000000, { 0x0038, 0x0038, 0x0038 },
+ }, {
+ 74250000, { 0x0028, 0x0038, 0x0038 },
+ }, {
+ 83500000, { 0x0028, 0x0038, 0x0038 },
+ }, {
+ 146250000, { 0x0038, 0x0038, 0x0038 },
+ }, {
+ 148500000, { 0x0000, 0x0038, 0x0038 },
+ }, {
+ ~0UL, { 0x0000, 0x0000, 0x0000},
+ }
+};
+
+static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
+ /*pixelclk symbol term vlev*/
+ { 74250000, 0x8009, 0x0004, 0x0272},
+ { 148500000, 0x802b, 0x0004, 0x028d},
+ { 297000000, 0x8039, 0x0005, 0x028d},
+ { ~0UL, 0x0000, 0x0000, 0x0000}
+};
+
+static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
+{
+ struct device_node *np = hdmi->dev->of_node;
+
+ hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+ if (IS_ERR(hdmi->regmap)) {
+ DRM_DEV_ERROR(hdmi->dev, "Unable to get rockchip,grf\n");
+ return PTR_ERR(hdmi->regmap);
+ }
+
+ hdmi->vpll_clk = devm_clk_get(hdmi->dev, "vpll");
+ if (PTR_ERR(hdmi->vpll_clk) == -ENOENT) {
+ hdmi->vpll_clk = NULL;
+ } else if (PTR_ERR(hdmi->vpll_clk) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (IS_ERR(hdmi->vpll_clk)) {
+ DRM_DEV_ERROR(hdmi->dev, "failed to get grf clock\n");
+ return PTR_ERR(hdmi->vpll_clk);
+ }
+
+ hdmi->grf_clk = devm_clk_get(hdmi->dev, "grf");
+ if (PTR_ERR(hdmi->grf_clk) == -ENOENT) {
+ hdmi->grf_clk = NULL;
+ } else if (PTR_ERR(hdmi->grf_clk) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (IS_ERR(hdmi->grf_clk)) {
+ DRM_DEV_ERROR(hdmi->dev, "failed to get grf clock\n");
+ return PTR_ERR(hdmi->grf_clk);
+ }
+
+ return 0;
+}
+
+static enum drm_mode_status
+dw_hdmi_rockchip_mode_valid(struct dw_hdmi *hdmi, void *data,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
+ int pclk = mode->clock * 1000;
+ bool valid = false;
+ int i;
+
+ for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
+ if (pclk == mpll_cfg[i].mpixelclock) {
+ valid = true;
+ break;
+ }
+ }
+
+ return (valid) ? MODE_OK : MODE_BAD;
+}
+
+static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
+{
+}
+
+static bool
+dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ return true;
+}
+
+static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+
+ clk_set_rate(hdmi->vpll_clk, adj_mode->clock * 1000);
+}
+
+static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
+{
+ struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+ u32 val;
+ int ret;
+
+ if (hdmi->chip_data->lcdsel_grf_reg < 0)
+ return;
+
+ ret = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
+ if (ret)
+ val = hdmi->chip_data->lcdsel_lit;
+ else
+ val = hdmi->chip_data->lcdsel_big;
+
+ ret = clk_prepare_enable(hdmi->grf_clk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(hdmi->dev, "failed to enable grfclk %d\n", ret);
+ return;
+ }
+
+ ret = regmap_write(hdmi->regmap, hdmi->chip_data->lcdsel_grf_reg, val);
+ if (ret != 0)
+ DRM_DEV_ERROR(hdmi->dev, "Could not write to GRF: %d\n", ret);
+
+ clk_disable_unprepare(hdmi->grf_clk);
+ DRM_DEV_DEBUG(hdmi->dev, "vop %s output to hdmi\n",
+ ret ? "LIT" : "BIG");
+}
+
+static int
+dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+
+ s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
+ s->output_type = DRM_MODE_CONNECTOR_HDMIA;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
+ .mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
+ .mode_set = dw_hdmi_rockchip_encoder_mode_set,
+ .enable = dw_hdmi_rockchip_encoder_enable,
+ .disable = dw_hdmi_rockchip_encoder_disable,
+ .atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
+};
+
+static int dw_hdmi_rockchip_genphy_init(struct dw_hdmi *dw_hdmi, void *data,
+ const struct drm_display_info *display,
+ const struct drm_display_mode *mode)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+ return phy_power_on(hdmi->phy);
+}
+
+static void dw_hdmi_rockchip_genphy_disable(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+ phy_power_off(hdmi->phy);
+}
+
+static void dw_hdmi_rk3228_setup_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+ dw_hdmi_phy_setup_hpd(dw_hdmi, data);
+
+ regmap_write(hdmi->regmap,
+ RK3228_GRF_SOC_CON6,
+ HIWORD_UPDATE(RK3228_HDMI_HPD_VSEL | RK3228_HDMI_SDA_VSEL |
+ RK3228_HDMI_SCL_VSEL,
+ RK3228_HDMI_HPD_VSEL | RK3228_HDMI_SDA_VSEL |
+ RK3228_HDMI_SCL_VSEL));
+
+ regmap_write(hdmi->regmap,
+ RK3228_GRF_SOC_CON2,
+ HIWORD_UPDATE(RK3228_HDMI_SDAIN_MSK | RK3228_HDMI_SCLIN_MSK,
+ RK3228_HDMI_SDAIN_MSK | RK3228_HDMI_SCLIN_MSK));
+}
+
+static enum drm_connector_status
+dw_hdmi_rk3328_read_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+ enum drm_connector_status status;
+
+ status = dw_hdmi_phy_read_hpd(dw_hdmi, data);
+
+ if (status == connector_status_connected)
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON4,
+ HIWORD_UPDATE(RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V,
+ RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V));
+ else
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON4,
+ HIWORD_UPDATE(0, RK3328_HDMI_SDA_5V |
+ RK3328_HDMI_SCL_5V));
+ return status;
+}
+
+static void dw_hdmi_rk3328_setup_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+
+ dw_hdmi_phy_setup_hpd(dw_hdmi, data);
+
+ /* Enable and map pins to 3V grf-controlled io-voltage */
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON4,
+ HIWORD_UPDATE(0, RK3328_HDMI_HPD_SARADC | RK3328_HDMI_CEC_5V |
+ RK3328_HDMI_SDA_5V | RK3328_HDMI_SCL_5V |
+ RK3328_HDMI_HPD_5V));
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON3,
+ HIWORD_UPDATE(0, RK3328_HDMI_SDA5V_GRF | RK3328_HDMI_SCL5V_GRF |
+ RK3328_HDMI_HPD5V_GRF |
+ RK3328_HDMI_CEC5V_GRF));
+ regmap_write(hdmi->regmap,
+ RK3328_GRF_SOC_CON2,
+ HIWORD_UPDATE(RK3328_HDMI_SDAIN_MSK | RK3328_HDMI_SCLIN_MSK,
+ RK3328_HDMI_SDAIN_MSK | RK3328_HDMI_SCLIN_MSK |
+ RK3328_HDMI_HPD_IOE));
+}
+
+static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = {
+ .init = dw_hdmi_rockchip_genphy_init,
+ .disable = dw_hdmi_rockchip_genphy_disable,
+ .read_hpd = dw_hdmi_phy_read_hpd,
+ .update_hpd = dw_hdmi_phy_update_hpd,
+ .setup_hpd = dw_hdmi_rk3228_setup_hpd,
+};
+
+static struct rockchip_hdmi_chip_data rk3228_chip_data = {
+ .lcdsel_grf_reg = -1,
+};
+
+static const struct dw_hdmi_plat_data rk3228_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .mpll_cfg = rockchip_mpll_cfg,
+ .cur_ctr = rockchip_cur_ctr,
+ .phy_config = rockchip_phy_config,
+ .phy_data = &rk3228_chip_data,
+ .phy_ops = &rk3228_hdmi_phy_ops,
+ .phy_name = "inno_dw_hdmi_phy2",
+ .phy_force_vendor = true,
+};
+
+static struct rockchip_hdmi_chip_data rk3288_chip_data = {
+ .lcdsel_grf_reg = RK3288_GRF_SOC_CON6,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3288_HDMI_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3288_HDMI_LCDC_SEL, RK3288_HDMI_LCDC_SEL),
+};
+
+static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .mpll_cfg = rockchip_mpll_cfg,
+ .cur_ctr = rockchip_cur_ctr,
+ .phy_config = rockchip_phy_config,
+ .phy_data = &rk3288_chip_data,
+};
+
+static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = {
+ .init = dw_hdmi_rockchip_genphy_init,
+ .disable = dw_hdmi_rockchip_genphy_disable,
+ .read_hpd = dw_hdmi_rk3328_read_hpd,
+ .update_hpd = dw_hdmi_phy_update_hpd,
+ .setup_hpd = dw_hdmi_rk3328_setup_hpd,
+};
+
+static struct rockchip_hdmi_chip_data rk3328_chip_data = {
+ .lcdsel_grf_reg = -1,
+};
+
+static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .mpll_cfg = rockchip_mpll_cfg,
+ .cur_ctr = rockchip_cur_ctr,
+ .phy_config = rockchip_phy_config,
+ .phy_data = &rk3328_chip_data,
+ .phy_ops = &rk3328_hdmi_phy_ops,
+ .phy_name = "inno_dw_hdmi_phy2",
+ .phy_force_vendor = true,
+ .use_drm_infoframe = true,
+};
+
+static struct rockchip_hdmi_chip_data rk3399_chip_data = {
+ .lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3399_HDMI_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3399_HDMI_LCDC_SEL, RK3399_HDMI_LCDC_SEL),
+};
+
+static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .mpll_cfg = rockchip_mpll_cfg,
+ .cur_ctr = rockchip_cur_ctr,
+ .phy_config = rockchip_phy_config,
+ .phy_data = &rk3399_chip_data,
+ .use_drm_infoframe = true,
+};
+
+static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
+ { .compatible = "rockchip,rk3228-dw-hdmi",
+ .data = &rk3228_hdmi_drv_data
+ },
+ { .compatible = "rockchip,rk3288-dw-hdmi",
+ .data = &rk3288_hdmi_drv_data
+ },
+ { .compatible = "rockchip,rk3328-dw-hdmi",
+ .data = &rk3328_hdmi_drv_data
+ },
+ { .compatible = "rockchip,rk3399-dw-hdmi",
+ .data = &rk3399_hdmi_drv_data
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids);
+
+static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_hdmi_plat_data *plat_data;
+ const struct of_device_id *match;
+ struct drm_device *drm = data;
+ struct drm_encoder *encoder;
+ struct rockchip_hdmi *hdmi;
+ int ret;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi)
+ return -ENOMEM;
+
+ match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
+ plat_data = devm_kmemdup(&pdev->dev, match->data,
+ sizeof(*plat_data), GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+
+ hdmi->dev = &pdev->dev;
+ hdmi->chip_data = plat_data->phy_data;
+ plat_data->phy_data = hdmi;
+ encoder = &hdmi->encoder;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+ /*
+ * If we failed to find the CRTC(s) which this encoder is
+ * supposed to be connected to, it's because the CRTC has
+ * not been registered yet. Defer probing, and hope that
+ * the required CRTC is added later.
+ */
+ if (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ ret = rockchip_hdmi_parse_dt(hdmi);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n");
+ return ret;
+ }
+
+ hdmi->phy = devm_phy_optional_get(dev, "hdmi");
+ if (IS_ERR(hdmi->phy)) {
+ ret = PTR_ERR(hdmi->phy);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(hdmi->vpll_clk);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI vpll: %d\n",
+ ret);
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+
+ platform_set_drvdata(pdev, hdmi);
+
+ hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
+
+ /*
+ * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+ * which would have called the encoder cleanup. Do it manually.
+ */
+ if (IS_ERR(hdmi->hdmi)) {
+ ret = PTR_ERR(hdmi->hdmi);
+ drm_encoder_cleanup(encoder);
+ clk_disable_unprepare(hdmi->vpll_clk);
+ }
+
+ return ret;
+}
+
+static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
+
+ dw_hdmi_unbind(hdmi->hdmi);
+ clk_disable_unprepare(hdmi->vpll_clk);
+}
+
+static const struct component_ops dw_hdmi_rockchip_ops = {
+ .bind = dw_hdmi_rockchip_bind,
+ .unbind = dw_hdmi_rockchip_unbind,
+};
+
+static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
+}
+
+static int dw_hdmi_rockchip_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
+
+ return 0;
+}
+
+static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev)
+{
+ struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
+
+ dw_hdmi_resume(hdmi->hdmi);
+
+ return 0;
+}
+
+static const struct dev_pm_ops dw_hdmi_rockchip_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume)
+};
+
+struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
+ .probe = dw_hdmi_rockchip_probe,
+ .remove = dw_hdmi_rockchip_remove,
+ .driver = {
+ .name = "dwhdmi-rockchip",
+ .pm = &dw_hdmi_rockchip_pm,
+ .of_match_table = dw_hdmi_rockchip_dt_ids,
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/rockchip/inno_hdmi.c
new file mode 100644
index 000000000..78120da5e
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/inno_hdmi.c
@@ -0,0 +1,935 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Zheng Yang <zhengyang@rock-chips.com>
+ * Yakir Yang <ykk@rock-chips.com>
+ */
+
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+#include "inno_hdmi.h"
+
+#define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x)
+
+struct hdmi_data_info {
+ int vic;
+ bool sink_is_hdmi;
+ bool sink_has_audio;
+ unsigned int enc_in_format;
+ unsigned int enc_out_format;
+ unsigned int colorimetry;
+};
+
+struct inno_hdmi_i2c {
+ struct i2c_adapter adap;
+
+ u8 ddc_addr;
+ u8 segment_addr;
+
+ struct mutex lock;
+ struct completion cmp;
+};
+
+struct inno_hdmi {
+ struct device *dev;
+ struct drm_device *drm_dev;
+
+ int irq;
+ struct clk *pclk;
+ void __iomem *regs;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct inno_hdmi_i2c *i2c;
+ struct i2c_adapter *ddc;
+
+ unsigned int tmds_rate;
+
+ struct hdmi_data_info hdmi_data;
+ struct drm_display_mode previous_mode;
+};
+
+enum {
+ CSC_ITU601_16_235_TO_RGB_0_255_8BIT,
+ CSC_ITU601_0_255_TO_RGB_0_255_8BIT,
+ CSC_ITU709_16_235_TO_RGB_0_255_8BIT,
+ CSC_RGB_0_255_TO_ITU601_16_235_8BIT,
+ CSC_RGB_0_255_TO_ITU709_16_235_8BIT,
+ CSC_RGB_0_255_TO_RGB_16_235_8BIT,
+};
+
+static const char coeff_csc[][24] = {
+ /*
+ * YUV2RGB:601 SD mode(Y[16:235], UV[16:240], RGB[0:255]):
+ * R = 1.164*Y + 1.596*V - 204
+ * G = 1.164*Y - 0.391*U - 0.813*V + 154
+ * B = 1.164*Y + 2.018*U - 258
+ */
+ {
+ 0x04, 0xa7, 0x00, 0x00, 0x06, 0x62, 0x02, 0xcc,
+ 0x04, 0xa7, 0x11, 0x90, 0x13, 0x40, 0x00, 0x9a,
+ 0x04, 0xa7, 0x08, 0x12, 0x00, 0x00, 0x03, 0x02
+ },
+ /*
+ * YUV2RGB:601 SD mode(YUV[0:255],RGB[0:255]):
+ * R = Y + 1.402*V - 248
+ * G = Y - 0.344*U - 0.714*V + 135
+ * B = Y + 1.772*U - 227
+ */
+ {
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x9b, 0x02, 0xf8,
+ 0x04, 0x00, 0x11, 0x60, 0x12, 0xdb, 0x00, 0x87,
+ 0x04, 0x00, 0x07, 0x16, 0x00, 0x00, 0x02, 0xe3
+ },
+ /*
+ * YUV2RGB:709 HD mode(Y[16:235],UV[16:240],RGB[0:255]):
+ * R = 1.164*Y + 1.793*V - 248
+ * G = 1.164*Y - 0.213*U - 0.534*V + 77
+ * B = 1.164*Y + 2.115*U - 289
+ */
+ {
+ 0x04, 0xa7, 0x00, 0x00, 0x07, 0x2c, 0x02, 0xf8,
+ 0x04, 0xa7, 0x10, 0xda, 0x12, 0x22, 0x00, 0x4d,
+ 0x04, 0xa7, 0x08, 0x74, 0x00, 0x00, 0x03, 0x21
+ },
+
+ /*
+ * RGB2YUV:601 SD mode:
+ * Cb = -0.291G - 0.148R + 0.439B + 128
+ * Y = 0.504G + 0.257R + 0.098B + 16
+ * Cr = -0.368G + 0.439R - 0.071B + 128
+ */
+ {
+ 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80,
+ 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e,
+ 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80
+ },
+ /*
+ * RGB2YUV:709 HD mode:
+ * Cb = - 0.338G - 0.101R + 0.439B + 128
+ * Y = 0.614G + 0.183R + 0.062B + 16
+ * Cr = - 0.399G + 0.439R - 0.040B + 128
+ */
+ {
+ 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80,
+ 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10,
+ 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80
+ },
+ /*
+ * RGB[0:255]2RGB[16:235]:
+ * R' = R x (235-16)/255 + 16;
+ * G' = G x (235-16)/255 + 16;
+ * B' = B x (235-16)/255 + 16;
+ */
+ {
+ 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10,
+ 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10
+ },
+};
+
+static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset)
+{
+ return readl_relaxed(hdmi->regs + (offset) * 0x04);
+}
+
+static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val)
+{
+ writel_relaxed(val, hdmi->regs + (offset) * 0x04);
+}
+
+static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset,
+ u32 msk, u32 val)
+{
+ u8 temp = hdmi_readb(hdmi, offset) & ~msk;
+
+ temp |= val & msk;
+ hdmi_writeb(hdmi, offset, temp);
+}
+
+static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi)
+{
+ int ddc_bus_freq;
+
+ ddc_bus_freq = (hdmi->tmds_rate >> 2) / HDMI_SCL_RATE;
+
+ hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
+ hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
+
+ /* Clear the EDID interrupt flag and mute the interrupt */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+}
+
+static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable)
+{
+ if (enable)
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON);
+ else
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF);
+}
+
+static void inno_hdmi_set_pwr_mode(struct inno_hdmi *hdmi, int mode)
+{
+ switch (mode) {
+ case NORMAL:
+ inno_hdmi_sys_power(hdmi, false);
+
+ hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x6f);
+ hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0xbb);
+
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14);
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10);
+ hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f);
+ hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01);
+
+ inno_hdmi_sys_power(hdmi, true);
+ break;
+
+ case LOWER_PWR:
+ inno_hdmi_sys_power(hdmi, false);
+ hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
+
+ break;
+
+ default:
+ DRM_DEV_ERROR(hdmi->dev, "Unknown power mode %d\n", mode);
+ }
+}
+
+static void inno_hdmi_reset(struct inno_hdmi *hdmi)
+{
+ u32 val;
+ u32 msk;
+
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL);
+ udelay(100);
+
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG);
+ udelay(100);
+
+ msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL;
+ val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH;
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val);
+
+ inno_hdmi_set_pwr_mode(hdmi, NORMAL);
+}
+
+static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, int setup_rc,
+ union hdmi_infoframe *frame, u32 frame_index,
+ u32 mask, u32 disable, u32 enable)
+{
+ if (mask)
+ hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, disable);
+
+ hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, frame_index);
+
+ if (setup_rc >= 0) {
+ u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
+ ssize_t rc, i;
+
+ rc = hdmi_infoframe_pack(frame, packed_frame,
+ sizeof(packed_frame));
+ if (rc < 0)
+ return rc;
+
+ for (i = 0; i < rc; i++)
+ hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i,
+ packed_frame[i]);
+
+ if (mask)
+ hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, enable);
+ }
+
+ return setup_rc;
+}
+
+static int inno_hdmi_config_video_vsi(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ union hdmi_infoframe frame;
+ int rc;
+
+ rc = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
+ &hdmi->connector,
+ mode);
+
+ return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_VSI,
+ m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1));
+}
+
+static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ union hdmi_infoframe frame;
+ int rc;
+
+ rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
+ &hdmi->connector,
+ mode);
+
+ if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444)
+ frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
+ else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422)
+ frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
+ else
+ frame.avi.colorspace = HDMI_COLORSPACE_RGB;
+
+ return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AVI, 0, 0, 0);
+}
+
+static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi)
+{
+ struct hdmi_data_info *data = &hdmi->hdmi_data;
+ int c0_c2_change = 0;
+ int csc_enable = 0;
+ int csc_mode = 0;
+ int auto_csc = 0;
+ int value;
+ int i;
+
+ /* Input video mode is SDR RGB24bit, data enable signal from external */
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL |
+ v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444));
+
+ /* Input color hardcode to RGB, and output color hardcode to RGB888 */
+ value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) |
+ v_VIDEO_OUTPUT_COLOR(0) |
+ v_VIDEO_INPUT_CSP(0);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value);
+
+ if (data->enc_in_format == data->enc_out_format) {
+ if ((data->enc_in_format == HDMI_COLORSPACE_RGB) ||
+ (data->enc_in_format >= HDMI_COLORSPACE_YUV444)) {
+ value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
+
+ hdmi_modb(hdmi, HDMI_VIDEO_CONTRL,
+ m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP,
+ v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) |
+ v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE));
+ return 0;
+ }
+ }
+
+ if (data->colorimetry == HDMI_COLORIMETRY_ITU_601) {
+ if ((data->enc_in_format == HDMI_COLORSPACE_RGB) &&
+ (data->enc_out_format == HDMI_COLORSPACE_YUV444)) {
+ csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT;
+ auto_csc = AUTO_CSC_DISABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_ENABLE;
+ } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
+ (data->enc_out_format == HDMI_COLORSPACE_RGB)) {
+ csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT;
+ auto_csc = AUTO_CSC_ENABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_DISABLE;
+ }
+ } else {
+ if ((data->enc_in_format == HDMI_COLORSPACE_RGB) &&
+ (data->enc_out_format == HDMI_COLORSPACE_YUV444)) {
+ csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT;
+ auto_csc = AUTO_CSC_DISABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_ENABLE;
+ } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
+ (data->enc_out_format == HDMI_COLORSPACE_RGB)) {
+ csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT;
+ auto_csc = AUTO_CSC_ENABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_DISABLE;
+ }
+ }
+
+ for (i = 0; i < 24; i++)
+ hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i,
+ coeff_csc[csc_mode][i]);
+
+ value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
+ hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC |
+ m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) |
+ v_VIDEO_C0_C2_SWAP(c0_c2_change));
+
+ return 0;
+}
+
+static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ int value;
+
+ /* Set detail external video timing polarity and interlace mode */
+ value = v_EXTERANL_VIDEO(1);
+ value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+ v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0);
+ value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+ v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0);
+ value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
+ v_INETLACE(1) : v_INETLACE(0);
+ hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value);
+
+ /* Set detail external video timing */
+ value = mode->htotal;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF);
+
+ value = mode->htotal - mode->hdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF);
+
+ value = mode->hsync_start - mode->hdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF);
+
+ value = mode->hsync_end - mode->hsync_start;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF);
+
+ value = mode->vtotal;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF);
+
+ value = mode->vtotal - mode->vdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF);
+
+ value = mode->vsync_start - mode->vdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF);
+
+ value = mode->vsync_end - mode->vsync_start;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF);
+
+ hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e);
+ hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c);
+ hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01);
+
+ return 0;
+}
+
+static int inno_hdmi_setup(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
+
+ hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB;
+ hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
+
+ if ((hdmi->hdmi_data.vic == 6) || (hdmi->hdmi_data.vic == 7) ||
+ (hdmi->hdmi_data.vic == 21) || (hdmi->hdmi_data.vic == 22) ||
+ (hdmi->hdmi_data.vic == 2) || (hdmi->hdmi_data.vic == 3) ||
+ (hdmi->hdmi_data.vic == 17) || (hdmi->hdmi_data.vic == 18))
+ hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601;
+ else
+ hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
+
+ /* Mute video and audio output */
+ hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
+ v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1));
+
+ /* Set HDMI Mode */
+ hdmi_writeb(hdmi, HDMI_HDCP_CTRL,
+ v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi));
+
+ inno_hdmi_config_video_timing(hdmi, mode);
+
+ inno_hdmi_config_video_csc(hdmi);
+
+ if (hdmi->hdmi_data.sink_is_hdmi) {
+ inno_hdmi_config_video_avi(hdmi, mode);
+ inno_hdmi_config_video_vsi(hdmi, mode);
+ }
+
+ /*
+ * When IP controller have configured to an accurate video
+ * timing, then the TMDS clock source would be switched to
+ * DCLK_LCDC, so we need to init the TMDS rate to mode pixel
+ * clock rate, and reconfigure the DDC clock.
+ */
+ hdmi->tmds_rate = mode->clock * 1000;
+ inno_hdmi_i2c_init(hdmi);
+
+ /* Unmute video and audio output */
+ hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
+ v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0));
+
+ return 0;
+}
+
+static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+
+ inno_hdmi_setup(hdmi, adj_mode);
+
+ /* Store the display mode for plugin/DPMS poweron events */
+ drm_mode_copy(&hdmi->previous_mode, adj_mode);
+}
+
+static void inno_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+
+ inno_hdmi_set_pwr_mode(hdmi, NORMAL);
+}
+
+static void inno_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+
+ inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR);
+}
+
+static bool inno_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ return true;
+}
+
+static int
+inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+
+ s->output_mode = ROCKCHIP_OUT_MODE_P888;
+ s->output_type = DRM_MODE_CONNECTOR_HDMIA;
+
+ return 0;
+}
+
+static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = {
+ .enable = inno_hdmi_encoder_enable,
+ .disable = inno_hdmi_encoder_disable,
+ .mode_fixup = inno_hdmi_encoder_mode_fixup,
+ .mode_set = inno_hdmi_encoder_mode_set,
+ .atomic_check = inno_hdmi_encoder_atomic_check,
+};
+
+static enum drm_connector_status
+inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(connector);
+
+ return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
+ connector_status_connected : connector_status_disconnected;
+}
+
+static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(connector);
+ struct edid *edid;
+ int ret = 0;
+
+ if (!hdmi->ddc)
+ return 0;
+
+ edid = drm_get_edid(connector, hdmi->ddc);
+ if (edid) {
+ hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+ hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid);
+ drm_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return ret;
+}
+
+static enum drm_mode_status
+inno_hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static int
+inno_hdmi_probe_single_connector_modes(struct drm_connector *connector,
+ uint32_t maxX, uint32_t maxY)
+{
+ return drm_helper_probe_single_connector_modes(connector, 1920, 1080);
+}
+
+static void inno_hdmi_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs inno_hdmi_connector_funcs = {
+ .fill_modes = inno_hdmi_probe_single_connector_modes,
+ .detect = inno_hdmi_connector_detect,
+ .destroy = inno_hdmi_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
+ .get_modes = inno_hdmi_connector_get_modes,
+ .mode_valid = inno_hdmi_connector_mode_valid,
+};
+
+static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi)
+{
+ struct drm_encoder *encoder = &hdmi->encoder;
+ struct device *dev = hdmi->dev;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+
+ /*
+ * If we failed to find the CRTC(s) which this encoder is
+ * supposed to be connected to, it's because the CRTC has
+ * not been registered yet. Defer probing, and hope that
+ * the required CRTC is added later.
+ */
+ if (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+
+ hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+ drm_connector_helper_add(&hdmi->connector,
+ &inno_hdmi_connector_helper_funcs);
+ drm_connector_init_with_ddc(drm, &hdmi->connector,
+ &inno_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA,
+ hdmi->ddc);
+
+ drm_connector_attach_encoder(&hdmi->connector, encoder);
+
+ return 0;
+}
+
+static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi)
+{
+ struct inno_hdmi_i2c *i2c = hdmi->i2c;
+ u8 stat;
+
+ stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1);
+ if (!(stat & m_INT_EDID_READY))
+ return IRQ_NONE;
+
+ /* Clear HDMI EDID interrupt flag */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+
+ complete(&i2c->cmp);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id)
+{
+ struct inno_hdmi *hdmi = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ u8 interrupt;
+
+ if (hdmi->i2c)
+ ret = inno_hdmi_i2c_irq(hdmi);
+
+ interrupt = hdmi_readb(hdmi, HDMI_STATUS);
+ if (interrupt & m_INT_HOTPLUG) {
+ hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
+}
+
+static irqreturn_t inno_hdmi_irq(int irq, void *dev_id)
+{
+ struct inno_hdmi *hdmi = dev_id;
+
+ drm_helper_hpd_irq_event(hdmi->connector.dev);
+
+ return IRQ_HANDLED;
+}
+
+static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
+{
+ int length = msgs->len;
+ u8 *buf = msgs->buf;
+ int ret;
+
+ ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10);
+ if (!ret)
+ return -EAGAIN;
+
+ while (length--)
+ *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR);
+
+ return 0;
+}
+
+static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
+{
+ /*
+ * The DDC module only support read EDID message, so
+ * we assume that each word write to this i2c adapter
+ * should be the offset of EDID word address.
+ */
+ if ((msgs->len != 1) ||
+ ((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR)))
+ return -EINVAL;
+
+ reinit_completion(&hdmi->i2c->cmp);
+
+ if (msgs->addr == DDC_SEGMENT_ADDR)
+ hdmi->i2c->segment_addr = msgs->buf[0];
+ if (msgs->addr == DDC_ADDR)
+ hdmi->i2c->ddc_addr = msgs->buf[0];
+
+ /* Set edid fifo first addr */
+ hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00);
+
+ /* Set edid word address 0x00/0x80 */
+ hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
+
+ /* Set edid segment pointer */
+ hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
+
+ return 0;
+}
+
+static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct inno_hdmi *hdmi = i2c_get_adapdata(adap);
+ struct inno_hdmi_i2c *i2c = hdmi->i2c;
+ int i, ret = 0;
+
+ mutex_lock(&i2c->lock);
+
+ /* Clear the EDID interrupt flag and unmute the interrupt */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY);
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+
+ for (i = 0; i < num; i++) {
+ DRM_DEV_DEBUG(hdmi->dev,
+ "xfer: num: %d/%d, len: %d, flags: %#x\n",
+ i + 1, num, msgs[i].len, msgs[i].flags);
+
+ if (msgs[i].flags & I2C_M_RD)
+ ret = inno_hdmi_i2c_read(hdmi, &msgs[i]);
+ else
+ ret = inno_hdmi_i2c_write(hdmi, &msgs[i]);
+
+ if (ret < 0)
+ break;
+ }
+
+ if (!ret)
+ ret = num;
+
+ /* Mute HDMI EDID interrupt */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
+
+ mutex_unlock(&i2c->lock);
+
+ return ret;
+}
+
+static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm inno_hdmi_algorithm = {
+ .master_xfer = inno_hdmi_i2c_xfer,
+ .functionality = inno_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi)
+{
+ struct i2c_adapter *adap;
+ struct inno_hdmi_i2c *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&i2c->lock);
+ init_completion(&i2c->cmp);
+
+ adap = &i2c->adap;
+ adap->class = I2C_CLASS_DDC;
+ adap->owner = THIS_MODULE;
+ adap->dev.parent = hdmi->dev;
+ adap->dev.of_node = hdmi->dev->of_node;
+ adap->algo = &inno_hdmi_algorithm;
+ strlcpy(adap->name, "Inno HDMI", sizeof(adap->name));
+ i2c_set_adapdata(adap, hdmi);
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+ devm_kfree(hdmi->dev, i2c);
+ return ERR_PTR(ret);
+ }
+
+ hdmi->i2c = i2c;
+
+ DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+ return adap;
+}
+
+static int inno_hdmi_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct drm_device *drm = data;
+ struct inno_hdmi *hdmi;
+ struct resource *iores;
+ int irq;
+ int ret;
+
+ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi)
+ return -ENOMEM;
+
+ hdmi->dev = dev;
+ hdmi->drm_dev = drm;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdmi->regs = devm_ioremap_resource(dev, iores);
+ if (IS_ERR(hdmi->regs))
+ return PTR_ERR(hdmi->regs);
+
+ hdmi->pclk = devm_clk_get(hdmi->dev, "pclk");
+ if (IS_ERR(hdmi->pclk)) {
+ DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI pclk clk\n");
+ return PTR_ERR(hdmi->pclk);
+ }
+
+ ret = clk_prepare_enable(hdmi->pclk);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev,
+ "Cannot enable HDMI pclk clock: %d\n", ret);
+ return ret;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ goto err_disable_clk;
+ }
+
+ inno_hdmi_reset(hdmi);
+
+ hdmi->ddc = inno_hdmi_i2c_adapter(hdmi);
+ if (IS_ERR(hdmi->ddc)) {
+ ret = PTR_ERR(hdmi->ddc);
+ hdmi->ddc = NULL;
+ goto err_disable_clk;
+ }
+
+ /*
+ * When IP controller haven't configured to an accurate video
+ * timing, then the TMDS clock source would be switched to
+ * PCLK_HDMI, so we need to init the TMDS rate to PCLK rate,
+ * and reconfigure the DDC clock.
+ */
+ hdmi->tmds_rate = clk_get_rate(hdmi->pclk);
+ inno_hdmi_i2c_init(hdmi);
+
+ ret = inno_hdmi_register(drm, hdmi);
+ if (ret)
+ goto err_put_adapter;
+
+ dev_set_drvdata(dev, hdmi);
+
+ /* Unmute hotplug interrupt */
+ hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1));
+
+ ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq,
+ inno_hdmi_irq, IRQF_SHARED,
+ dev_name(dev), hdmi);
+ if (ret < 0)
+ goto err_cleanup_hdmi;
+
+ return 0;
+err_cleanup_hdmi:
+ hdmi->connector.funcs->destroy(&hdmi->connector);
+ hdmi->encoder.funcs->destroy(&hdmi->encoder);
+err_put_adapter:
+ i2c_put_adapter(hdmi->ddc);
+err_disable_clk:
+ clk_disable_unprepare(hdmi->pclk);
+ return ret;
+}
+
+static void inno_hdmi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct inno_hdmi *hdmi = dev_get_drvdata(dev);
+
+ hdmi->connector.funcs->destroy(&hdmi->connector);
+ hdmi->encoder.funcs->destroy(&hdmi->encoder);
+
+ i2c_put_adapter(hdmi->ddc);
+ clk_disable_unprepare(hdmi->pclk);
+}
+
+static const struct component_ops inno_hdmi_ops = {
+ .bind = inno_hdmi_bind,
+ .unbind = inno_hdmi_unbind,
+};
+
+static int inno_hdmi_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &inno_hdmi_ops);
+}
+
+static int inno_hdmi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &inno_hdmi_ops);
+
+ return 0;
+}
+
+static const struct of_device_id inno_hdmi_dt_ids[] = {
+ { .compatible = "rockchip,rk3036-inno-hdmi",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids);
+
+struct platform_driver inno_hdmi_driver = {
+ .probe = inno_hdmi_probe,
+ .remove = inno_hdmi_remove,
+ .driver = {
+ .name = "innohdmi-rockchip",
+ .of_match_table = inno_hdmi_dt_ids,
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.h b/drivers/gpu/drm/rockchip/inno_hdmi.h
new file mode 100644
index 000000000..93245b55f
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/inno_hdmi.h
@@ -0,0 +1,354 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Zheng Yang <zhengyang@rock-chips.com>
+ * Yakir Yang <ykk@rock-chips.com>
+ */
+
+#ifndef __INNO_HDMI_H__
+#define __INNO_HDMI_H__
+
+#define DDC_SEGMENT_ADDR 0x30
+
+enum PWR_MODE {
+ NORMAL,
+ LOWER_PWR,
+};
+
+#define HDMI_SCL_RATE (100*1000)
+#define DDC_BUS_FREQ_L 0x4b
+#define DDC_BUS_FREQ_H 0x4c
+
+#define HDMI_SYS_CTRL 0x00
+#define m_RST_ANALOG (1 << 6)
+#define v_RST_ANALOG (0 << 6)
+#define v_NOT_RST_ANALOG (1 << 6)
+#define m_RST_DIGITAL (1 << 5)
+#define v_RST_DIGITAL (0 << 5)
+#define v_NOT_RST_DIGITAL (1 << 5)
+#define m_REG_CLK_INV (1 << 4)
+#define v_REG_CLK_NOT_INV (0 << 4)
+#define v_REG_CLK_INV (1 << 4)
+#define m_VCLK_INV (1 << 3)
+#define v_VCLK_NOT_INV (0 << 3)
+#define v_VCLK_INV (1 << 3)
+#define m_REG_CLK_SOURCE (1 << 2)
+#define v_REG_CLK_SOURCE_TMDS (0 << 2)
+#define v_REG_CLK_SOURCE_SYS (1 << 2)
+#define m_POWER (1 << 1)
+#define v_PWR_ON (0 << 1)
+#define v_PWR_OFF (1 << 1)
+#define m_INT_POL (1 << 0)
+#define v_INT_POL_HIGH 1
+#define v_INT_POL_LOW 0
+
+#define HDMI_VIDEO_CONTRL1 0x01
+#define m_VIDEO_INPUT_FORMAT (7 << 1)
+#define m_DE_SOURCE (1 << 0)
+#define v_VIDEO_INPUT_FORMAT(n) (n << 1)
+#define v_DE_EXTERNAL 1
+#define v_DE_INTERNAL 0
+enum {
+ VIDEO_INPUT_SDR_RGB444 = 0,
+ VIDEO_INPUT_DDR_RGB444 = 5,
+ VIDEO_INPUT_DDR_YCBCR422 = 6
+};
+
+#define HDMI_VIDEO_CONTRL2 0x02
+#define m_VIDEO_OUTPUT_COLOR (3 << 6)
+#define m_VIDEO_INPUT_BITS (3 << 4)
+#define m_VIDEO_INPUT_CSP (1 << 0)
+#define v_VIDEO_OUTPUT_COLOR(n) (((n) & 0x3) << 6)
+#define v_VIDEO_INPUT_BITS(n) (n << 4)
+#define v_VIDEO_INPUT_CSP(n) (n << 0)
+enum {
+ VIDEO_INPUT_12BITS = 0,
+ VIDEO_INPUT_10BITS = 1,
+ VIDEO_INPUT_REVERT = 2,
+ VIDEO_INPUT_8BITS = 3,
+};
+
+#define HDMI_VIDEO_CONTRL 0x03
+#define m_VIDEO_AUTO_CSC (1 << 7)
+#define v_VIDEO_AUTO_CSC(n) (n << 7)
+#define m_VIDEO_C0_C2_SWAP (1 << 0)
+#define v_VIDEO_C0_C2_SWAP(n) (n << 0)
+enum {
+ C0_C2_CHANGE_ENABLE = 0,
+ C0_C2_CHANGE_DISABLE = 1,
+ AUTO_CSC_DISABLE = 0,
+ AUTO_CSC_ENABLE = 1,
+};
+
+#define HDMI_VIDEO_CONTRL3 0x04
+#define m_COLOR_DEPTH_NOT_INDICATED (1 << 4)
+#define m_SOF (1 << 3)
+#define m_COLOR_RANGE (1 << 2)
+#define m_CSC (1 << 0)
+#define v_COLOR_DEPTH_NOT_INDICATED(n) ((n) << 4)
+#define v_SOF_ENABLE (0 << 3)
+#define v_SOF_DISABLE (1 << 3)
+#define v_COLOR_RANGE_FULL (1 << 2)
+#define v_COLOR_RANGE_LIMITED (0 << 2)
+#define v_CSC_ENABLE 1
+#define v_CSC_DISABLE 0
+
+#define HDMI_AV_MUTE 0x05
+#define m_AVMUTE_CLEAR (1 << 7)
+#define m_AVMUTE_ENABLE (1 << 6)
+#define m_AUDIO_MUTE (1 << 1)
+#define m_VIDEO_BLACK (1 << 0)
+#define v_AVMUTE_CLEAR(n) (n << 7)
+#define v_AVMUTE_ENABLE(n) (n << 6)
+#define v_AUDIO_MUTE(n) (n << 1)
+#define v_VIDEO_MUTE(n) (n << 0)
+
+#define HDMI_VIDEO_TIMING_CTL 0x08
+#define v_HSYNC_POLARITY(n) (n << 3)
+#define v_VSYNC_POLARITY(n) (n << 2)
+#define v_INETLACE(n) (n << 1)
+#define v_EXTERANL_VIDEO(n) (n << 0)
+
+#define HDMI_VIDEO_EXT_HTOTAL_L 0x09
+#define HDMI_VIDEO_EXT_HTOTAL_H 0x0a
+#define HDMI_VIDEO_EXT_HBLANK_L 0x0b
+#define HDMI_VIDEO_EXT_HBLANK_H 0x0c
+#define HDMI_VIDEO_EXT_HDELAY_L 0x0d
+#define HDMI_VIDEO_EXT_HDELAY_H 0x0e
+#define HDMI_VIDEO_EXT_HDURATION_L 0x0f
+#define HDMI_VIDEO_EXT_HDURATION_H 0x10
+#define HDMI_VIDEO_EXT_VTOTAL_L 0x11
+#define HDMI_VIDEO_EXT_VTOTAL_H 0x12
+#define HDMI_VIDEO_EXT_VBLANK 0x13
+#define HDMI_VIDEO_EXT_VDELAY 0x14
+#define HDMI_VIDEO_EXT_VDURATION 0x15
+
+#define HDMI_VIDEO_CSC_COEF 0x18
+
+#define HDMI_AUDIO_CTRL1 0x35
+enum {
+ CTS_SOURCE_INTERNAL = 0,
+ CTS_SOURCE_EXTERNAL = 1,
+};
+#define v_CTS_SOURCE(n) (n << 7)
+
+enum {
+ DOWNSAMPLE_DISABLE = 0,
+ DOWNSAMPLE_1_2 = 1,
+ DOWNSAMPLE_1_4 = 2,
+};
+#define v_DOWN_SAMPLE(n) (n << 5)
+
+enum {
+ AUDIO_SOURCE_IIS = 0,
+ AUDIO_SOURCE_SPDIF = 1,
+};
+#define v_AUDIO_SOURCE(n) (n << 3)
+
+#define v_MCLK_ENABLE(n) (n << 2)
+enum {
+ MCLK_128FS = 0,
+ MCLK_256FS = 1,
+ MCLK_384FS = 2,
+ MCLK_512FS = 3,
+};
+#define v_MCLK_RATIO(n) (n)
+
+#define AUDIO_SAMPLE_RATE 0x37
+enum {
+ AUDIO_32K = 0x3,
+ AUDIO_441K = 0x0,
+ AUDIO_48K = 0x2,
+ AUDIO_882K = 0x8,
+ AUDIO_96K = 0xa,
+ AUDIO_1764K = 0xc,
+ AUDIO_192K = 0xe,
+};
+
+#define AUDIO_I2S_MODE 0x38
+enum {
+ I2S_CHANNEL_1_2 = 1,
+ I2S_CHANNEL_3_4 = 3,
+ I2S_CHANNEL_5_6 = 7,
+ I2S_CHANNEL_7_8 = 0xf
+};
+#define v_I2S_CHANNEL(n) ((n) << 2)
+enum {
+ I2S_STANDARD = 0,
+ I2S_LEFT_JUSTIFIED = 1,
+ I2S_RIGHT_JUSTIFIED = 2,
+};
+#define v_I2S_MODE(n) (n)
+
+#define AUDIO_I2S_MAP 0x39
+#define AUDIO_I2S_SWAPS_SPDIF 0x3a
+#define v_SPIDF_FREQ(n) (n)
+
+#define N_32K 0x1000
+#define N_441K 0x1880
+#define N_882K 0x3100
+#define N_1764K 0x6200
+#define N_48K 0x1800
+#define N_96K 0x3000
+#define N_192K 0x6000
+
+#define HDMI_AUDIO_CHANNEL_STATUS 0x3e
+#define m_AUDIO_STATUS_NLPCM (1 << 7)
+#define m_AUDIO_STATUS_USE (1 << 6)
+#define m_AUDIO_STATUS_COPYRIGHT (1 << 5)
+#define m_AUDIO_STATUS_ADDITION (3 << 2)
+#define m_AUDIO_STATUS_CLK_ACCURACY (2 << 0)
+#define v_AUDIO_STATUS_NLPCM(n) ((n & 1) << 7)
+#define AUDIO_N_H 0x3f
+#define AUDIO_N_M 0x40
+#define AUDIO_N_L 0x41
+
+#define HDMI_AUDIO_CTS_H 0x45
+#define HDMI_AUDIO_CTS_M 0x46
+#define HDMI_AUDIO_CTS_L 0x47
+
+#define HDMI_DDC_CLK_L 0x4b
+#define HDMI_DDC_CLK_H 0x4c
+
+#define HDMI_EDID_SEGMENT_POINTER 0x4d
+#define HDMI_EDID_WORD_ADDR 0x4e
+#define HDMI_EDID_FIFO_OFFSET 0x4f
+#define HDMI_EDID_FIFO_ADDR 0x50
+
+#define HDMI_PACKET_SEND_MANUAL 0x9c
+#define HDMI_PACKET_SEND_AUTO 0x9d
+#define m_PACKET_GCP_EN (1 << 7)
+#define m_PACKET_MSI_EN (1 << 6)
+#define m_PACKET_SDI_EN (1 << 5)
+#define m_PACKET_VSI_EN (1 << 4)
+#define v_PACKET_GCP_EN(n) ((n & 1) << 7)
+#define v_PACKET_MSI_EN(n) ((n & 1) << 6)
+#define v_PACKET_SDI_EN(n) ((n & 1) << 5)
+#define v_PACKET_VSI_EN(n) ((n & 1) << 4)
+
+#define HDMI_CONTROL_PACKET_BUF_INDEX 0x9f
+enum {
+ INFOFRAME_VSI = 0x05,
+ INFOFRAME_AVI = 0x06,
+ INFOFRAME_AAI = 0x08,
+};
+
+#define HDMI_CONTROL_PACKET_ADDR 0xa0
+#define HDMI_MAXIMUM_INFO_FRAME_SIZE 0x11
+enum {
+ AVI_COLOR_MODE_RGB = 0,
+ AVI_COLOR_MODE_YCBCR422 = 1,
+ AVI_COLOR_MODE_YCBCR444 = 2,
+ AVI_COLORIMETRY_NO_DATA = 0,
+
+ AVI_COLORIMETRY_SMPTE_170M = 1,
+ AVI_COLORIMETRY_ITU709 = 2,
+ AVI_COLORIMETRY_EXTENDED = 3,
+
+ AVI_CODED_FRAME_ASPECT_NO_DATA = 0,
+ AVI_CODED_FRAME_ASPECT_4_3 = 1,
+ AVI_CODED_FRAME_ASPECT_16_9 = 2,
+
+ ACTIVE_ASPECT_RATE_SAME_AS_CODED_FRAME = 0x08,
+ ACTIVE_ASPECT_RATE_4_3 = 0x09,
+ ACTIVE_ASPECT_RATE_16_9 = 0x0A,
+ ACTIVE_ASPECT_RATE_14_9 = 0x0B,
+};
+
+#define HDMI_HDCP_CTRL 0x52
+#define m_HDMI_DVI (1 << 1)
+#define v_HDMI_DVI(n) (n << 1)
+
+#define HDMI_INTERRUPT_MASK1 0xc0
+#define HDMI_INTERRUPT_STATUS1 0xc1
+#define m_INT_ACTIVE_VSYNC (1 << 5)
+#define m_INT_EDID_READY (1 << 2)
+
+#define HDMI_INTERRUPT_MASK2 0xc2
+#define HDMI_INTERRUPT_STATUS2 0xc3
+#define m_INT_HDCP_ERR (1 << 7)
+#define m_INT_BKSV_FLAG (1 << 6)
+#define m_INT_HDCP_OK (1 << 4)
+
+#define HDMI_STATUS 0xc8
+#define m_HOTPLUG (1 << 7)
+#define m_MASK_INT_HOTPLUG (1 << 5)
+#define m_INT_HOTPLUG (1 << 1)
+#define v_MASK_INT_HOTPLUG(n) ((n & 0x1) << 5)
+
+#define HDMI_COLORBAR 0xc9
+
+#define HDMI_PHY_SYNC 0xce
+#define HDMI_PHY_SYS_CTL 0xe0
+#define m_TMDS_CLK_SOURCE (1 << 5)
+#define v_TMDS_FROM_PLL (0 << 5)
+#define v_TMDS_FROM_GEN (1 << 5)
+#define m_PHASE_CLK (1 << 4)
+#define v_DEFAULT_PHASE (0 << 4)
+#define v_SYNC_PHASE (1 << 4)
+#define m_TMDS_CURRENT_PWR (1 << 3)
+#define v_TURN_ON_CURRENT (0 << 3)
+#define v_CAT_OFF_CURRENT (1 << 3)
+#define m_BANDGAP_PWR (1 << 2)
+#define v_BANDGAP_PWR_UP (0 << 2)
+#define v_BANDGAP_PWR_DOWN (1 << 2)
+#define m_PLL_PWR (1 << 1)
+#define v_PLL_PWR_UP (0 << 1)
+#define v_PLL_PWR_DOWN (1 << 1)
+#define m_TMDS_CHG_PWR (1 << 0)
+#define v_TMDS_CHG_PWR_UP (0 << 0)
+#define v_TMDS_CHG_PWR_DOWN (1 << 0)
+
+#define HDMI_PHY_CHG_PWR 0xe1
+#define v_CLK_CHG_PWR(n) ((n & 1) << 3)
+#define v_DATA_CHG_PWR(n) ((n & 7) << 0)
+
+#define HDMI_PHY_DRIVER 0xe2
+#define v_CLK_MAIN_DRIVER(n) (n << 4)
+#define v_DATA_MAIN_DRIVER(n) (n << 0)
+
+#define HDMI_PHY_PRE_EMPHASIS 0xe3
+#define v_PRE_EMPHASIS(n) ((n & 7) << 4)
+#define v_CLK_PRE_DRIVER(n) ((n & 3) << 2)
+#define v_DATA_PRE_DRIVER(n) ((n & 3) << 0)
+
+#define HDMI_PHY_FEEDBACK_DIV_RATIO_LOW 0xe7
+#define v_FEEDBACK_DIV_LOW(n) (n & 0xff)
+#define HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH 0xe8
+#define v_FEEDBACK_DIV_HIGH(n) (n & 1)
+
+#define HDMI_PHY_PRE_DIV_RATIO 0xed
+#define v_PRE_DIV_RATIO(n) (n & 0x1f)
+
+#define HDMI_CEC_CTRL 0xd0
+#define m_ADJUST_FOR_HISENSE (1 << 6)
+#define m_REJECT_RX_BROADCAST (1 << 5)
+#define m_BUSFREETIME_ENABLE (1 << 2)
+#define m_REJECT_RX (1 << 1)
+#define m_START_TX (1 << 0)
+
+#define HDMI_CEC_DATA 0xd1
+#define HDMI_CEC_TX_OFFSET 0xd2
+#define HDMI_CEC_RX_OFFSET 0xd3
+#define HDMI_CEC_CLK_H 0xd4
+#define HDMI_CEC_CLK_L 0xd5
+#define HDMI_CEC_TX_LENGTH 0xd6
+#define HDMI_CEC_RX_LENGTH 0xd7
+#define HDMI_CEC_TX_INT_MASK 0xd8
+#define m_TX_DONE (1 << 3)
+#define m_TX_NOACK (1 << 2)
+#define m_TX_BROADCAST_REJ (1 << 1)
+#define m_TX_BUSNOTFREE (1 << 0)
+
+#define HDMI_CEC_RX_INT_MASK 0xd9
+#define m_RX_LA_ERR (1 << 4)
+#define m_RX_GLITCH (1 << 3)
+#define m_RX_DONE (1 << 0)
+
+#define HDMI_CEC_TX_INT 0xda
+#define HDMI_CEC_RX_INT 0xdb
+#define HDMI_CEC_BUSFREETIME_L 0xdc
+#define HDMI_CEC_BUSFREETIME_H 0xdd
+#define HDMI_CEC_LOGICADDR 0xde
+
+#endif /* __INNO_HDMI_H__ */
diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c b/drivers/gpu/drm/rockchip/rk3066_hdmi.c
new file mode 100644
index 000000000..17e7c40a9
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Zheng Yang <zhengyang@rock-chips.com>
+ */
+
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "rk3066_hdmi.h"
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+#define DEFAULT_PLLA_RATE 30000000
+
+struct hdmi_data_info {
+ int vic; /* The CEA Video ID (VIC) of the current drm display mode. */
+ bool sink_is_hdmi;
+ unsigned int enc_out_format;
+ unsigned int colorimetry;
+};
+
+struct rk3066_hdmi_i2c {
+ struct i2c_adapter adap;
+
+ u8 ddc_addr;
+ u8 segment_addr;
+ u8 stat;
+
+ struct mutex i2c_lock; /* For i2c operation. */
+ struct completion cmpltn;
+};
+
+struct rk3066_hdmi {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ struct regmap *grf_regmap;
+ int irq;
+ struct clk *hclk;
+ void __iomem *regs;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct rk3066_hdmi_i2c *i2c;
+ struct i2c_adapter *ddc;
+
+ unsigned int tmdsclk;
+
+ struct hdmi_data_info hdmi_data;
+ struct drm_display_mode previous_mode;
+};
+
+#define to_rk3066_hdmi(x) container_of(x, struct rk3066_hdmi, x)
+
+static inline u8 hdmi_readb(struct rk3066_hdmi *hdmi, u16 offset)
+{
+ return readl_relaxed(hdmi->regs + offset);
+}
+
+static inline void hdmi_writeb(struct rk3066_hdmi *hdmi, u16 offset, u32 val)
+{
+ writel_relaxed(val, hdmi->regs + offset);
+}
+
+static inline void hdmi_modb(struct rk3066_hdmi *hdmi, u16 offset,
+ u32 msk, u32 val)
+{
+ u8 temp = hdmi_readb(hdmi, offset) & ~msk;
+
+ temp |= val & msk;
+ hdmi_writeb(hdmi, offset, temp);
+}
+
+static void rk3066_hdmi_i2c_init(struct rk3066_hdmi *hdmi)
+{
+ int ddc_bus_freq;
+
+ ddc_bus_freq = (hdmi->tmdsclk >> 2) / HDMI_SCL_RATE;
+
+ hdmi_writeb(hdmi, HDMI_DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
+ hdmi_writeb(hdmi, HDMI_DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
+
+ /* Clear the EDID interrupt flag and mute the interrupt. */
+ hdmi_modb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_EDID_MASK, 0);
+ hdmi_writeb(hdmi, HDMI_INTR_STATUS1, HDMI_INTR_EDID_MASK);
+}
+
+static inline u8 rk3066_hdmi_get_power_mode(struct rk3066_hdmi *hdmi)
+{
+ return hdmi_readb(hdmi, HDMI_SYS_CTRL) & HDMI_SYS_POWER_MODE_MASK;
+}
+
+static void rk3066_hdmi_set_power_mode(struct rk3066_hdmi *hdmi, int mode)
+{
+ u8 current_mode, next_mode;
+ u8 i = 0;
+
+ current_mode = rk3066_hdmi_get_power_mode(hdmi);
+
+ DRM_DEV_DEBUG(hdmi->dev, "mode :%d\n", mode);
+ DRM_DEV_DEBUG(hdmi->dev, "current_mode :%d\n", current_mode);
+
+ if (current_mode == mode)
+ return;
+
+ do {
+ if (current_mode > mode) {
+ next_mode = current_mode / 2;
+ } else {
+ if (current_mode < HDMI_SYS_POWER_MODE_A)
+ next_mode = HDMI_SYS_POWER_MODE_A;
+ else
+ next_mode = current_mode * 2;
+ }
+
+ DRM_DEV_DEBUG(hdmi->dev, "%d: next_mode :%d\n", i, next_mode);
+
+ if (next_mode != HDMI_SYS_POWER_MODE_D) {
+ hdmi_modb(hdmi, HDMI_SYS_CTRL,
+ HDMI_SYS_POWER_MODE_MASK, next_mode);
+ } else {
+ hdmi_writeb(hdmi, HDMI_SYS_CTRL,
+ HDMI_SYS_POWER_MODE_D |
+ HDMI_SYS_PLL_RESET_MASK);
+ usleep_range(90, 100);
+ hdmi_writeb(hdmi, HDMI_SYS_CTRL,
+ HDMI_SYS_POWER_MODE_D |
+ HDMI_SYS_PLLB_RESET);
+ usleep_range(90, 100);
+ hdmi_writeb(hdmi, HDMI_SYS_CTRL,
+ HDMI_SYS_POWER_MODE_D);
+ }
+ current_mode = next_mode;
+ i = i + 1;
+ } while ((next_mode != mode) && (i < 5));
+
+ /*
+ * When the IP controller isn't configured with accurate video timing,
+ * DDC_CLK should be equal to the PLLA frequency, which is 30MHz,
+ * so we need to init the TMDS rate to the PCLK rate and reconfigure
+ * the DDC clock.
+ */
+ if (mode < HDMI_SYS_POWER_MODE_D)
+ hdmi->tmdsclk = DEFAULT_PLLA_RATE;
+}
+
+static int
+rk3066_hdmi_upload_frame(struct rk3066_hdmi *hdmi, int setup_rc,
+ union hdmi_infoframe *frame, u32 frame_index,
+ u32 mask, u32 disable, u32 enable)
+{
+ if (mask)
+ hdmi_modb(hdmi, HDMI_CP_AUTO_SEND_CTRL, mask, disable);
+
+ hdmi_writeb(hdmi, HDMI_CP_BUF_INDEX, frame_index);
+
+ if (setup_rc >= 0) {
+ u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
+ ssize_t rc, i;
+
+ rc = hdmi_infoframe_pack(frame, packed_frame,
+ sizeof(packed_frame));
+ if (rc < 0)
+ return rc;
+
+ for (i = 0; i < rc; i++)
+ hdmi_writeb(hdmi, HDMI_CP_BUF_ACC_HB0 + i * 4,
+ packed_frame[i]);
+
+ if (mask)
+ hdmi_modb(hdmi, HDMI_CP_AUTO_SEND_CTRL, mask, enable);
+ }
+
+ return setup_rc;
+}
+
+static int rk3066_hdmi_config_avi(struct rk3066_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ union hdmi_infoframe frame;
+ int rc;
+
+ rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
+ &hdmi->connector, mode);
+
+ if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444)
+ frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
+ else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422)
+ frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
+ else
+ frame.avi.colorspace = HDMI_COLORSPACE_RGB;
+
+ frame.avi.colorimetry = hdmi->hdmi_data.colorimetry;
+ frame.avi.scan_mode = HDMI_SCAN_MODE_NONE;
+
+ return rk3066_hdmi_upload_frame(hdmi, rc, &frame,
+ HDMI_INFOFRAME_AVI, 0, 0, 0);
+}
+
+static int rk3066_hdmi_config_video_timing(struct rk3066_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ int value, vsync_offset;
+
+ /* Set the details for the external polarity and interlace mode. */
+ value = HDMI_EXT_VIDEO_SET_EN;
+ value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+ HDMI_VIDEO_HSYNC_ACTIVE_HIGH : HDMI_VIDEO_HSYNC_ACTIVE_LOW;
+ value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+ HDMI_VIDEO_VSYNC_ACTIVE_HIGH : HDMI_VIDEO_VSYNC_ACTIVE_LOW;
+ value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
+ HDMI_VIDEO_MODE_INTERLACE : HDMI_VIDEO_MODE_PROGRESSIVE;
+
+ if (hdmi->hdmi_data.vic == 2 || hdmi->hdmi_data.vic == 3)
+ vsync_offset = 6;
+ else
+ vsync_offset = 0;
+
+ value |= vsync_offset << HDMI_VIDEO_VSYNC_OFFSET_SHIFT;
+ hdmi_writeb(hdmi, HDMI_EXT_VIDEO_PARA, value);
+
+ /* Set the details for the external video timing. */
+ value = mode->htotal;
+ hdmi_writeb(hdmi, HDMI_EXT_HTOTAL_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_EXT_HTOTAL_H, (value >> 8) & 0xFF);
+
+ value = mode->htotal - mode->hdisplay;
+ hdmi_writeb(hdmi, HDMI_EXT_HBLANK_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_EXT_HBLANK_H, (value >> 8) & 0xFF);
+
+ value = mode->htotal - mode->hsync_start;
+ hdmi_writeb(hdmi, HDMI_EXT_HDELAY_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_EXT_HDELAY_H, (value >> 8) & 0xFF);
+
+ value = mode->hsync_end - mode->hsync_start;
+ hdmi_writeb(hdmi, HDMI_EXT_HDURATION_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_EXT_HDURATION_H, (value >> 8) & 0xFF);
+
+ value = mode->vtotal;
+ hdmi_writeb(hdmi, HDMI_EXT_VTOTAL_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_EXT_VTOTAL_H, (value >> 8) & 0xFF);
+
+ value = mode->vtotal - mode->vdisplay;
+ hdmi_writeb(hdmi, HDMI_EXT_VBLANK_L, value & 0xFF);
+
+ value = mode->vtotal - mode->vsync_start + vsync_offset;
+ hdmi_writeb(hdmi, HDMI_EXT_VDELAY, value & 0xFF);
+
+ value = mode->vsync_end - mode->vsync_start;
+ hdmi_writeb(hdmi, HDMI_EXT_VDURATION, value & 0xFF);
+
+ return 0;
+}
+
+static void
+rk3066_hdmi_phy_write(struct rk3066_hdmi *hdmi, u16 offset, u8 value)
+{
+ hdmi_writeb(hdmi, offset, value);
+ hdmi_modb(hdmi, HDMI_SYS_CTRL,
+ HDMI_SYS_PLL_RESET_MASK, HDMI_SYS_PLL_RESET);
+ usleep_range(90, 100);
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, HDMI_SYS_PLL_RESET_MASK, 0);
+ usleep_range(900, 1000);
+}
+
+static void rk3066_hdmi_config_phy(struct rk3066_hdmi *hdmi)
+{
+ /* TMDS uses the same frequency as dclk. */
+ hdmi_writeb(hdmi, HDMI_DEEP_COLOR_MODE, 0x22);
+
+ /*
+ * The semi-public documentation does not describe the hdmi registers
+ * used by the function rk3066_hdmi_phy_write(), so we keep using
+ * these magic values for now.
+ */
+ if (hdmi->tmdsclk > 100000000) {
+ rk3066_hdmi_phy_write(hdmi, 0x158, 0x0E);
+ rk3066_hdmi_phy_write(hdmi, 0x15c, 0x00);
+ rk3066_hdmi_phy_write(hdmi, 0x160, 0x60);
+ rk3066_hdmi_phy_write(hdmi, 0x164, 0x00);
+ rk3066_hdmi_phy_write(hdmi, 0x168, 0xDA);
+ rk3066_hdmi_phy_write(hdmi, 0x16c, 0xA1);
+ rk3066_hdmi_phy_write(hdmi, 0x170, 0x0e);
+ rk3066_hdmi_phy_write(hdmi, 0x174, 0x22);
+ rk3066_hdmi_phy_write(hdmi, 0x178, 0x00);
+ } else if (hdmi->tmdsclk > 50000000) {
+ rk3066_hdmi_phy_write(hdmi, 0x158, 0x06);
+ rk3066_hdmi_phy_write(hdmi, 0x15c, 0x00);
+ rk3066_hdmi_phy_write(hdmi, 0x160, 0x60);
+ rk3066_hdmi_phy_write(hdmi, 0x164, 0x00);
+ rk3066_hdmi_phy_write(hdmi, 0x168, 0xCA);
+ rk3066_hdmi_phy_write(hdmi, 0x16c, 0xA3);
+ rk3066_hdmi_phy_write(hdmi, 0x170, 0x0e);
+ rk3066_hdmi_phy_write(hdmi, 0x174, 0x20);
+ rk3066_hdmi_phy_write(hdmi, 0x178, 0x00);
+ } else {
+ rk3066_hdmi_phy_write(hdmi, 0x158, 0x02);
+ rk3066_hdmi_phy_write(hdmi, 0x15c, 0x00);
+ rk3066_hdmi_phy_write(hdmi, 0x160, 0x60);
+ rk3066_hdmi_phy_write(hdmi, 0x164, 0x00);
+ rk3066_hdmi_phy_write(hdmi, 0x168, 0xC2);
+ rk3066_hdmi_phy_write(hdmi, 0x16c, 0xA2);
+ rk3066_hdmi_phy_write(hdmi, 0x170, 0x0e);
+ rk3066_hdmi_phy_write(hdmi, 0x174, 0x20);
+ rk3066_hdmi_phy_write(hdmi, 0x178, 0x00);
+ }
+}
+
+static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
+ hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
+
+ if (hdmi->hdmi_data.vic == 6 || hdmi->hdmi_data.vic == 7 ||
+ hdmi->hdmi_data.vic == 21 || hdmi->hdmi_data.vic == 22 ||
+ hdmi->hdmi_data.vic == 2 || hdmi->hdmi_data.vic == 3 ||
+ hdmi->hdmi_data.vic == 17 || hdmi->hdmi_data.vic == 18)
+ hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601;
+ else
+ hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
+
+ hdmi->tmdsclk = mode->clock * 1000;
+
+ /* Mute video and audio output. */
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_VIDEO_AUDIO_DISABLE_MASK,
+ HDMI_AUDIO_DISABLE | HDMI_VIDEO_DISABLE);
+
+ /* Set power state to mode B. */
+ if (rk3066_hdmi_get_power_mode(hdmi) != HDMI_SYS_POWER_MODE_B)
+ rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_B);
+
+ /* Input video mode is RGB 24 bit. Use external data enable signal. */
+ hdmi_modb(hdmi, HDMI_AV_CTRL1,
+ HDMI_VIDEO_DE_MASK, HDMI_VIDEO_EXTERNAL_DE);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CTRL1,
+ HDMI_VIDEO_OUTPUT_RGB444 |
+ HDMI_VIDEO_INPUT_DATA_DEPTH_8BIT |
+ HDMI_VIDEO_INPUT_COLOR_RGB);
+ hdmi_writeb(hdmi, HDMI_DEEP_COLOR_MODE, 0x20);
+
+ rk3066_hdmi_config_video_timing(hdmi, mode);
+
+ if (hdmi->hdmi_data.sink_is_hdmi) {
+ hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK,
+ HDMI_VIDEO_MODE_HDMI);
+ rk3066_hdmi_config_avi(hdmi, mode);
+ } else {
+ hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, 0);
+ }
+
+ rk3066_hdmi_config_phy(hdmi);
+
+ rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_E);
+
+ /*
+ * When the IP controller is configured with accurate video
+ * timing, the TMDS clock source should be switched to
+ * DCLK_LCDC, so we need to init the TMDS rate to the pixel mode
+ * clock rate and reconfigure the DDC clock.
+ */
+ rk3066_hdmi_i2c_init(hdmi);
+
+ /* Unmute video output. */
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_VIDEO_AUDIO_DISABLE_MASK, HDMI_AUDIO_DISABLE);
+ return 0;
+}
+
+static void
+rk3066_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder);
+
+ /* Store the display mode for plugin/DPMS poweron events. */
+ drm_mode_copy(&hdmi->previous_mode, adj_mode);
+}
+
+static void rk3066_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder);
+ int mux, val;
+
+ mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
+ if (mux)
+ val = (HDMI_VIDEO_SEL << 16) | HDMI_VIDEO_SEL;
+ else
+ val = HDMI_VIDEO_SEL << 16;
+
+ regmap_write(hdmi->grf_regmap, GRF_SOC_CON0, val);
+
+ DRM_DEV_DEBUG(hdmi->dev, "hdmi encoder enable select: vop%s\n",
+ (mux) ? "1" : "0");
+
+ rk3066_hdmi_setup(hdmi, &hdmi->previous_mode);
+}
+
+static void rk3066_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder);
+
+ DRM_DEV_DEBUG(hdmi->dev, "hdmi encoder disable\n");
+
+ if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_E) {
+ hdmi_writeb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_VIDEO_AUDIO_DISABLE_MASK);
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_AUDIO_CP_LOGIC_RESET_MASK,
+ HDMI_AUDIO_CP_LOGIC_RESET);
+ usleep_range(500, 510);
+ }
+ rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_A);
+}
+
+static bool
+rk3066_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ return true;
+}
+
+static int
+rk3066_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+
+ s->output_mode = ROCKCHIP_OUT_MODE_P888;
+ s->output_type = DRM_MODE_CONNECTOR_HDMIA;
+
+ return 0;
+}
+
+static const
+struct drm_encoder_helper_funcs rk3066_hdmi_encoder_helper_funcs = {
+ .enable = rk3066_hdmi_encoder_enable,
+ .disable = rk3066_hdmi_encoder_disable,
+ .mode_fixup = rk3066_hdmi_encoder_mode_fixup,
+ .mode_set = rk3066_hdmi_encoder_mode_set,
+ .atomic_check = rk3066_hdmi_encoder_atomic_check,
+};
+
+static enum drm_connector_status
+rk3066_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector);
+
+ return (hdmi_readb(hdmi, HDMI_HPG_MENS_STA) & HDMI_HPG_IN_STATUS_HIGH) ?
+ connector_status_connected : connector_status_disconnected;
+}
+
+static int rk3066_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector);
+ struct edid *edid;
+ int ret = 0;
+
+ if (!hdmi->ddc)
+ return 0;
+
+ edid = drm_get_edid(connector, hdmi->ddc);
+ if (edid) {
+ hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+ drm_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return ret;
+}
+
+static enum drm_mode_status
+rk3066_hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ u32 vic = drm_match_cea_mode(mode);
+
+ if (vic > 1)
+ return MODE_OK;
+ else
+ return MODE_BAD;
+}
+
+static struct drm_encoder *
+rk3066_hdmi_connector_best_encoder(struct drm_connector *connector)
+{
+ struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector);
+
+ return &hdmi->encoder;
+}
+
+static int
+rk3066_hdmi_probe_single_connector_modes(struct drm_connector *connector,
+ uint32_t maxX, uint32_t maxY)
+{
+ if (maxX > 1920)
+ maxX = 1920;
+ if (maxY > 1080)
+ maxY = 1080;
+
+ return drm_helper_probe_single_connector_modes(connector, maxX, maxY);
+}
+
+static void rk3066_hdmi_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs rk3066_hdmi_connector_funcs = {
+ .fill_modes = rk3066_hdmi_probe_single_connector_modes,
+ .detect = rk3066_hdmi_connector_detect,
+ .destroy = rk3066_hdmi_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const
+struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = {
+ .get_modes = rk3066_hdmi_connector_get_modes,
+ .mode_valid = rk3066_hdmi_connector_mode_valid,
+ .best_encoder = rk3066_hdmi_connector_best_encoder,
+};
+
+static int
+rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
+{
+ struct drm_encoder *encoder = &hdmi->encoder;
+ struct device *dev = hdmi->dev;
+
+ encoder->possible_crtcs =
+ drm_of_find_possible_crtcs(drm, dev->of_node);
+
+ /*
+ * If we failed to find the CRTC(s) which this encoder is
+ * supposed to be connected to, it's because the CRTC has
+ * not been registered yet. Defer probing, and hope that
+ * the required CRTC is added later.
+ */
+ if (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ drm_encoder_helper_add(encoder, &rk3066_hdmi_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+
+ hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+ drm_connector_helper_add(&hdmi->connector,
+ &rk3066_hdmi_connector_helper_funcs);
+ drm_connector_init_with_ddc(drm, &hdmi->connector,
+ &rk3066_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA,
+ hdmi->ddc);
+
+ drm_connector_attach_encoder(&hdmi->connector, encoder);
+
+ return 0;
+}
+
+static irqreturn_t rk3066_hdmi_hardirq(int irq, void *dev_id)
+{
+ struct rk3066_hdmi *hdmi = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ u8 interrupt;
+
+ if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_A)
+ hdmi_writeb(hdmi, HDMI_SYS_CTRL, HDMI_SYS_POWER_MODE_B);
+
+ interrupt = hdmi_readb(hdmi, HDMI_INTR_STATUS1);
+ if (interrupt)
+ hdmi_writeb(hdmi, HDMI_INTR_STATUS1, interrupt);
+
+ if (interrupt & HDMI_INTR_EDID_MASK) {
+ hdmi->i2c->stat = interrupt;
+ complete(&hdmi->i2c->cmpltn);
+ }
+
+ if (interrupt & (HDMI_INTR_HOTPLUG | HDMI_INTR_MSENS))
+ ret = IRQ_WAKE_THREAD;
+
+ return ret;
+}
+
+static irqreturn_t rk3066_hdmi_irq(int irq, void *dev_id)
+{
+ struct rk3066_hdmi *hdmi = dev_id;
+
+ drm_helper_hpd_irq_event(hdmi->connector.dev);
+
+ return IRQ_HANDLED;
+}
+
+static int rk3066_hdmi_i2c_read(struct rk3066_hdmi *hdmi, struct i2c_msg *msgs)
+{
+ int length = msgs->len;
+ u8 *buf = msgs->buf;
+ int ret;
+
+ ret = wait_for_completion_timeout(&hdmi->i2c->cmpltn, HZ / 10);
+ if (!ret || hdmi->i2c->stat & HDMI_INTR_EDID_ERR)
+ return -EAGAIN;
+
+ while (length--)
+ *buf++ = hdmi_readb(hdmi, HDMI_DDC_READ_FIFO_ADDR);
+
+ return 0;
+}
+
+static int rk3066_hdmi_i2c_write(struct rk3066_hdmi *hdmi, struct i2c_msg *msgs)
+{
+ /*
+ * The DDC module only supports read EDID message, so
+ * we assume that each word write to this i2c adapter
+ * should be the offset of the EDID word address.
+ */
+ if (msgs->len != 1 ||
+ (msgs->addr != DDC_ADDR && msgs->addr != DDC_SEGMENT_ADDR))
+ return -EINVAL;
+
+ reinit_completion(&hdmi->i2c->cmpltn);
+
+ if (msgs->addr == DDC_SEGMENT_ADDR)
+ hdmi->i2c->segment_addr = msgs->buf[0];
+ if (msgs->addr == DDC_ADDR)
+ hdmi->i2c->ddc_addr = msgs->buf[0];
+
+ /* Set edid fifo first address. */
+ hdmi_writeb(hdmi, HDMI_EDID_FIFO_ADDR, 0x00);
+
+ /* Set edid word address 0x00/0x80. */
+ hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
+
+ /* Set edid segment pointer. */
+ hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
+
+ return 0;
+}
+
+static int rk3066_hdmi_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct rk3066_hdmi *hdmi = i2c_get_adapdata(adap);
+ struct rk3066_hdmi_i2c *i2c = hdmi->i2c;
+ int i, ret = 0;
+
+ mutex_lock(&i2c->i2c_lock);
+
+ rk3066_hdmi_i2c_init(hdmi);
+
+ /* Unmute HDMI EDID interrupt. */
+ hdmi_modb(hdmi, HDMI_INTR_MASK1,
+ HDMI_INTR_EDID_MASK, HDMI_INTR_EDID_MASK);
+ i2c->stat = 0;
+
+ for (i = 0; i < num; i++) {
+ DRM_DEV_DEBUG(hdmi->dev,
+ "xfer: num: %d/%d, len: %d, flags: %#x\n",
+ i + 1, num, msgs[i].len, msgs[i].flags);
+
+ if (msgs[i].flags & I2C_M_RD)
+ ret = rk3066_hdmi_i2c_read(hdmi, &msgs[i]);
+ else
+ ret = rk3066_hdmi_i2c_write(hdmi, &msgs[i]);
+
+ if (ret < 0)
+ break;
+ }
+
+ if (!ret)
+ ret = num;
+
+ /* Mute HDMI EDID interrupt. */
+ hdmi_modb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_EDID_MASK, 0);
+
+ mutex_unlock(&i2c->i2c_lock);
+
+ return ret;
+}
+
+static u32 rk3066_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm rk3066_hdmi_algorithm = {
+ .master_xfer = rk3066_hdmi_i2c_xfer,
+ .functionality = rk3066_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *rk3066_hdmi_i2c_adapter(struct rk3066_hdmi *hdmi)
+{
+ struct i2c_adapter *adap;
+ struct rk3066_hdmi_i2c *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&i2c->i2c_lock);
+ init_completion(&i2c->cmpltn);
+
+ adap = &i2c->adap;
+ adap->class = I2C_CLASS_DDC;
+ adap->owner = THIS_MODULE;
+ adap->dev.parent = hdmi->dev;
+ adap->dev.of_node = hdmi->dev->of_node;
+ adap->algo = &rk3066_hdmi_algorithm;
+ strlcpy(adap->name, "RK3066 HDMI", sizeof(adap->name));
+ i2c_set_adapdata(adap, hdmi);
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev, "cannot add %s I2C adapter\n",
+ adap->name);
+ devm_kfree(hdmi->dev, i2c);
+ return ERR_PTR(ret);
+ }
+
+ hdmi->i2c = i2c;
+
+ DRM_DEV_DEBUG(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+ return adap;
+}
+
+static int rk3066_hdmi_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct drm_device *drm = data;
+ struct rk3066_hdmi *hdmi;
+ int irq;
+ int ret;
+
+ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi)
+ return -ENOMEM;
+
+ hdmi->dev = dev;
+ hdmi->drm_dev = drm;
+ hdmi->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hdmi->regs))
+ return PTR_ERR(hdmi->regs);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ hdmi->hclk = devm_clk_get(dev, "hclk");
+ if (IS_ERR(hdmi->hclk)) {
+ DRM_DEV_ERROR(dev, "unable to get HDMI hclk clock\n");
+ return PTR_ERR(hdmi->hclk);
+ }
+
+ ret = clk_prepare_enable(hdmi->hclk);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "cannot enable HDMI hclk clock: %d\n", ret);
+ return ret;
+ }
+
+ hdmi->grf_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(hdmi->grf_regmap)) {
+ DRM_DEV_ERROR(dev, "unable to get rockchip,grf\n");
+ ret = PTR_ERR(hdmi->grf_regmap);
+ goto err_disable_hclk;
+ }
+
+ /* internal hclk = hdmi_hclk / 25 */
+ hdmi_writeb(hdmi, HDMI_INTERNAL_CLK_DIVIDER, 25);
+
+ hdmi->ddc = rk3066_hdmi_i2c_adapter(hdmi);
+ if (IS_ERR(hdmi->ddc)) {
+ ret = PTR_ERR(hdmi->ddc);
+ hdmi->ddc = NULL;
+ goto err_disable_hclk;
+ }
+
+ rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_B);
+ usleep_range(999, 1000);
+ hdmi_writeb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_HOTPLUG);
+ hdmi_writeb(hdmi, HDMI_INTR_MASK2, 0);
+ hdmi_writeb(hdmi, HDMI_INTR_MASK3, 0);
+ hdmi_writeb(hdmi, HDMI_INTR_MASK4, 0);
+ rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_A);
+
+ ret = rk3066_hdmi_register(drm, hdmi);
+ if (ret)
+ goto err_disable_i2c;
+
+ dev_set_drvdata(dev, hdmi);
+
+ ret = devm_request_threaded_irq(dev, irq, rk3066_hdmi_hardirq,
+ rk3066_hdmi_irq, IRQF_SHARED,
+ dev_name(dev), hdmi);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to request hdmi irq: %d\n", ret);
+ goto err_cleanup_hdmi;
+ }
+
+ return 0;
+
+err_cleanup_hdmi:
+ hdmi->connector.funcs->destroy(&hdmi->connector);
+ hdmi->encoder.funcs->destroy(&hdmi->encoder);
+err_disable_i2c:
+ i2c_put_adapter(hdmi->ddc);
+err_disable_hclk:
+ clk_disable_unprepare(hdmi->hclk);
+
+ return ret;
+}
+
+static void rk3066_hdmi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+
+ hdmi->connector.funcs->destroy(&hdmi->connector);
+ hdmi->encoder.funcs->destroy(&hdmi->encoder);
+
+ i2c_put_adapter(hdmi->ddc);
+ clk_disable_unprepare(hdmi->hclk);
+}
+
+static const struct component_ops rk3066_hdmi_ops = {
+ .bind = rk3066_hdmi_bind,
+ .unbind = rk3066_hdmi_unbind,
+};
+
+static int rk3066_hdmi_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &rk3066_hdmi_ops);
+}
+
+static int rk3066_hdmi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &rk3066_hdmi_ops);
+
+ return 0;
+}
+
+static const struct of_device_id rk3066_hdmi_dt_ids[] = {
+ { .compatible = "rockchip,rk3066-hdmi" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rk3066_hdmi_dt_ids);
+
+struct platform_driver rk3066_hdmi_driver = {
+ .probe = rk3066_hdmi_probe,
+ .remove = rk3066_hdmi_remove,
+ .driver = {
+ .name = "rockchip-rk3066-hdmi",
+ .of_match_table = rk3066_hdmi_dt_ids,
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.h b/drivers/gpu/drm/rockchip/rk3066_hdmi.h
new file mode 100644
index 000000000..39a31c62a
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.h
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Zheng Yang <zhengyang@rock-chips.com>
+ */
+
+#ifndef __RK3066_HDMI_H__
+#define __RK3066_HDMI_H__
+
+#define GRF_SOC_CON0 0x150
+#define HDMI_VIDEO_SEL BIT(14)
+
+#define DDC_SEGMENT_ADDR 0x30
+#define HDMI_SCL_RATE (50 * 1000)
+#define HDMI_MAXIMUM_INFO_FRAME_SIZE 0x11
+
+#define N_32K 0x1000
+#define N_441K 0x1880
+#define N_882K 0x3100
+#define N_1764K 0x6200
+#define N_48K 0x1800
+#define N_96K 0x3000
+#define N_192K 0x6000
+
+#define HDMI_SYS_CTRL 0x000
+#define HDMI_LR_SWAP_N3 0x004
+#define HDMI_N2 0x008
+#define HDMI_N1 0x00c
+#define HDMI_SPDIF_FS_CTS_INT3 0x010
+#define HDMI_CTS_INT2 0x014
+#define HDMI_CTS_INT1 0x018
+#define HDMI_CTS_EXT3 0x01c
+#define HDMI_CTS_EXT2 0x020
+#define HDMI_CTS_EXT1 0x024
+#define HDMI_AUDIO_CTRL1 0x028
+#define HDMI_AUDIO_CTRL2 0x02c
+#define HDMI_I2S_AUDIO_CTRL 0x030
+#define HDMI_I2S_SWAP 0x040
+#define HDMI_AUDIO_STA_BIT_CTRL1 0x044
+#define HDMI_AUDIO_STA_BIT_CTRL2 0x048
+#define HDMI_AUDIO_SRC_NUM_AND_LENGTH 0x050
+#define HDMI_AV_CTRL1 0x054
+#define HDMI_VIDEO_CTRL1 0x058
+#define HDMI_DEEP_COLOR_MODE 0x05c
+
+#define HDMI_EXT_VIDEO_PARA 0x0c0
+#define HDMI_EXT_HTOTAL_L 0x0c4
+#define HDMI_EXT_HTOTAL_H 0x0c8
+#define HDMI_EXT_HBLANK_L 0x0cc
+#define HDMI_EXT_HBLANK_H 0x0d0
+#define HDMI_EXT_HDELAY_L 0x0d4
+#define HDMI_EXT_HDELAY_H 0x0d8
+#define HDMI_EXT_HDURATION_L 0x0dc
+#define HDMI_EXT_HDURATION_H 0x0e0
+#define HDMI_EXT_VTOTAL_L 0x0e4
+#define HDMI_EXT_VTOTAL_H 0x0e8
+#define HDMI_AV_CTRL2 0x0ec
+#define HDMI_EXT_VBLANK_L 0x0f4
+#define HDMI_EXT_VBLANK_H 0x10c
+#define HDMI_EXT_VDELAY 0x0f8
+#define HDMI_EXT_VDURATION 0x0fc
+
+#define HDMI_CP_MANU_SEND_CTRL 0x100
+#define HDMI_CP_AUTO_SEND_CTRL 0x104
+#define HDMI_AUTO_CHECKSUM_OPT 0x108
+
+#define HDMI_VIDEO_CTRL2 0x114
+
+#define HDMI_PHY_OPTION 0x144
+
+#define HDMI_CP_BUF_INDEX 0x17c
+#define HDMI_CP_BUF_ACC_HB0 0x180
+#define HDMI_CP_BUF_ACC_HB1 0x184
+#define HDMI_CP_BUF_ACC_HB2 0x188
+#define HDMI_CP_BUF_ACC_PB0 0x18c
+
+#define HDMI_DDC_READ_FIFO_ADDR 0x200
+#define HDMI_DDC_BUS_FREQ_L 0x204
+#define HDMI_DDC_BUS_FREQ_H 0x208
+#define HDMI_DDC_BUS_CTRL 0x2dc
+#define HDMI_DDC_I2C_LEN 0x278
+#define HDMI_DDC_I2C_OFFSET 0x280
+#define HDMI_DDC_I2C_CTRL 0x284
+#define HDMI_DDC_I2C_READ_BUF0 0x288
+#define HDMI_DDC_I2C_READ_BUF1 0x28c
+#define HDMI_DDC_I2C_READ_BUF2 0x290
+#define HDMI_DDC_I2C_READ_BUF3 0x294
+#define HDMI_DDC_I2C_WRITE_BUF0 0x298
+#define HDMI_DDC_I2C_WRITE_BUF1 0x29c
+#define HDMI_DDC_I2C_WRITE_BUF2 0x2a0
+#define HDMI_DDC_I2C_WRITE_BUF3 0x2a4
+#define HDMI_DDC_I2C_WRITE_BUF4 0x2ac
+#define HDMI_DDC_I2C_WRITE_BUF5 0x2b0
+#define HDMI_DDC_I2C_WRITE_BUF6 0x2b4
+
+#define HDMI_INTR_MASK1 0x248
+#define HDMI_INTR_MASK2 0x24c
+#define HDMI_INTR_STATUS1 0x250
+#define HDMI_INTR_STATUS2 0x254
+#define HDMI_INTR_MASK3 0x258
+#define HDMI_INTR_MASK4 0x25c
+#define HDMI_INTR_STATUS3 0x260
+#define HDMI_INTR_STATUS4 0x264
+
+#define HDMI_HDCP_CTRL 0x2bc
+
+#define HDMI_EDID_SEGMENT_POINTER 0x310
+#define HDMI_EDID_WORD_ADDR 0x314
+#define HDMI_EDID_FIFO_ADDR 0x318
+
+#define HDMI_HPG_MENS_STA 0x37c
+
+#define HDMI_INTERNAL_CLK_DIVIDER 0x800
+
+enum {
+ /* HDMI_SYS_CTRL */
+ HDMI_SYS_POWER_MODE_MASK = 0xf0,
+ HDMI_SYS_POWER_MODE_A = 0x10,
+ HDMI_SYS_POWER_MODE_B = 0x20,
+ HDMI_SYS_POWER_MODE_D = 0x40,
+ HDMI_SYS_POWER_MODE_E = 0x80,
+ HDMI_SYS_PLL_RESET_MASK = 0x0c,
+ HDMI_SYS_PLL_RESET = 0x0c,
+ HDMI_SYS_PLLB_RESET = 0x08,
+
+ /* HDMI_LR_SWAP_N3 */
+ HDMI_AUDIO_LR_SWAP_MASK = 0xf0,
+ HDMI_AUDIO_LR_SWAP_SUBPACKET0 = 0x10,
+ HDMI_AUDIO_LR_SWAP_SUBPACKET1 = 0x20,
+ HDMI_AUDIO_LR_SWAP_SUBPACKET2 = 0x40,
+ HDMI_AUDIO_LR_SWAP_SUBPACKET3 = 0x80,
+ HDMI_AUDIO_N_19_16_MASK = 0x0f,
+
+ /* HDMI_AUDIO_CTRL1 */
+ HDMI_AUDIO_EXTERNAL_CTS = BIT(7),
+ HDMI_AUDIO_INPUT_IIS = 0,
+ HDMI_AUDIO_INPUT_SPDIF = 0x08,
+ HDMI_AUDIO_INPUT_MCLK_ACTIVE = 0x04,
+ HDMI_AUDIO_INPUT_MCLK_DEACTIVE = 0,
+ HDMI_AUDIO_INPUT_MCLK_RATE_128X = 0,
+ HDMI_AUDIO_INPUT_MCLK_RATE_256X = 1,
+ HDMI_AUDIO_INPUT_MCLK_RATE_384X = 2,
+ HDMI_AUDIO_INPUT_MCLK_RATE_512X = 3,
+
+ /* HDMI_I2S_AUDIO_CTRL */
+ HDMI_AUDIO_I2S_FORMAT_STANDARD = 0,
+ HDMI_AUDIO_I2S_CHANNEL_1_2 = 0x04,
+ HDMI_AUDIO_I2S_CHANNEL_3_4 = 0x0c,
+ HDMI_AUDIO_I2S_CHANNEL_5_6 = 0x1c,
+ HDMI_AUDIO_I2S_CHANNEL_7_8 = 0x3c,
+
+ /* HDMI_AV_CTRL1 */
+ HDMI_AUDIO_SAMPLE_FRE_MASK = 0xf0,
+ HDMI_AUDIO_SAMPLE_FRE_32000 = 0x30,
+ HDMI_AUDIO_SAMPLE_FRE_44100 = 0,
+ HDMI_AUDIO_SAMPLE_FRE_48000 = 0x20,
+ HDMI_AUDIO_SAMPLE_FRE_88200 = 0x80,
+ HDMI_AUDIO_SAMPLE_FRE_96000 = 0xa0,
+ HDMI_AUDIO_SAMPLE_FRE_176400 = 0xc0,
+ HDMI_AUDIO_SAMPLE_FRE_192000 = 0xe0,
+ HDMI_AUDIO_SAMPLE_FRE_768000 = 0x90,
+
+ HDMI_VIDEO_INPUT_FORMAT_MASK = 0x0e,
+ HDMI_VIDEO_INPUT_RGB_YCBCR444 = 0,
+ HDMI_VIDEO_INPUT_YCBCR422 = 0x02,
+ HDMI_VIDEO_DE_MASK = 0x1,
+ HDMI_VIDEO_INTERNAL_DE = 0,
+ HDMI_VIDEO_EXTERNAL_DE = 0x01,
+
+ /* HDMI_VIDEO_CTRL1 */
+ HDMI_VIDEO_OUTPUT_FORMAT_MASK = 0xc0,
+ HDMI_VIDEO_OUTPUT_RGB444 = 0,
+ HDMI_VIDEO_OUTPUT_YCBCR444 = 0x40,
+ HDMI_VIDEO_OUTPUT_YCBCR422 = 0x80,
+ HDMI_VIDEO_INPUT_DATA_DEPTH_MASK = 0x30,
+ HDMI_VIDEO_INPUT_DATA_DEPTH_12BIT = 0,
+ HDMI_VIDEO_INPUT_DATA_DEPTH_10BIT = 0x10,
+ HDMI_VIDEO_INPUT_DATA_DEPTH_8BIT = 0x30,
+ HDMI_VIDEO_INPUT_COLOR_MASK = 1,
+ HDMI_VIDEO_INPUT_COLOR_RGB = 0,
+ HDMI_VIDEO_INPUT_COLOR_YCBCR = 1,
+
+ /* HDMI_EXT_VIDEO_PARA */
+ HDMI_VIDEO_VSYNC_OFFSET_SHIFT = 4,
+ HDMI_VIDEO_VSYNC_ACTIVE_HIGH = BIT(3),
+ HDMI_VIDEO_VSYNC_ACTIVE_LOW = 0,
+ HDMI_VIDEO_HSYNC_ACTIVE_HIGH = BIT(2),
+ HDMI_VIDEO_HSYNC_ACTIVE_LOW = 0,
+ HDMI_VIDEO_MODE_INTERLACE = BIT(1),
+ HDMI_VIDEO_MODE_PROGRESSIVE = 0,
+ HDMI_EXT_VIDEO_SET_EN = BIT(0),
+
+ /* HDMI_CP_AUTO_SEND_CTRL */
+
+ /* HDMI_VIDEO_CTRL2 */
+ HDMI_VIDEO_AV_MUTE_MASK = 0xc0,
+ HDMI_VIDEO_CLR_AV_MUTE = BIT(7),
+ HDMI_VIDEO_SET_AV_MUTE = BIT(6),
+ HDMI_AUDIO_CP_LOGIC_RESET_MASK = BIT(2),
+ HDMI_AUDIO_CP_LOGIC_RESET = BIT(2),
+ HDMI_VIDEO_AUDIO_DISABLE_MASK = 0x3,
+ HDMI_AUDIO_DISABLE = BIT(1),
+ HDMI_VIDEO_DISABLE = BIT(0),
+
+ /* HDMI_CP_BUF_INDEX */
+ HDMI_INFOFRAME_VSI = 0x05,
+ HDMI_INFOFRAME_AVI = 0x06,
+ HDMI_INFOFRAME_AAI = 0x08,
+
+ /* HDMI_INTR_MASK1 */
+ /* HDMI_INTR_STATUS1 */
+ HDMI_INTR_HOTPLUG = BIT(7),
+ HDMI_INTR_MSENS = BIT(6),
+ HDMI_INTR_VSYNC = BIT(5),
+ HDMI_INTR_AUDIO_FIFO_FULL = BIT(4),
+ HDMI_INTR_EDID_MASK = 0x6,
+ HDMI_INTR_EDID_READY = BIT(2),
+ HDMI_INTR_EDID_ERR = BIT(1),
+
+ /* HDMI_HDCP_CTRL */
+ HDMI_VIDEO_MODE_MASK = BIT(1),
+ HDMI_VIDEO_MODE_HDMI = BIT(1),
+
+ /* HDMI_HPG_MENS_STA */
+ HDMI_HPG_IN_STATUS_HIGH = BIT(7),
+ HDMI_MSENS_IN_STATUS_HIGH = BIT(6),
+};
+
+#endif /* __RK3066_HDMI_H__ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
new file mode 100644
index 000000000..0f3eb392f
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * based on exynos_drm_drv.c
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/dma-iommu.h>
+#include <linux/pm_runtime.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/component.h>
+#include <linux/console.h>
+#include <linux/iommu.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_fbdev.h"
+#include "rockchip_drm_gem.h"
+
+#define DRIVER_NAME "rockchip"
+#define DRIVER_DESC "RockChip Soc DRM"
+#define DRIVER_DATE "20140818"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+
+static bool is_support_iommu = true;
+static struct drm_driver rockchip_drm_driver;
+
+/*
+ * Attach a (component) device to the shared drm dma mapping from master drm
+ * device. This is used by the VOPs to map GEM buffers to a common DMA
+ * mapping.
+ */
+int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ struct rockchip_drm_private *private = drm_dev->dev_private;
+ int ret;
+
+ if (!is_support_iommu)
+ return 0;
+
+ ret = iommu_attach_device(private->domain, dev);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to attach iommu device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ struct rockchip_drm_private *private = drm_dev->dev_private;
+ struct iommu_domain *domain = private->domain;
+
+ if (!is_support_iommu)
+ return;
+
+ iommu_detach_device(domain, dev);
+}
+
+static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
+{
+ struct rockchip_drm_private *private = drm_dev->dev_private;
+ struct iommu_domain_geometry *geometry;
+ u64 start, end;
+
+ if (!is_support_iommu)
+ return 0;
+
+ private->domain = iommu_domain_alloc(&platform_bus_type);
+ if (!private->domain)
+ return -ENOMEM;
+
+ geometry = &private->domain->geometry;
+ start = geometry->aperture_start;
+ end = geometry->aperture_end;
+
+ DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n",
+ start, end);
+ drm_mm_init(&private->mm, start, end - start + 1);
+ mutex_init(&private->mm_lock);
+
+ return 0;
+}
+
+static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
+{
+ struct rockchip_drm_private *private = drm_dev->dev_private;
+
+ if (!is_support_iommu)
+ return;
+
+ drm_mm_takedown(&private->mm);
+ iommu_domain_free(private->domain);
+}
+
+static int rockchip_drm_bind(struct device *dev)
+{
+ struct drm_device *drm_dev;
+ struct rockchip_drm_private *private;
+ int ret;
+
+ drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
+ if (IS_ERR(drm_dev))
+ return PTR_ERR(drm_dev);
+
+ dev_set_drvdata(dev, drm_dev);
+
+ private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
+ if (!private) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ drm_dev->dev_private = private;
+
+ INIT_LIST_HEAD(&private->psr_list);
+ mutex_init(&private->psr_list_lock);
+
+ ret = rockchip_drm_init_iommu(drm_dev);
+ if (ret)
+ goto err_free;
+
+ ret = drmm_mode_config_init(drm_dev);
+ if (ret)
+ goto err_iommu_cleanup;
+
+ rockchip_drm_mode_config_init(drm_dev);
+
+ /* Try to bind all sub drivers. */
+ ret = component_bind_all(dev, drm_dev);
+ if (ret)
+ goto err_iommu_cleanup;
+
+ ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
+ if (ret)
+ goto err_unbind_all;
+
+ drm_mode_config_reset(drm_dev);
+
+ /*
+ * enable drm irq mode.
+ * - with irq_enabled = true, we can use the vblank feature.
+ */
+ drm_dev->irq_enabled = true;
+
+ ret = rockchip_drm_fbdev_init(drm_dev);
+ if (ret)
+ goto err_unbind_all;
+
+ /* init kms poll for handling hpd */
+ drm_kms_helper_poll_init(drm_dev);
+
+ ret = drm_dev_register(drm_dev, 0);
+ if (ret)
+ goto err_kms_helper_poll_fini;
+
+ return 0;
+err_kms_helper_poll_fini:
+ drm_kms_helper_poll_fini(drm_dev);
+ rockchip_drm_fbdev_fini(drm_dev);
+err_unbind_all:
+ component_unbind_all(dev, drm_dev);
+err_iommu_cleanup:
+ rockchip_iommu_cleanup(drm_dev);
+err_free:
+ drm_dev_put(drm_dev);
+ return ret;
+}
+
+static void rockchip_drm_unbind(struct device *dev)
+{
+ struct drm_device *drm_dev = dev_get_drvdata(dev);
+
+ drm_dev_unregister(drm_dev);
+
+ rockchip_drm_fbdev_fini(drm_dev);
+ drm_kms_helper_poll_fini(drm_dev);
+
+ drm_atomic_helper_shutdown(drm_dev);
+ component_unbind_all(dev, drm_dev);
+ rockchip_iommu_cleanup(drm_dev);
+
+ drm_dev_put(drm_dev);
+}
+
+static const struct file_operations rockchip_drm_driver_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .mmap = rockchip_gem_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .unlocked_ioctl = drm_ioctl,
+ .compat_ioctl = drm_compat_ioctl,
+ .release = drm_release,
+};
+
+static struct drm_driver rockchip_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ .lastclose = drm_fb_helper_lastclose,
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .gem_free_object_unlocked = rockchip_gem_free_object,
+ .dumb_create = rockchip_gem_dumb_create,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table,
+ .gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table,
+ .gem_prime_vmap = rockchip_gem_prime_vmap,
+ .gem_prime_vunmap = rockchip_gem_prime_vunmap,
+ .gem_prime_mmap = rockchip_gem_mmap_buf,
+ .fops = &rockchip_drm_driver_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int rockchip_drm_sys_suspend(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_suspend(drm);
+}
+
+static int rockchip_drm_sys_resume(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_resume(drm);
+}
+#endif
+
+static const struct dev_pm_ops rockchip_drm_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
+ rockchip_drm_sys_resume)
+};
+
+#define MAX_ROCKCHIP_SUB_DRIVERS 16
+static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
+static int num_rockchip_sub_drivers;
+
+/*
+ * Check if a vop endpoint is leading to a rockchip subdriver or bridge.
+ * Should be called from the component bind stage of the drivers
+ * to ensure that all subdrivers are probed.
+ *
+ * @ep: endpoint of a rockchip vop
+ *
+ * returns true if subdriver, false if external bridge and -ENODEV
+ * if remote port does not contain a device.
+ */
+int rockchip_drm_endpoint_is_subdriver(struct device_node *ep)
+{
+ struct device_node *node = of_graph_get_remote_port_parent(ep);
+ struct platform_device *pdev;
+ struct device_driver *drv;
+ int i;
+
+ if (!node)
+ return -ENODEV;
+
+ /* status disabled will prevent creation of platform-devices */
+ pdev = of_find_device_by_node(node);
+ of_node_put(node);
+ if (!pdev)
+ return -ENODEV;
+
+ /*
+ * All rockchip subdrivers have probed at this point, so
+ * any device not having a driver now is an external bridge.
+ */
+ drv = pdev->dev.driver;
+ if (!drv) {
+ platform_device_put(pdev);
+ return false;
+ }
+
+ for (i = 0; i < num_rockchip_sub_drivers; i++) {
+ if (rockchip_sub_drivers[i] == to_platform_driver(drv)) {
+ platform_device_put(pdev);
+ return true;
+ }
+ }
+
+ platform_device_put(pdev);
+ return false;
+}
+
+static int compare_dev(struct device *dev, void *data)
+{
+ return dev == (struct device *)data;
+}
+
+static void rockchip_drm_match_remove(struct device *dev)
+{
+ struct device_link *link;
+
+ list_for_each_entry(link, &dev->links.consumers, s_node)
+ device_link_del(link);
+}
+
+static struct component_match *rockchip_drm_match_add(struct device *dev)
+{
+ struct component_match *match = NULL;
+ int i;
+
+ for (i = 0; i < num_rockchip_sub_drivers; i++) {
+ struct platform_driver *drv = rockchip_sub_drivers[i];
+ struct device *p = NULL, *d;
+
+ do {
+ d = platform_find_device_by_driver(p, &drv->driver);
+ put_device(p);
+ p = d;
+
+ if (!d)
+ break;
+
+ device_link_add(dev, d, DL_FLAG_STATELESS);
+ component_match_add(dev, &match, compare_dev, d);
+ } while (true);
+ }
+
+ if (IS_ERR(match))
+ rockchip_drm_match_remove(dev);
+
+ return match ?: ERR_PTR(-ENODEV);
+}
+
+static const struct component_master_ops rockchip_drm_ops = {
+ .bind = rockchip_drm_bind,
+ .unbind = rockchip_drm_unbind,
+};
+
+static int rockchip_drm_platform_of_probe(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *port;
+ bool found = false;
+ int i;
+
+ if (!np)
+ return -ENODEV;
+
+ for (i = 0;; i++) {
+ struct device_node *iommu;
+
+ port = of_parse_phandle(np, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ iommu = of_parse_phandle(port->parent, "iommus", 0);
+ if (!iommu || !of_device_is_available(iommu->parent)) {
+ DRM_DEV_DEBUG(dev,
+ "no iommu attached for %pOF, using non-iommu buffers\n",
+ port->parent);
+ /*
+ * if there is a crtc not support iommu, force set all
+ * crtc use non-iommu buffer.
+ */
+ is_support_iommu = false;
+ }
+
+ found = true;
+
+ of_node_put(iommu);
+ of_node_put(port);
+ }
+
+ if (i == 0) {
+ DRM_DEV_ERROR(dev, "missing 'ports' property\n");
+ return -ENODEV;
+ }
+
+ if (!found) {
+ DRM_DEV_ERROR(dev,
+ "No available vop found for display-subsystem.\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int rockchip_drm_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match = NULL;
+ int ret;
+
+ ret = rockchip_drm_platform_of_probe(dev);
+ if (ret)
+ return ret;
+
+ match = rockchip_drm_match_add(dev);
+ if (IS_ERR(match))
+ return PTR_ERR(match);
+
+ ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
+ if (ret < 0) {
+ rockchip_drm_match_remove(dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rockchip_drm_platform_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &rockchip_drm_ops);
+
+ rockchip_drm_match_remove(&pdev->dev);
+
+ return 0;
+}
+
+static void rockchip_drm_platform_shutdown(struct platform_device *pdev)
+{
+ struct drm_device *drm = platform_get_drvdata(pdev);
+
+ if (drm)
+ drm_atomic_helper_shutdown(drm);
+}
+
+static const struct of_device_id rockchip_drm_dt_ids[] = {
+ { .compatible = "rockchip,display-subsystem", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);
+
+static struct platform_driver rockchip_drm_platform_driver = {
+ .probe = rockchip_drm_platform_probe,
+ .remove = rockchip_drm_platform_remove,
+ .shutdown = rockchip_drm_platform_shutdown,
+ .driver = {
+ .name = "rockchip-drm",
+ .of_match_table = rockchip_drm_dt_ids,
+ .pm = &rockchip_drm_pm_ops,
+ },
+};
+
+#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
+ if (IS_ENABLED(cond) && \
+ !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
+ rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
+}
+
+static int __init rockchip_drm_init(void)
+{
+ int ret;
+
+ num_rockchip_sub_drivers = 0;
+ ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP);
+ ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
+ CONFIG_ROCKCHIP_LVDS);
+ ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
+ CONFIG_ROCKCHIP_ANALOGIX_DP);
+ ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
+ ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
+ CONFIG_ROCKCHIP_DW_HDMI);
+ ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver,
+ CONFIG_ROCKCHIP_DW_MIPI_DSI);
+ ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
+ ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver,
+ CONFIG_ROCKCHIP_RK3066_HDMI);
+
+ ret = platform_register_drivers(rockchip_sub_drivers,
+ num_rockchip_sub_drivers);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&rockchip_drm_platform_driver);
+ if (ret)
+ goto err_unreg_drivers;
+
+ return 0;
+
+err_unreg_drivers:
+ platform_unregister_drivers(rockchip_sub_drivers,
+ num_rockchip_sub_drivers);
+ return ret;
+}
+
+static void __exit rockchip_drm_fini(void)
+{
+ platform_driver_unregister(&rockchip_drm_platform_driver);
+
+ platform_unregister_drivers(rockchip_sub_drivers,
+ num_rockchip_sub_drivers);
+}
+
+module_init(rockchip_drm_init);
+module_exit(rockchip_drm_fini);
+
+MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
+MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
new file mode 100644
index 000000000..e33c2dcd0
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ *
+ * based on exynos_drm_drv.h
+ */
+
+#ifndef _ROCKCHIP_DRM_DRV_H
+#define _ROCKCHIP_DRM_DRV_H
+
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem.h>
+
+#include <linux/module.h>
+#include <linux/component.h>
+
+#define ROCKCHIP_MAX_FB_BUFFER 3
+#define ROCKCHIP_MAX_CONNECTOR 2
+#define ROCKCHIP_MAX_CRTC 2
+
+struct drm_device;
+struct drm_connector;
+struct iommu_domain;
+
+struct rockchip_crtc_state {
+ struct drm_crtc_state base;
+ int output_type;
+ int output_mode;
+ int output_bpc;
+ int output_flags;
+ bool enable_afbc;
+};
+#define to_rockchip_crtc_state(s) \
+ container_of(s, struct rockchip_crtc_state, base)
+
+/*
+ * Rockchip drm private structure.
+ *
+ * @crtc: array of enabled CRTCs, used to map from "pipe" to drm_crtc.
+ * @num_pipe: number of pipes for this device.
+ * @mm_lock: protect drm_mm on multi-threads.
+ */
+struct rockchip_drm_private {
+ struct drm_fb_helper fbdev_helper;
+ struct drm_gem_object *fbdev_bo;
+ struct iommu_domain *domain;
+ struct mutex mm_lock;
+ struct drm_mm mm;
+ struct list_head psr_list;
+ struct mutex psr_list_lock;
+};
+
+int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
+ struct device *dev);
+void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
+ struct device *dev);
+int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout);
+
+int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
+extern struct platform_driver cdn_dp_driver;
+extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
+extern struct platform_driver dw_mipi_dsi_rockchip_driver;
+extern struct platform_driver inno_hdmi_driver;
+extern struct platform_driver rockchip_dp_driver;
+extern struct platform_driver rockchip_lvds_driver;
+extern struct platform_driver vop_platform_driver;
+extern struct platform_driver rk3066_hdmi_driver;
+#endif /* _ROCKCHIP_DRM_DRV_H_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
new file mode 100644
index 000000000..3aa37e177
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#include <linux/kernel.h>
+
+#include <drm/drm.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_probe_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_gem.h"
+
+static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
+ .destroy = drm_gem_fb_destroy,
+ .create_handle = drm_gem_fb_create_handle,
+ .dirty = drm_atomic_helper_dirtyfb,
+};
+
+static struct drm_framebuffer *
+rockchip_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object **obj, unsigned int num_planes)
+{
+ struct drm_framebuffer *fb;
+ int ret;
+ int i;
+
+ fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+ if (!fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);
+
+ for (i = 0; i < num_planes; i++)
+ fb->obj[i] = obj[i];
+
+ ret = drm_framebuffer_init(dev, fb, &rockchip_drm_fb_funcs);
+ if (ret) {
+ DRM_DEV_ERROR(dev->dev,
+ "Failed to initialize framebuffer: %d\n",
+ ret);
+ kfree(fb);
+ return ERR_PTR(ret);
+ }
+
+ return fb;
+}
+
+static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+static struct drm_framebuffer *
+rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct drm_afbc_framebuffer *afbc_fb;
+ const struct drm_format_info *info;
+ int ret;
+
+ info = drm_get_format_info(dev, mode_cmd);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ afbc_fb = kzalloc(sizeof(*afbc_fb), GFP_KERNEL);
+ if (!afbc_fb)
+ return ERR_PTR(-ENOMEM);
+
+ ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
+ &rockchip_drm_fb_funcs);
+ if (ret) {
+ kfree(afbc_fb);
+ return ERR_PTR(ret);
+ }
+
+ if (drm_is_afbc(mode_cmd->modifier[0])) {
+ int ret, i;
+
+ ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb);
+ if (ret) {
+ struct drm_gem_object **obj = afbc_fb->base.obj;
+
+ for (i = 0; i < info->num_planes; ++i)
+ drm_gem_object_put(obj[i]);
+
+ kfree(afbc_fb);
+ return ERR_PTR(ret);
+ }
+ }
+
+ return &afbc_fb->base;
+}
+
+static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
+ .fb_create = rockchip_fb_create,
+ .output_poll_changed = drm_fb_helper_output_poll_changed,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+struct drm_framebuffer *
+rockchip_drm_framebuffer_init(struct drm_device *dev,
+ const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj)
+{
+ struct drm_framebuffer *fb;
+
+ fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1);
+ if (IS_ERR(fb))
+ return ERR_CAST(fb);
+
+ return fb;
+}
+
+void rockchip_drm_mode_config_init(struct drm_device *dev)
+{
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+
+ /*
+ * set max width and height as default value(4096x4096).
+ * this value would be used to check framebuffer size limitation
+ * at drm_mode_addfb().
+ */
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 4096;
+
+ dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
+ dev->mode_config.helper_private = &rockchip_mode_config_helpers;
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
new file mode 100644
index 000000000..1a6965210
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_DRM_FB_H
+#define _ROCKCHIP_DRM_FB_H
+
+struct drm_framebuffer *
+rockchip_drm_framebuffer_init(struct drm_device *dev,
+ const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj);
+void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
+
+void rockchip_drm_mode_config_init(struct drm_device *dev);
+#endif /* _ROCKCHIP_DRM_FB_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
new file mode 100644
index 000000000..2fdc455c4
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#include <drm/drm.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_probe_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_fbdev.h"
+
+#define PREFERRED_BPP 32
+#define to_drm_private(x) \
+ container_of(x, struct rockchip_drm_private, fbdev_helper)
+
+static int rockchip_fbdev_mmap(struct fb_info *info,
+ struct vm_area_struct *vma)
+{
+ struct drm_fb_helper *helper = info->par;
+ struct rockchip_drm_private *private = to_drm_private(helper);
+
+ return rockchip_gem_mmap_buf(private->fbdev_bo, vma);
+}
+
+static const struct fb_ops rockchip_drm_fbdev_ops = {
+ .owner = THIS_MODULE,
+ DRM_FB_HELPER_DEFAULT_OPS,
+ .fb_mmap = rockchip_fbdev_mmap,
+ .fb_fillrect = drm_fb_helper_cfb_fillrect,
+ .fb_copyarea = drm_fb_helper_cfb_copyarea,
+ .fb_imageblit = drm_fb_helper_cfb_imageblit,
+};
+
+static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct rockchip_drm_private *private = to_drm_private(helper);
+ struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+ struct drm_device *dev = helper->dev;
+ struct rockchip_gem_object *rk_obj;
+ struct drm_framebuffer *fb;
+ unsigned int bytes_per_pixel;
+ unsigned long offset;
+ struct fb_info *fbi;
+ size_t size;
+ int ret;
+
+ bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ size = mode_cmd.pitches[0] * mode_cmd.height;
+
+ rk_obj = rockchip_gem_create_object(dev, size, true);
+ if (IS_ERR(rk_obj))
+ return -ENOMEM;
+
+ private->fbdev_bo = &rk_obj->base;
+
+ fbi = drm_fb_helper_alloc_fbi(helper);
+ if (IS_ERR(fbi)) {
+ DRM_DEV_ERROR(dev->dev, "Failed to create framebuffer info.\n");
+ ret = PTR_ERR(fbi);
+ goto out;
+ }
+
+ helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd,
+ private->fbdev_bo);
+ if (IS_ERR(helper->fb)) {
+ DRM_DEV_ERROR(dev->dev,
+ "Failed to allocate DRM framebuffer.\n");
+ ret = PTR_ERR(helper->fb);
+ goto out;
+ }
+
+ fbi->fbops = &rockchip_drm_fbdev_ops;
+
+ fb = helper->fb;
+ drm_fb_helper_fill_info(fbi, helper, sizes);
+
+ offset = fbi->var.xoffset * bytes_per_pixel;
+ offset += fbi->var.yoffset * fb->pitches[0];
+
+ dev->mode_config.fb_base = 0;
+ fbi->screen_base = rk_obj->kvaddr + offset;
+ fbi->screen_size = rk_obj->base.size;
+ fbi->fix.smem_len = rk_obj->base.size;
+
+ DRM_DEBUG_KMS("FB [%dx%d]-%d kvaddr=%p offset=%ld size=%zu\n",
+ fb->width, fb->height, fb->format->depth,
+ rk_obj->kvaddr,
+ offset, size);
+
+ return 0;
+
+out:
+ rockchip_gem_free_object(&rk_obj->base);
+ return ret;
+}
+
+static const struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
+ .fb_probe = rockchip_drm_fbdev_create,
+};
+
+int rockchip_drm_fbdev_init(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *helper;
+ int ret;
+
+ if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
+ return -EINVAL;
+
+ helper = &private->fbdev_helper;
+
+ drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs);
+
+ ret = drm_fb_helper_init(dev, helper);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev->dev,
+ "Failed to initialize drm fb helper - %d.\n",
+ ret);
+ return ret;
+ }
+
+ ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev->dev,
+ "Failed to set initial hw config - %d.\n",
+ ret);
+ goto err_drm_fb_helper_fini;
+ }
+
+ return 0;
+
+err_drm_fb_helper_fini:
+ drm_fb_helper_fini(helper);
+ return ret;
+}
+
+void rockchip_drm_fbdev_fini(struct drm_device *dev)
+{
+ struct rockchip_drm_private *private = dev->dev_private;
+ struct drm_fb_helper *helper;
+
+ helper = &private->fbdev_helper;
+
+ drm_fb_helper_unregister_fbi(helper);
+
+ if (helper->fb)
+ drm_framebuffer_put(helper->fb);
+
+ drm_fb_helper_fini(helper);
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
new file mode 100644
index 000000000..5fb7ac237
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_DRM_FBDEV_H
+#define _ROCKCHIP_DRM_FBDEV_H
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+int rockchip_drm_fbdev_init(struct drm_device *dev);
+void rockchip_drm_fbdev_fini(struct drm_device *dev);
+#else
+static inline int rockchip_drm_fbdev_init(struct drm_device *dev)
+{
+ return 0;
+}
+
+static inline void rockchip_drm_fbdev_fini(struct drm_device *dev)
+{
+}
+#endif
+
+#endif /* _ROCKCHIP_DRM_FBDEV_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
new file mode 100644
index 000000000..6038aafa2
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/iommu.h>
+#include <linux/vmalloc.h>
+
+#include <drm/drm.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_vma_manager.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+
+static int rockchip_gem_iommu_map(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_device *drm = rk_obj->base.dev;
+ struct rockchip_drm_private *private = drm->dev_private;
+ int prot = IOMMU_READ | IOMMU_WRITE;
+ ssize_t ret;
+
+ mutex_lock(&private->mm_lock);
+ ret = drm_mm_insert_node_generic(&private->mm, &rk_obj->mm,
+ rk_obj->base.size, PAGE_SIZE,
+ 0, 0);
+ mutex_unlock(&private->mm_lock);
+
+ if (ret < 0) {
+ DRM_ERROR("out of I/O virtual memory: %zd\n", ret);
+ return ret;
+ }
+
+ rk_obj->dma_addr = rk_obj->mm.start;
+
+ ret = iommu_map_sgtable(private->domain, rk_obj->dma_addr, rk_obj->sgt,
+ prot);
+ if (ret < (ssize_t)rk_obj->base.size) {
+ DRM_ERROR("failed to map buffer: size=%zd request_size=%zd\n",
+ ret, rk_obj->base.size);
+ ret = -ENOMEM;
+ goto err_remove_node;
+ }
+
+ rk_obj->size = ret;
+
+ return 0;
+
+err_remove_node:
+ mutex_lock(&private->mm_lock);
+ drm_mm_remove_node(&rk_obj->mm);
+ mutex_unlock(&private->mm_lock);
+
+ return ret;
+}
+
+static int rockchip_gem_iommu_unmap(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_device *drm = rk_obj->base.dev;
+ struct rockchip_drm_private *private = drm->dev_private;
+
+ iommu_unmap(private->domain, rk_obj->dma_addr, rk_obj->size);
+
+ mutex_lock(&private->mm_lock);
+
+ drm_mm_remove_node(&rk_obj->mm);
+
+ mutex_unlock(&private->mm_lock);
+
+ return 0;
+}
+
+static int rockchip_gem_get_pages(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_device *drm = rk_obj->base.dev;
+ int ret, i;
+ struct scatterlist *s;
+
+ rk_obj->pages = drm_gem_get_pages(&rk_obj->base);
+ if (IS_ERR(rk_obj->pages))
+ return PTR_ERR(rk_obj->pages);
+
+ rk_obj->num_pages = rk_obj->base.size >> PAGE_SHIFT;
+
+ rk_obj->sgt = drm_prime_pages_to_sg(rk_obj->base.dev,
+ rk_obj->pages, rk_obj->num_pages);
+ if (IS_ERR(rk_obj->sgt)) {
+ ret = PTR_ERR(rk_obj->sgt);
+ goto err_put_pages;
+ }
+
+ /*
+ * Fake up the SG table so that dma_sync_sg_for_device() can be used
+ * to flush the pages associated with it.
+ *
+ * TODO: Replace this by drm_clflush_sg() once it can be implemented
+ * without relying on symbols that are not exported.
+ */
+ for_each_sgtable_sg(rk_obj->sgt, s, i)
+ sg_dma_address(s) = sg_phys(s);
+
+ dma_sync_sgtable_for_device(drm->dev, rk_obj->sgt, DMA_TO_DEVICE);
+
+ return 0;
+
+err_put_pages:
+ drm_gem_put_pages(&rk_obj->base, rk_obj->pages, false, false);
+ return ret;
+}
+
+static void rockchip_gem_put_pages(struct rockchip_gem_object *rk_obj)
+{
+ sg_free_table(rk_obj->sgt);
+ kfree(rk_obj->sgt);
+ drm_gem_put_pages(&rk_obj->base, rk_obj->pages, true, true);
+}
+
+static int rockchip_gem_alloc_iommu(struct rockchip_gem_object *rk_obj,
+ bool alloc_kmap)
+{
+ int ret;
+
+ ret = rockchip_gem_get_pages(rk_obj);
+ if (ret < 0)
+ return ret;
+
+ ret = rockchip_gem_iommu_map(rk_obj);
+ if (ret < 0)
+ goto err_free;
+
+ if (alloc_kmap) {
+ rk_obj->kvaddr = vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
+ pgprot_writecombine(PAGE_KERNEL));
+ if (!rk_obj->kvaddr) {
+ DRM_ERROR("failed to vmap() buffer\n");
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+ }
+
+ return 0;
+
+err_unmap:
+ rockchip_gem_iommu_unmap(rk_obj);
+err_free:
+ rockchip_gem_put_pages(rk_obj);
+
+ return ret;
+}
+
+static int rockchip_gem_alloc_dma(struct rockchip_gem_object *rk_obj,
+ bool alloc_kmap)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+
+ rk_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE;
+
+ if (!alloc_kmap)
+ rk_obj->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
+
+ rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
+ &rk_obj->dma_addr, GFP_KERNEL,
+ rk_obj->dma_attrs);
+ if (!rk_obj->kvaddr) {
+ DRM_ERROR("failed to allocate %zu byte dma buffer", obj->size);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj,
+ bool alloc_kmap)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+ struct rockchip_drm_private *private = drm->dev_private;
+
+ if (private->domain)
+ return rockchip_gem_alloc_iommu(rk_obj, alloc_kmap);
+ else
+ return rockchip_gem_alloc_dma(rk_obj, alloc_kmap);
+}
+
+static void rockchip_gem_free_iommu(struct rockchip_gem_object *rk_obj)
+{
+ vunmap(rk_obj->kvaddr);
+ rockchip_gem_iommu_unmap(rk_obj);
+ rockchip_gem_put_pages(rk_obj);
+}
+
+static void rockchip_gem_free_dma(struct rockchip_gem_object *rk_obj)
+{
+ struct drm_gem_object *obj = &rk_obj->base;
+ struct drm_device *drm = obj->dev;
+
+ dma_free_attrs(drm->dev, obj->size, rk_obj->kvaddr, rk_obj->dma_addr,
+ rk_obj->dma_attrs);
+}
+
+static void rockchip_gem_free_buf(struct rockchip_gem_object *rk_obj)
+{
+ if (rk_obj->pages)
+ rockchip_gem_free_iommu(rk_obj);
+ else
+ rockchip_gem_free_dma(rk_obj);
+}
+
+static int rockchip_drm_gem_object_mmap_iommu(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ unsigned int count = obj->size >> PAGE_SHIFT;
+ unsigned long user_count = vma_pages(vma);
+
+ if (user_count == 0)
+ return -ENXIO;
+
+ return vm_map_pages(vma, rk_obj->pages, count);
+}
+
+static int rockchip_drm_gem_object_mmap_dma(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ struct drm_device *drm = obj->dev;
+
+ return dma_mmap_attrs(drm->dev, vma, rk_obj->kvaddr, rk_obj->dma_addr,
+ obj->size, rk_obj->dma_attrs);
+}
+
+static int rockchip_drm_gem_object_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ int ret;
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+
+ /*
+ * We allocated a struct page table for rk_obj, so clear
+ * VM_PFNMAP flag that was set by drm_gem_mmap_obj()/drm_gem_mmap().
+ */
+ vma->vm_flags &= ~VM_PFNMAP;
+
+ if (rk_obj->pages)
+ ret = rockchip_drm_gem_object_mmap_iommu(obj, vma);
+ else
+ ret = rockchip_drm_gem_object_mmap_dma(obj, vma);
+
+ return ret;
+}
+
+int rockchip_gem_mmap_buf(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ int ret;
+
+ ret = drm_gem_mmap_obj(obj, obj->size, vma);
+ if (ret)
+ return ret;
+
+ return rockchip_drm_gem_object_mmap(obj, vma);
+}
+
+/* drm driver mmap file operations */
+int rockchip_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj;
+ int ret;
+
+ ret = drm_gem_mmap(filp, vma);
+ if (ret)
+ return ret;
+
+ /*
+ * Set vm_pgoff (used as a fake buffer offset by DRM) to 0 and map the
+ * whole buffer from the start.
+ */
+ vma->vm_pgoff = 0;
+
+ obj = vma->vm_private_data;
+
+ return rockchip_drm_gem_object_mmap(obj, vma);
+}
+
+static void rockchip_gem_release_object(struct rockchip_gem_object *rk_obj)
+{
+ drm_gem_object_release(&rk_obj->base);
+ kfree(rk_obj);
+}
+
+static struct rockchip_gem_object *
+ rockchip_gem_alloc_object(struct drm_device *drm, unsigned int size)
+{
+ struct rockchip_gem_object *rk_obj;
+ struct drm_gem_object *obj;
+
+ size = round_up(size, PAGE_SIZE);
+
+ rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
+ if (!rk_obj)
+ return ERR_PTR(-ENOMEM);
+
+ obj = &rk_obj->base;
+
+ drm_gem_object_init(drm, obj, size);
+
+ return rk_obj;
+}
+
+struct rockchip_gem_object *
+rockchip_gem_create_object(struct drm_device *drm, unsigned int size,
+ bool alloc_kmap)
+{
+ struct rockchip_gem_object *rk_obj;
+ int ret;
+
+ rk_obj = rockchip_gem_alloc_object(drm, size);
+ if (IS_ERR(rk_obj))
+ return rk_obj;
+
+ ret = rockchip_gem_alloc_buf(rk_obj, alloc_kmap);
+ if (ret)
+ goto err_free_rk_obj;
+
+ return rk_obj;
+
+err_free_rk_obj:
+ rockchip_gem_release_object(rk_obj);
+ return ERR_PTR(ret);
+}
+
+/*
+ * rockchip_gem_free_object - (struct drm_driver)->gem_free_object_unlocked
+ * callback function
+ */
+void rockchip_gem_free_object(struct drm_gem_object *obj)
+{
+ struct drm_device *drm = obj->dev;
+ struct rockchip_drm_private *private = drm->dev_private;
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+
+ if (obj->import_attach) {
+ if (private->domain) {
+ rockchip_gem_iommu_unmap(rk_obj);
+ } else {
+ dma_unmap_sgtable(drm->dev, rk_obj->sgt,
+ DMA_BIDIRECTIONAL, 0);
+ }
+ drm_prime_gem_destroy(obj, rk_obj->sgt);
+ } else {
+ rockchip_gem_free_buf(rk_obj);
+ }
+
+ rockchip_gem_release_object(rk_obj);
+}
+
+/*
+ * rockchip_gem_create_with_handle - allocate an object with the given
+ * size and create a gem handle on it
+ *
+ * returns a struct rockchip_gem_object* on success or ERR_PTR values
+ * on failure.
+ */
+static struct rockchip_gem_object *
+rockchip_gem_create_with_handle(struct drm_file *file_priv,
+ struct drm_device *drm, unsigned int size,
+ unsigned int *handle)
+{
+ struct rockchip_gem_object *rk_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ rk_obj = rockchip_gem_create_object(drm, size, false);
+ if (IS_ERR(rk_obj))
+ return ERR_CAST(rk_obj);
+
+ obj = &rk_obj->base;
+
+ /*
+ * allocate a id of idr table where the obj is registered
+ * and handle has the id what user can see.
+ */
+ ret = drm_gem_handle_create(file_priv, obj, handle);
+ if (ret)
+ goto err_handle_create;
+
+ /* drop reference from allocate - handle holds it now. */
+ drm_gem_object_put(obj);
+
+ return rk_obj;
+
+err_handle_create:
+ rockchip_gem_free_object(obj);
+
+ return ERR_PTR(ret);
+}
+
+/*
+ * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
+ * function
+ *
+ * This aligns the pitch and size arguments to the minimum required. wrap
+ * this into your own function if you need bigger alignment.
+ */
+int rockchip_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct rockchip_gem_object *rk_obj;
+ int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+
+ /*
+ * align to 64 bytes since Mali requires it.
+ */
+ args->pitch = ALIGN(min_pitch, 64);
+ args->size = args->pitch * args->height;
+
+ rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
+ &args->handle);
+
+ return PTR_ERR_OR_ZERO(rk_obj);
+}
+
+/*
+ * Allocate a sg_table for this GEM object.
+ * Note: Both the table's contents, and the sg_table itself must be freed by
+ * the caller.
+ * Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
+ */
+struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+ struct drm_device *drm = obj->dev;
+ struct sg_table *sgt;
+ int ret;
+
+ if (rk_obj->pages)
+ return drm_prime_pages_to_sg(obj->dev, rk_obj->pages, rk_obj->num_pages);
+
+ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+ if (!sgt)
+ return ERR_PTR(-ENOMEM);
+
+ ret = dma_get_sgtable_attrs(drm->dev, sgt, rk_obj->kvaddr,
+ rk_obj->dma_addr, obj->size,
+ rk_obj->dma_attrs);
+ if (ret) {
+ DRM_ERROR("failed to allocate sgt, %d\n", ret);
+ kfree(sgt);
+ return ERR_PTR(ret);
+ }
+
+ return sgt;
+}
+
+static int
+rockchip_gem_iommu_map_sg(struct drm_device *drm,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sg,
+ struct rockchip_gem_object *rk_obj)
+{
+ rk_obj->sgt = sg;
+ return rockchip_gem_iommu_map(rk_obj);
+}
+
+static int
+rockchip_gem_dma_map_sg(struct drm_device *drm,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sg,
+ struct rockchip_gem_object *rk_obj)
+{
+ int err = dma_map_sgtable(drm->dev, sg, DMA_BIDIRECTIONAL, 0);
+ if (err)
+ return err;
+
+ if (drm_prime_get_contiguous_size(sg) < attach->dmabuf->size) {
+ DRM_ERROR("failed to map sg_table to contiguous linear address.\n");
+ dma_unmap_sgtable(drm->dev, sg, DMA_BIDIRECTIONAL, 0);
+ return -EINVAL;
+ }
+
+ rk_obj->dma_addr = sg_dma_address(sg->sgl);
+ rk_obj->sgt = sg;
+ return 0;
+}
+
+struct drm_gem_object *
+rockchip_gem_prime_import_sg_table(struct drm_device *drm,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sg)
+{
+ struct rockchip_drm_private *private = drm->dev_private;
+ struct rockchip_gem_object *rk_obj;
+ int ret;
+
+ rk_obj = rockchip_gem_alloc_object(drm, attach->dmabuf->size);
+ if (IS_ERR(rk_obj))
+ return ERR_CAST(rk_obj);
+
+ if (private->domain)
+ ret = rockchip_gem_iommu_map_sg(drm, attach, sg, rk_obj);
+ else
+ ret = rockchip_gem_dma_map_sg(drm, attach, sg, rk_obj);
+
+ if (ret < 0) {
+ DRM_ERROR("failed to import sg table: %d\n", ret);
+ goto err_free_rk_obj;
+ }
+
+ return &rk_obj->base;
+
+err_free_rk_obj:
+ rockchip_gem_release_object(rk_obj);
+ return ERR_PTR(ret);
+}
+
+void *rockchip_gem_prime_vmap(struct drm_gem_object *obj)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+
+ if (rk_obj->pages)
+ return vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
+ pgprot_writecombine(PAGE_KERNEL));
+
+ if (rk_obj->dma_attrs & DMA_ATTR_NO_KERNEL_MAPPING)
+ return NULL;
+
+ return rk_obj->kvaddr;
+}
+
+void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
+{
+ struct rockchip_gem_object *rk_obj = to_rockchip_obj(obj);
+
+ if (rk_obj->pages) {
+ vunmap(vaddr);
+ return;
+ }
+
+ /* Nothing to do if allocated by DMA mapping API. */
+}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.h b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
new file mode 100644
index 000000000..7ffc541be
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_DRM_GEM_H
+#define _ROCKCHIP_DRM_GEM_H
+
+#define to_rockchip_obj(x) container_of(x, struct rockchip_gem_object, base)
+
+struct rockchip_gem_object {
+ struct drm_gem_object base;
+ unsigned int flags;
+
+ void *kvaddr;
+ dma_addr_t dma_addr;
+ /* Used when IOMMU is disabled */
+ unsigned long dma_attrs;
+
+ /* Used when IOMMU is enabled */
+ struct drm_mm_node mm;
+ unsigned long num_pages;
+ struct page **pages;
+ struct sg_table *sgt;
+ size_t size;
+};
+
+struct sg_table *rockchip_gem_prime_get_sg_table(struct drm_gem_object *obj);
+struct drm_gem_object *
+rockchip_gem_prime_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sg);
+void *rockchip_gem_prime_vmap(struct drm_gem_object *obj);
+void rockchip_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
+
+/* drm driver mmap file operations */
+int rockchip_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* mmap a gem object to userspace. */
+int rockchip_gem_mmap_buf(struct drm_gem_object *obj,
+ struct vm_area_struct *vma);
+
+struct rockchip_gem_object *
+ rockchip_gem_create_object(struct drm_device *drm, unsigned int size,
+ bool alloc_kmap);
+
+void rockchip_gem_free_object(struct drm_gem_object *obj);
+
+int rockchip_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+#endif /* _ROCKCHIP_DRM_GEM_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
new file mode 100644
index 000000000..682d78fab
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -0,0 +1,2199 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/overflow.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include <drm/drm.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_flip_work.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_self_refresh_helper.h>
+#include <drm/drm_vblank.h>
+
+#ifdef CONFIG_DRM_ANALOGIX_DP
+#include <drm/bridge/analogix_dp.h>
+#endif
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_vop.h"
+#include "rockchip_rgb.h"
+
+#define VOP_WIN_SET(vop, win, name, v) \
+ vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
+#define VOP_SCL_SET(vop, win, name, v) \
+ vop_reg_set(vop, &win->phy->scl->name, win->base, ~0, v, #name)
+#define VOP_SCL_SET_EXT(vop, win, name, v) \
+ vop_reg_set(vop, &win->phy->scl->ext->name, \
+ win->base, ~0, v, #name)
+
+#define VOP_WIN_YUV2YUV_SET(vop, win_yuv2yuv, name, v) \
+ do { \
+ if (win_yuv2yuv && win_yuv2yuv->name.mask) \
+ vop_reg_set(vop, &win_yuv2yuv->name, 0, ~0, v, #name); \
+ } while (0)
+
+#define VOP_WIN_YUV2YUV_COEFFICIENT_SET(vop, win_yuv2yuv, name, v) \
+ do { \
+ if (win_yuv2yuv && win_yuv2yuv->phy->name.mask) \
+ vop_reg_set(vop, &win_yuv2yuv->phy->name, win_yuv2yuv->base, ~0, v, #name); \
+ } while (0)
+
+#define VOP_INTR_SET_MASK(vop, name, mask, v) \
+ vop_reg_set(vop, &vop->data->intr->name, 0, mask, v, #name)
+
+#define VOP_REG_SET(vop, group, name, v) \
+ vop_reg_set(vop, &vop->data->group->name, 0, ~0, v, #name)
+
+#define VOP_INTR_SET_TYPE(vop, name, type, v) \
+ do { \
+ int i, reg = 0, mask = 0; \
+ for (i = 0; i < vop->data->intr->nintrs; i++) { \
+ if (vop->data->intr->intrs[i] & type) { \
+ reg |= (v) << i; \
+ mask |= 1 << i; \
+ } \
+ } \
+ VOP_INTR_SET_MASK(vop, name, mask, reg); \
+ } while (0)
+#define VOP_INTR_GET_TYPE(vop, name, type) \
+ vop_get_intr_type(vop, &vop->data->intr->name, type)
+
+#define VOP_WIN_GET(vop, win, name) \
+ vop_read_reg(vop, win->base, &win->phy->name)
+
+#define VOP_WIN_HAS_REG(win, name) \
+ (!!(win->phy->name.mask))
+
+#define VOP_WIN_GET_YRGBADDR(vop, win) \
+ vop_readl(vop, win->base + win->phy->yrgb_mst.offset)
+
+#define VOP_WIN_TO_INDEX(vop_win) \
+ ((vop_win) - (vop_win)->vop->win)
+
+#define VOP_AFBC_SET(vop, name, v) \
+ do { \
+ if ((vop)->data->afbc) \
+ vop_reg_set((vop), &(vop)->data->afbc->name, \
+ 0, ~0, v, #name); \
+ } while (0)
+
+#define to_vop(x) container_of(x, struct vop, crtc)
+#define to_vop_win(x) container_of(x, struct vop_win, base)
+
+#define AFBC_FMT_RGB565 0x0
+#define AFBC_FMT_U8U8U8U8 0x5
+#define AFBC_FMT_U8U8U8 0x4
+
+#define AFBC_TILE_16x16 BIT(4)
+
+/*
+ * The coefficients of the following matrix are all fixed points.
+ * The format is S2.10 for the 3x3 part of the matrix, and S9.12 for the offsets.
+ * They are all represented in two's complement.
+ */
+static const uint32_t bt601_yuv2rgb[] = {
+ 0x4A8, 0x0, 0x662,
+ 0x4A8, 0x1E6F, 0x1CBF,
+ 0x4A8, 0x812, 0x0,
+ 0x321168, 0x0877CF, 0x2EB127
+};
+
+enum vop_pending {
+ VOP_PENDING_FB_UNREF,
+};
+
+struct vop_win {
+ struct drm_plane base;
+ const struct vop_win_data *data;
+ const struct vop_win_yuv2yuv_data *yuv2yuv_data;
+ struct vop *vop;
+};
+
+struct rockchip_rgb;
+struct vop {
+ struct drm_crtc crtc;
+ struct device *dev;
+ struct drm_device *drm_dev;
+ bool is_enabled;
+
+ struct completion dsp_hold_completion;
+ unsigned int win_enabled;
+
+ /* protected by dev->event_lock */
+ struct drm_pending_vblank_event *event;
+
+ struct drm_flip_work fb_unref_work;
+ unsigned long pending;
+
+ struct completion line_flag_completion;
+
+ const struct vop_data *data;
+
+ uint32_t *regsbak;
+ void __iomem *regs;
+ void __iomem *lut_regs;
+
+ /* physical map length of vop register */
+ uint32_t len;
+
+ /* one time only one process allowed to config the register */
+ spinlock_t reg_lock;
+ /* lock vop irq reg */
+ spinlock_t irq_lock;
+ /* protects crtc enable/disable */
+ struct mutex vop_lock;
+
+ unsigned int irq;
+
+ /* vop AHP clk */
+ struct clk *hclk;
+ /* vop dclk */
+ struct clk *dclk;
+ /* vop share memory frequency */
+ struct clk *aclk;
+
+ /* vop dclk reset */
+ struct reset_control *dclk_rst;
+
+ /* optional internal rgb encoder */
+ struct rockchip_rgb *rgb;
+
+ struct vop_win win[];
+};
+
+static inline void vop_writel(struct vop *vop, uint32_t offset, uint32_t v)
+{
+ writel(v, vop->regs + offset);
+ vop->regsbak[offset >> 2] = v;
+}
+
+static inline uint32_t vop_readl(struct vop *vop, uint32_t offset)
+{
+ return readl(vop->regs + offset);
+}
+
+static inline uint32_t vop_read_reg(struct vop *vop, uint32_t base,
+ const struct vop_reg *reg)
+{
+ return (vop_readl(vop, base + reg->offset) >> reg->shift) & reg->mask;
+}
+
+static void vop_reg_set(struct vop *vop, const struct vop_reg *reg,
+ uint32_t _offset, uint32_t _mask, uint32_t v,
+ const char *reg_name)
+{
+ int offset, mask, shift;
+
+ if (!reg || !reg->mask) {
+ DRM_DEV_DEBUG(vop->dev, "Warning: not support %s\n", reg_name);
+ return;
+ }
+
+ offset = reg->offset + _offset;
+ mask = reg->mask & _mask;
+ shift = reg->shift;
+
+ if (reg->write_mask) {
+ v = ((v << shift) & 0xffff) | (mask << (shift + 16));
+ } else {
+ uint32_t cached_val = vop->regsbak[offset >> 2];
+
+ v = (cached_val & ~(mask << shift)) | ((v & mask) << shift);
+ vop->regsbak[offset >> 2] = v;
+ }
+
+ if (reg->relaxed)
+ writel_relaxed(v, vop->regs + offset);
+ else
+ writel(v, vop->regs + offset);
+}
+
+static inline uint32_t vop_get_intr_type(struct vop *vop,
+ const struct vop_reg *reg, int type)
+{
+ uint32_t i, ret = 0;
+ uint32_t regs = vop_read_reg(vop, 0, reg);
+
+ for (i = 0; i < vop->data->intr->nintrs; i++) {
+ if ((type & vop->data->intr->intrs[i]) && (regs & 1 << i))
+ ret |= vop->data->intr->intrs[i];
+ }
+
+ return ret;
+}
+
+static inline void vop_cfg_done(struct vop *vop)
+{
+ VOP_REG_SET(vop, common, cfg_done, 1);
+}
+
+static bool has_rb_swapped(uint32_t version, uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_BGR565:
+ return true;
+ /*
+ * full framework (IP version 3.x) only need rb swapped for RGB888 and
+ * little framework (IP version 2.x) only need rb swapped for BGR888,
+ * check for 3.x to also only rb swap BGR888 for unknown vop version
+ */
+ case DRM_FORMAT_RGB888:
+ return VOP_MAJOR(version) == 3;
+ case DRM_FORMAT_BGR888:
+ return VOP_MAJOR(version) != 3;
+ default:
+ return false;
+ }
+}
+
+static enum vop_data_format vop_convert_format(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ return VOP_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return VOP_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ return VOP_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP_FMT_YUV420SP;
+ case DRM_FORMAT_NV16:
+ return VOP_FMT_YUV422SP;
+ case DRM_FORMAT_NV24:
+ return VOP_FMT_YUV444SP;
+ default:
+ DRM_ERROR("unsupported format[%08x]\n", format);
+ return -EINVAL;
+ }
+}
+
+static int vop_convert_afbc_format(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ return AFBC_FMT_U8U8U8U8;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return AFBC_FMT_U8U8U8;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ return AFBC_FMT_RGB565;
+ /* either of the below should not be reachable */
+ default:
+ DRM_WARN_ONCE("unsupported AFBC format[%08x]\n", format);
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static uint16_t scl_vop_cal_scale(enum scale_mode mode, uint32_t src,
+ uint32_t dst, bool is_horizontal,
+ int vsu_mode, int *vskiplines)
+{
+ uint16_t val = 1 << SCL_FT_DEFAULT_FIXPOINT_SHIFT;
+
+ if (vskiplines)
+ *vskiplines = 0;
+
+ if (is_horizontal) {
+ if (mode == SCALE_UP)
+ val = GET_SCL_FT_BIC(src, dst);
+ else if (mode == SCALE_DOWN)
+ val = GET_SCL_FT_BILI_DN(src, dst);
+ } else {
+ if (mode == SCALE_UP) {
+ if (vsu_mode == SCALE_UP_BIL)
+ val = GET_SCL_FT_BILI_UP(src, dst);
+ else
+ val = GET_SCL_FT_BIC(src, dst);
+ } else if (mode == SCALE_DOWN) {
+ if (vskiplines) {
+ *vskiplines = scl_get_vskiplines(src, dst);
+ val = scl_get_bili_dn_vskip(src, dst,
+ *vskiplines);
+ } else {
+ val = GET_SCL_FT_BILI_DN(src, dst);
+ }
+ }
+ }
+
+ return val;
+}
+
+static void scl_vop_cal_scl_fac(struct vop *vop, const struct vop_win_data *win,
+ uint32_t src_w, uint32_t src_h, uint32_t dst_w,
+ uint32_t dst_h, const struct drm_format_info *info)
+{
+ uint16_t yrgb_hor_scl_mode, yrgb_ver_scl_mode;
+ uint16_t cbcr_hor_scl_mode = SCALE_NONE;
+ uint16_t cbcr_ver_scl_mode = SCALE_NONE;
+ bool is_yuv = false;
+ uint16_t cbcr_src_w = src_w / info->hsub;
+ uint16_t cbcr_src_h = src_h / info->vsub;
+ uint16_t vsu_mode;
+ uint16_t lb_mode;
+ uint32_t val;
+ int vskiplines;
+
+ if (info->is_yuv)
+ is_yuv = true;
+
+ if (dst_w > 3840) {
+ DRM_DEV_ERROR(vop->dev, "Maximum dst width (3840) exceeded\n");
+ return;
+ }
+
+ if (!win->phy->scl->ext) {
+ VOP_SCL_SET(vop, win, scale_yrgb_x,
+ scl_cal_scale2(src_w, dst_w));
+ VOP_SCL_SET(vop, win, scale_yrgb_y,
+ scl_cal_scale2(src_h, dst_h));
+ if (is_yuv) {
+ VOP_SCL_SET(vop, win, scale_cbcr_x,
+ scl_cal_scale2(cbcr_src_w, dst_w));
+ VOP_SCL_SET(vop, win, scale_cbcr_y,
+ scl_cal_scale2(cbcr_src_h, dst_h));
+ }
+ return;
+ }
+
+ yrgb_hor_scl_mode = scl_get_scl_mode(src_w, dst_w);
+ yrgb_ver_scl_mode = scl_get_scl_mode(src_h, dst_h);
+
+ if (is_yuv) {
+ cbcr_hor_scl_mode = scl_get_scl_mode(cbcr_src_w, dst_w);
+ cbcr_ver_scl_mode = scl_get_scl_mode(cbcr_src_h, dst_h);
+ if (cbcr_hor_scl_mode == SCALE_DOWN)
+ lb_mode = scl_vop_cal_lb_mode(dst_w, true);
+ else
+ lb_mode = scl_vop_cal_lb_mode(cbcr_src_w, true);
+ } else {
+ if (yrgb_hor_scl_mode == SCALE_DOWN)
+ lb_mode = scl_vop_cal_lb_mode(dst_w, false);
+ else
+ lb_mode = scl_vop_cal_lb_mode(src_w, false);
+ }
+
+ VOP_SCL_SET_EXT(vop, win, lb_mode, lb_mode);
+ if (lb_mode == LB_RGB_3840X2) {
+ if (yrgb_ver_scl_mode != SCALE_NONE) {
+ DRM_DEV_ERROR(vop->dev, "not allow yrgb ver scale\n");
+ return;
+ }
+ if (cbcr_ver_scl_mode != SCALE_NONE) {
+ DRM_DEV_ERROR(vop->dev, "not allow cbcr ver scale\n");
+ return;
+ }
+ vsu_mode = SCALE_UP_BIL;
+ } else if (lb_mode == LB_RGB_2560X4) {
+ vsu_mode = SCALE_UP_BIL;
+ } else {
+ vsu_mode = SCALE_UP_BIC;
+ }
+
+ val = scl_vop_cal_scale(yrgb_hor_scl_mode, src_w, dst_w,
+ true, 0, NULL);
+ VOP_SCL_SET(vop, win, scale_yrgb_x, val);
+ val = scl_vop_cal_scale(yrgb_ver_scl_mode, src_h, dst_h,
+ false, vsu_mode, &vskiplines);
+ VOP_SCL_SET(vop, win, scale_yrgb_y, val);
+
+ VOP_SCL_SET_EXT(vop, win, vsd_yrgb_gt4, vskiplines == 4);
+ VOP_SCL_SET_EXT(vop, win, vsd_yrgb_gt2, vskiplines == 2);
+
+ VOP_SCL_SET_EXT(vop, win, yrgb_hor_scl_mode, yrgb_hor_scl_mode);
+ VOP_SCL_SET_EXT(vop, win, yrgb_ver_scl_mode, yrgb_ver_scl_mode);
+ VOP_SCL_SET_EXT(vop, win, yrgb_hsd_mode, SCALE_DOWN_BIL);
+ VOP_SCL_SET_EXT(vop, win, yrgb_vsd_mode, SCALE_DOWN_BIL);
+ VOP_SCL_SET_EXT(vop, win, yrgb_vsu_mode, vsu_mode);
+ if (is_yuv) {
+ val = scl_vop_cal_scale(cbcr_hor_scl_mode, cbcr_src_w,
+ dst_w, true, 0, NULL);
+ VOP_SCL_SET(vop, win, scale_cbcr_x, val);
+ val = scl_vop_cal_scale(cbcr_ver_scl_mode, cbcr_src_h,
+ dst_h, false, vsu_mode, &vskiplines);
+ VOP_SCL_SET(vop, win, scale_cbcr_y, val);
+
+ VOP_SCL_SET_EXT(vop, win, vsd_cbcr_gt4, vskiplines == 4);
+ VOP_SCL_SET_EXT(vop, win, vsd_cbcr_gt2, vskiplines == 2);
+ VOP_SCL_SET_EXT(vop, win, cbcr_hor_scl_mode, cbcr_hor_scl_mode);
+ VOP_SCL_SET_EXT(vop, win, cbcr_ver_scl_mode, cbcr_ver_scl_mode);
+ VOP_SCL_SET_EXT(vop, win, cbcr_hsd_mode, SCALE_DOWN_BIL);
+ VOP_SCL_SET_EXT(vop, win, cbcr_vsd_mode, SCALE_DOWN_BIL);
+ VOP_SCL_SET_EXT(vop, win, cbcr_vsu_mode, vsu_mode);
+ }
+}
+
+static void vop_dsp_hold_valid_irq_enable(struct vop *vop)
+{
+ unsigned long flags;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ VOP_INTR_SET_TYPE(vop, clear, DSP_HOLD_VALID_INTR, 1);
+ VOP_INTR_SET_TYPE(vop, enable, DSP_HOLD_VALID_INTR, 1);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+
+static void vop_dsp_hold_valid_irq_disable(struct vop *vop)
+{
+ unsigned long flags;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ VOP_INTR_SET_TYPE(vop, enable, DSP_HOLD_VALID_INTR, 0);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+
+/*
+ * (1) each frame starts at the start of the Vsync pulse which is signaled by
+ * the "FRAME_SYNC" interrupt.
+ * (2) the active data region of each frame ends at dsp_vact_end
+ * (3) we should program this same number (dsp_vact_end) into dsp_line_frag_num,
+ * to get "LINE_FLAG" interrupt at the end of the active on screen data.
+ *
+ * VOP_INTR_CTRL0.dsp_line_frag_num = VOP_DSP_VACT_ST_END.dsp_vact_end
+ * Interrupts
+ * LINE_FLAG -------------------------------+
+ * FRAME_SYNC ----+ |
+ * | |
+ * v v
+ * | Vsync | Vbp | Vactive | Vfp |
+ * ^ ^ ^ ^
+ * | | | |
+ * | | | |
+ * dsp_vs_end ------------+ | | | VOP_DSP_VTOTAL_VS_END
+ * dsp_vact_start --------------+ | | VOP_DSP_VACT_ST_END
+ * dsp_vact_end ----------------------------+ | VOP_DSP_VACT_ST_END
+ * dsp_total -------------------------------------+ VOP_DSP_VTOTAL_VS_END
+ */
+static bool vop_line_flag_irq_is_enabled(struct vop *vop)
+{
+ uint32_t line_flag_irq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ line_flag_irq = VOP_INTR_GET_TYPE(vop, enable, LINE_FLAG_INTR);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+
+ return !!line_flag_irq;
+}
+
+static void vop_line_flag_irq_enable(struct vop *vop)
+{
+ unsigned long flags;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ VOP_INTR_SET_TYPE(vop, clear, LINE_FLAG_INTR, 1);
+ VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+
+static void vop_line_flag_irq_disable(struct vop *vop)
+{
+ unsigned long flags;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 0);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+
+static int vop_core_clks_enable(struct vop *vop)
+{
+ int ret;
+
+ ret = clk_enable(vop->hclk);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_enable(vop->aclk);
+ if (ret < 0)
+ goto err_disable_hclk;
+
+ return 0;
+
+err_disable_hclk:
+ clk_disable(vop->hclk);
+ return ret;
+}
+
+static void vop_core_clks_disable(struct vop *vop)
+{
+ clk_disable(vop->aclk);
+ clk_disable(vop->hclk);
+}
+
+static void vop_win_disable(struct vop *vop, const struct vop_win *vop_win)
+{
+ const struct vop_win_data *win = vop_win->data;
+
+ if (win->phy->scl && win->phy->scl->ext) {
+ VOP_SCL_SET_EXT(vop, win, yrgb_hor_scl_mode, SCALE_NONE);
+ VOP_SCL_SET_EXT(vop, win, yrgb_ver_scl_mode, SCALE_NONE);
+ VOP_SCL_SET_EXT(vop, win, cbcr_hor_scl_mode, SCALE_NONE);
+ VOP_SCL_SET_EXT(vop, win, cbcr_ver_scl_mode, SCALE_NONE);
+ }
+
+ VOP_WIN_SET(vop, win, enable, 0);
+ vop->win_enabled &= ~BIT(VOP_WIN_TO_INDEX(vop_win));
+}
+
+static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
+{
+ struct vop *vop = to_vop(crtc);
+ int ret, i;
+
+ ret = pm_runtime_get_sync(vop->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(vop->dev, "failed to get pm runtime: %d\n", ret);
+ return ret;
+ }
+
+ ret = vop_core_clks_enable(vop);
+ if (WARN_ON(ret < 0))
+ goto err_put_pm_runtime;
+
+ ret = clk_enable(vop->dclk);
+ if (WARN_ON(ret < 0))
+ goto err_disable_core;
+
+ /*
+ * Slave iommu shares power, irq and clock with vop. It was associated
+ * automatically with this master device via common driver code.
+ * Now that we have enabled the clock we attach it to the shared drm
+ * mapping.
+ */
+ ret = rockchip_drm_dma_attach_device(vop->drm_dev, vop->dev);
+ if (ret) {
+ DRM_DEV_ERROR(vop->dev,
+ "failed to attach dma mapping, %d\n", ret);
+ goto err_disable_dclk;
+ }
+
+ spin_lock(&vop->reg_lock);
+ for (i = 0; i < vop->len; i += 4)
+ writel_relaxed(vop->regsbak[i / 4], vop->regs + i);
+
+ /*
+ * We need to make sure that all windows are disabled before we
+ * enable the crtc. Otherwise we might try to scan from a destroyed
+ * buffer later.
+ *
+ * In the case of enable-after-PSR, we don't need to worry about this
+ * case since the buffer is guaranteed to be valid and disabling the
+ * window will result in screen glitches on PSR exit.
+ */
+ if (!old_state || !old_state->self_refresh_active) {
+ for (i = 0; i < vop->data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+
+ vop_win_disable(vop, vop_win);
+ }
+ }
+
+ if (vop->data->afbc) {
+ struct rockchip_crtc_state *s;
+ /*
+ * Disable AFBC and forget there was a vop window with AFBC
+ */
+ VOP_AFBC_SET(vop, enable, 0);
+ s = to_rockchip_crtc_state(crtc->state);
+ s->enable_afbc = false;
+ }
+
+ vop_cfg_done(vop);
+
+ spin_unlock(&vop->reg_lock);
+
+ /*
+ * At here, vop clock & iommu is enable, R/W vop regs would be safe.
+ */
+ vop->is_enabled = true;
+
+ spin_lock(&vop->reg_lock);
+
+ VOP_REG_SET(vop, common, standby, 1);
+
+ spin_unlock(&vop->reg_lock);
+
+ drm_crtc_vblank_on(crtc);
+
+ return 0;
+
+err_disable_dclk:
+ clk_disable(vop->dclk);
+err_disable_core:
+ vop_core_clks_disable(vop);
+err_put_pm_runtime:
+ pm_runtime_put_sync(vop->dev);
+ return ret;
+}
+
+static void rockchip_drm_set_win_enabled(struct drm_crtc *crtc, bool enabled)
+{
+ struct vop *vop = to_vop(crtc);
+ int i;
+
+ spin_lock(&vop->reg_lock);
+
+ for (i = 0; i < vop->data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win = vop_win->data;
+
+ VOP_WIN_SET(vop, win, enable,
+ enabled && (vop->win_enabled & BIT(i)));
+ }
+ vop_cfg_done(vop);
+
+ spin_unlock(&vop->reg_lock);
+}
+
+static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct vop *vop = to_vop(crtc);
+
+ WARN_ON(vop->event);
+
+ if (crtc->state->self_refresh_active)
+ rockchip_drm_set_win_enabled(crtc, false);
+
+ if (crtc->state->self_refresh_active)
+ goto out;
+
+ mutex_lock(&vop->vop_lock);
+
+ drm_crtc_vblank_off(crtc);
+
+ /*
+ * Vop standby will take effect at end of current frame,
+ * if dsp hold valid irq happen, it means standby complete.
+ *
+ * we must wait standby complete when we want to disable aclk,
+ * if not, memory bus maybe dead.
+ */
+ reinit_completion(&vop->dsp_hold_completion);
+ vop_dsp_hold_valid_irq_enable(vop);
+
+ spin_lock(&vop->reg_lock);
+
+ VOP_REG_SET(vop, common, standby, 1);
+
+ spin_unlock(&vop->reg_lock);
+
+ wait_for_completion(&vop->dsp_hold_completion);
+
+ vop_dsp_hold_valid_irq_disable(vop);
+
+ vop->is_enabled = false;
+
+ /*
+ * vop standby complete, so iommu detach is safe.
+ */
+ rockchip_drm_dma_detach_device(vop->drm_dev, vop->dev);
+
+ clk_disable(vop->dclk);
+ vop_core_clks_disable(vop);
+ pm_runtime_put(vop->dev);
+
+ mutex_unlock(&vop->vop_lock);
+
+out:
+ if (crtc->state->event && !crtc->state->active) {
+ spin_lock_irq(&crtc->dev->event_lock);
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ crtc->state->event = NULL;
+ }
+}
+
+static void vop_plane_destroy(struct drm_plane *plane)
+{
+ drm_plane_cleanup(plane);
+}
+
+static inline bool rockchip_afbc(u64 modifier)
+{
+ return modifier == ROCKCHIP_AFBC_MOD;
+}
+
+static bool rockchip_mod_supported(struct drm_plane *plane,
+ u32 format, u64 modifier)
+{
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ return true;
+
+ if (!rockchip_afbc(modifier)) {
+ DRM_DEBUG_KMS("Unsupported format modifier 0x%llx\n", modifier);
+
+ return false;
+ }
+
+ return vop_convert_afbc_format(format) >= 0;
+}
+
+static int vop_plane_atomic_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct drm_crtc *crtc = state->crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_framebuffer *fb = state->fb;
+ struct vop_win *vop_win = to_vop_win(plane);
+ const struct vop_win_data *win = vop_win->data;
+ int ret;
+ int min_scale = win->phy->scl ? FRAC_16_16(1, 8) :
+ DRM_PLANE_HELPER_NO_SCALING;
+ int max_scale = win->phy->scl ? FRAC_16_16(8, 1) :
+ DRM_PLANE_HELPER_NO_SCALING;
+
+ if (!crtc || WARN_ON(!fb))
+ return 0;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ ret = drm_atomic_helper_check_plane_state(state, crtc_state,
+ min_scale, max_scale,
+ true, true);
+ if (ret)
+ return ret;
+
+ if (!state->visible)
+ return 0;
+
+ ret = vop_convert_format(fb->format->format);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Src.x1 can be odd when do clip, but yuv plane start point
+ * need align with 2 pixel.
+ */
+ if (fb->format->is_yuv && ((state->src.x1 >> 16) % 2)) {
+ DRM_ERROR("Invalid Source: Yuv format not support odd xpos\n");
+ return -EINVAL;
+ }
+
+ if (fb->format->is_yuv && state->rotation & DRM_MODE_REFLECT_Y) {
+ DRM_ERROR("Invalid Source: Yuv format does not support this rotation\n");
+ return -EINVAL;
+ }
+
+ if (rockchip_afbc(fb->modifier)) {
+ struct vop *vop = to_vop(crtc);
+
+ if (!vop->data->afbc) {
+ DRM_ERROR("vop does not support AFBC\n");
+ return -EINVAL;
+ }
+
+ ret = vop_convert_afbc_format(fb->format->format);
+ if (ret < 0)
+ return ret;
+
+ if (state->src.x1 || state->src.y1) {
+ DRM_ERROR("AFBC does not support offset display, xpos=%d, ypos=%d, offset=%d\n", state->src.x1, state->src.y1, fb->offsets[0]);
+ return -EINVAL;
+ }
+
+ if (state->rotation && state->rotation != DRM_MODE_ROTATE_0) {
+ DRM_ERROR("No rotation support in AFBC, rotation=%d\n",
+ state->rotation);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void vop_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct vop_win *vop_win = to_vop_win(plane);
+ struct vop *vop = to_vop(old_state->crtc);
+
+ if (!old_state->crtc)
+ return;
+
+ spin_lock(&vop->reg_lock);
+
+ vop_win_disable(vop, vop_win);
+
+ spin_unlock(&vop->reg_lock);
+}
+
+static void vop_plane_atomic_update(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_plane_state *state = plane->state;
+ struct drm_crtc *crtc = state->crtc;
+ struct vop_win *vop_win = to_vop_win(plane);
+ const struct vop_win_data *win = vop_win->data;
+ const struct vop_win_yuv2yuv_data *win_yuv2yuv = vop_win->yuv2yuv_data;
+ struct vop *vop = to_vop(state->crtc);
+ struct drm_framebuffer *fb = state->fb;
+ unsigned int actual_w, actual_h;
+ unsigned int dsp_stx, dsp_sty;
+ uint32_t act_info, dsp_info, dsp_st;
+ struct drm_rect *src = &state->src;
+ struct drm_rect *dest = &state->dst;
+ struct drm_gem_object *obj, *uv_obj;
+ struct rockchip_gem_object *rk_obj, *rk_uv_obj;
+ unsigned long offset;
+ dma_addr_t dma_addr;
+ uint32_t val;
+ bool rb_swap;
+ int win_index = VOP_WIN_TO_INDEX(vop_win);
+ int format;
+ int is_yuv = fb->format->is_yuv;
+ int i;
+
+ /*
+ * can't update plane when vop is disabled.
+ */
+ if (WARN_ON(!crtc))
+ return;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ if (!state->visible) {
+ vop_plane_atomic_disable(plane, old_state);
+ return;
+ }
+
+ obj = fb->obj[0];
+ rk_obj = to_rockchip_obj(obj);
+
+ actual_w = drm_rect_width(src) >> 16;
+ actual_h = drm_rect_height(src) >> 16;
+ act_info = (actual_h - 1) << 16 | ((actual_w - 1) & 0xffff);
+
+ dsp_info = (drm_rect_height(dest) - 1) << 16;
+ dsp_info |= (drm_rect_width(dest) - 1) & 0xffff;
+
+ dsp_stx = dest->x1 + crtc->mode.htotal - crtc->mode.hsync_start;
+ dsp_sty = dest->y1 + crtc->mode.vtotal - crtc->mode.vsync_start;
+ dsp_st = dsp_sty << 16 | (dsp_stx & 0xffff);
+
+ offset = (src->x1 >> 16) * fb->format->cpp[0];
+ offset += (src->y1 >> 16) * fb->pitches[0];
+ dma_addr = rk_obj->dma_addr + offset + fb->offsets[0];
+
+ /*
+ * For y-mirroring we need to move address
+ * to the beginning of the last line.
+ */
+ if (state->rotation & DRM_MODE_REFLECT_Y)
+ dma_addr += (actual_h - 1) * fb->pitches[0];
+
+ format = vop_convert_format(fb->format->format);
+
+ spin_lock(&vop->reg_lock);
+
+ if (rockchip_afbc(fb->modifier)) {
+ int afbc_format = vop_convert_afbc_format(fb->format->format);
+
+ VOP_AFBC_SET(vop, format, afbc_format | AFBC_TILE_16x16);
+ VOP_AFBC_SET(vop, hreg_block_split, 0);
+ VOP_AFBC_SET(vop, win_sel, VOP_WIN_TO_INDEX(vop_win));
+ VOP_AFBC_SET(vop, hdr_ptr, dma_addr);
+ VOP_AFBC_SET(vop, pic_size, act_info);
+ }
+
+ VOP_WIN_SET(vop, win, format, format);
+ VOP_WIN_SET(vop, win, yrgb_vir, DIV_ROUND_UP(fb->pitches[0], 4));
+ VOP_WIN_SET(vop, win, yrgb_mst, dma_addr);
+ VOP_WIN_YUV2YUV_SET(vop, win_yuv2yuv, y2r_en, is_yuv);
+ VOP_WIN_SET(vop, win, y_mir_en,
+ (state->rotation & DRM_MODE_REFLECT_Y) ? 1 : 0);
+ VOP_WIN_SET(vop, win, x_mir_en,
+ (state->rotation & DRM_MODE_REFLECT_X) ? 1 : 0);
+
+ if (is_yuv) {
+ int hsub = fb->format->hsub;
+ int vsub = fb->format->vsub;
+ int bpp = fb->format->cpp[1];
+
+ uv_obj = fb->obj[1];
+ rk_uv_obj = to_rockchip_obj(uv_obj);
+
+ offset = (src->x1 >> 16) * bpp / hsub;
+ offset += (src->y1 >> 16) * fb->pitches[1] / vsub;
+
+ dma_addr = rk_uv_obj->dma_addr + offset + fb->offsets[1];
+ VOP_WIN_SET(vop, win, uv_vir, DIV_ROUND_UP(fb->pitches[1], 4));
+ VOP_WIN_SET(vop, win, uv_mst, dma_addr);
+
+ for (i = 0; i < NUM_YUV2YUV_COEFFICIENTS; i++) {
+ VOP_WIN_YUV2YUV_COEFFICIENT_SET(vop,
+ win_yuv2yuv,
+ y2r_coefficients[i],
+ bt601_yuv2rgb[i]);
+ }
+ }
+
+ if (win->phy->scl)
+ scl_vop_cal_scl_fac(vop, win, actual_w, actual_h,
+ drm_rect_width(dest), drm_rect_height(dest),
+ fb->format);
+
+ VOP_WIN_SET(vop, win, act_info, act_info);
+ VOP_WIN_SET(vop, win, dsp_info, dsp_info);
+ VOP_WIN_SET(vop, win, dsp_st, dsp_st);
+
+ rb_swap = has_rb_swapped(vop->data->version, fb->format->format);
+ VOP_WIN_SET(vop, win, rb_swap, rb_swap);
+
+ /*
+ * Blending win0 with the background color doesn't seem to work
+ * correctly. We only get the background color, no matter the contents
+ * of the win0 framebuffer. However, blending pre-multiplied color
+ * with the default opaque black default background color is a no-op,
+ * so we can just disable blending to get the correct result.
+ */
+ if (fb->format->has_alpha && win_index > 0) {
+ VOP_WIN_SET(vop, win, dst_alpha_ctl,
+ DST_FACTOR_M0(ALPHA_SRC_INVERSE));
+ val = SRC_ALPHA_EN(1) | SRC_COLOR_M0(ALPHA_SRC_PRE_MUL) |
+ SRC_ALPHA_M0(ALPHA_STRAIGHT) |
+ SRC_BLEND_M0(ALPHA_PER_PIX) |
+ SRC_ALPHA_CAL_M0(ALPHA_NO_SATURATION) |
+ SRC_FACTOR_M0(ALPHA_ONE);
+ VOP_WIN_SET(vop, win, src_alpha_ctl, val);
+
+ VOP_WIN_SET(vop, win, alpha_pre_mul, ALPHA_SRC_PRE_MUL);
+ VOP_WIN_SET(vop, win, alpha_mode, ALPHA_PER_PIX);
+ VOP_WIN_SET(vop, win, alpha_en, 1);
+ } else {
+ VOP_WIN_SET(vop, win, src_alpha_ctl, SRC_ALPHA_EN(0));
+ VOP_WIN_SET(vop, win, alpha_en, 0);
+ }
+
+ VOP_WIN_SET(vop, win, enable, 1);
+ vop->win_enabled |= BIT(win_index);
+ spin_unlock(&vop->reg_lock);
+}
+
+static int vop_plane_atomic_async_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct vop_win *vop_win = to_vop_win(plane);
+ const struct vop_win_data *win = vop_win->data;
+ int min_scale = win->phy->scl ? FRAC_16_16(1, 8) :
+ DRM_PLANE_HELPER_NO_SCALING;
+ int max_scale = win->phy->scl ? FRAC_16_16(8, 1) :
+ DRM_PLANE_HELPER_NO_SCALING;
+ struct drm_crtc_state *crtc_state;
+
+ if (plane != state->crtc->cursor)
+ return -EINVAL;
+
+ if (!plane->state)
+ return -EINVAL;
+
+ if (!plane->state->fb)
+ return -EINVAL;
+
+ if (state->state)
+ crtc_state = drm_atomic_get_existing_crtc_state(state->state,
+ state->crtc);
+ else /* Special case for asynchronous cursor updates. */
+ crtc_state = plane->crtc->state;
+
+ return drm_atomic_helper_check_plane_state(plane->state, crtc_state,
+ min_scale, max_scale,
+ true, true);
+}
+
+static void vop_plane_atomic_async_update(struct drm_plane *plane,
+ struct drm_plane_state *new_state)
+{
+ struct vop *vop = to_vop(plane->state->crtc);
+ struct drm_framebuffer *old_fb = plane->state->fb;
+
+ plane->state->crtc_x = new_state->crtc_x;
+ plane->state->crtc_y = new_state->crtc_y;
+ plane->state->crtc_h = new_state->crtc_h;
+ plane->state->crtc_w = new_state->crtc_w;
+ plane->state->src_x = new_state->src_x;
+ plane->state->src_y = new_state->src_y;
+ plane->state->src_h = new_state->src_h;
+ plane->state->src_w = new_state->src_w;
+ swap(plane->state->fb, new_state->fb);
+
+ if (vop->is_enabled) {
+ vop_plane_atomic_update(plane, plane->state);
+ spin_lock(&vop->reg_lock);
+ vop_cfg_done(vop);
+ spin_unlock(&vop->reg_lock);
+
+ /*
+ * A scanout can still be occurring, so we can't drop the
+ * reference to the old framebuffer. To solve this we get a
+ * reference to old_fb and set a worker to release it later.
+ * FIXME: if we perform 500 async_update calls before the
+ * vblank, then we can have 500 different framebuffers waiting
+ * to be released.
+ */
+ if (old_fb && plane->state->fb != old_fb) {
+ drm_framebuffer_get(old_fb);
+ WARN_ON(drm_crtc_vblank_get(plane->state->crtc) != 0);
+ drm_flip_work_queue(&vop->fb_unref_work, old_fb);
+ set_bit(VOP_PENDING_FB_UNREF, &vop->pending);
+ }
+ }
+}
+
+static const struct drm_plane_helper_funcs plane_helper_funcs = {
+ .atomic_check = vop_plane_atomic_check,
+ .atomic_update = vop_plane_atomic_update,
+ .atomic_disable = vop_plane_atomic_disable,
+ .atomic_async_check = vop_plane_atomic_async_check,
+ .atomic_async_update = vop_plane_atomic_async_update,
+ .prepare_fb = drm_gem_fb_prepare_fb,
+};
+
+static const struct drm_plane_funcs vop_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = vop_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 = rockchip_mod_supported,
+};
+
+static int vop_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct vop *vop = to_vop(crtc);
+ unsigned long flags;
+
+ if (WARN_ON(!vop->is_enabled))
+ return -EPERM;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ VOP_INTR_SET_TYPE(vop, clear, FS_INTR, 1);
+ VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 1);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+
+ return 0;
+}
+
+static void vop_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct vop *vop = to_vop(crtc);
+ unsigned long flags;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ spin_lock_irqsave(&vop->irq_lock, flags);
+
+ VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0);
+
+ spin_unlock_irqrestore(&vop->irq_lock, flags);
+}
+
+static bool vop_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct vop *vop = to_vop(crtc);
+ unsigned long rate;
+
+ /*
+ * Clock craziness.
+ *
+ * Key points:
+ *
+ * - DRM works in in kHz.
+ * - Clock framework works in Hz.
+ * - Rockchip's clock driver picks the clock rate that is the
+ * same _OR LOWER_ than the one requested.
+ *
+ * Action plan:
+ *
+ * 1. When DRM gives us a mode, we should add 999 Hz to it. That way
+ * if the clock we need is 60000001 Hz (~60 MHz) and DRM tells us to
+ * make 60000 kHz then the clock framework will actually give us
+ * the right clock.
+ *
+ * NOTE: if the PLL (maybe through a divider) could actually make
+ * a clock rate 999 Hz higher instead of the one we want then this
+ * could be a problem. Unfortunately there's not much we can do
+ * since it's baked into DRM to use kHz. It shouldn't matter in
+ * practice since Rockchip PLLs are controlled by tables and
+ * even if there is a divider in the middle I wouldn't expect PLL
+ * rates in the table that are just a few kHz different.
+ *
+ * 2. Get the clock framework to round the rate for us to tell us
+ * what it will actually make.
+ *
+ * 3. Store the rounded up rate so that we don't need to worry about
+ * this in the actual clk_set_rate().
+ */
+ rate = clk_round_rate(vop->dclk, adjusted_mode->clock * 1000 + 999);
+ adjusted_mode->clock = DIV_ROUND_UP(rate, 1000);
+
+ return true;
+}
+
+static bool vop_dsp_lut_is_enabled(struct vop *vop)
+{
+ return vop_read_reg(vop, 0, &vop->data->common->dsp_lut_en);
+}
+
+static void vop_crtc_write_gamma_lut(struct vop *vop, struct drm_crtc *crtc)
+{
+ struct drm_color_lut *lut = crtc->state->gamma_lut->data;
+ unsigned int i;
+
+ for (i = 0; i < crtc->gamma_size; i++) {
+ u32 word;
+
+ word = (drm_color_lut_extract(lut[i].red, 10) << 20) |
+ (drm_color_lut_extract(lut[i].green, 10) << 10) |
+ drm_color_lut_extract(lut[i].blue, 10);
+ writel(word, vop->lut_regs + i * 4);
+ }
+}
+
+static void vop_crtc_gamma_set(struct vop *vop, struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct drm_crtc_state *state = crtc->state;
+ unsigned int idle;
+ int ret;
+
+ if (!vop->lut_regs)
+ return;
+ /*
+ * To disable gamma (gamma_lut is null) or to write
+ * an update to the LUT, clear dsp_lut_en.
+ */
+ spin_lock(&vop->reg_lock);
+ VOP_REG_SET(vop, common, dsp_lut_en, 0);
+ vop_cfg_done(vop);
+ spin_unlock(&vop->reg_lock);
+
+ /*
+ * In order to write the LUT to the internal memory,
+ * we need to first make sure the dsp_lut_en bit is cleared.
+ */
+ ret = readx_poll_timeout(vop_dsp_lut_is_enabled, vop,
+ idle, !idle, 5, 30 * 1000);
+ if (ret) {
+ DRM_DEV_ERROR(vop->dev, "display LUT RAM enable timeout!\n");
+ return;
+ }
+
+ if (!state->gamma_lut)
+ return;
+
+ spin_lock(&vop->reg_lock);
+ vop_crtc_write_gamma_lut(vop, crtc);
+ VOP_REG_SET(vop, common, dsp_lut_en, 1);
+ vop_cfg_done(vop);
+ spin_unlock(&vop->reg_lock);
+}
+
+static void vop_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ struct vop *vop = to_vop(crtc);
+
+ /*
+ * Only update GAMMA if the 'active' flag is not changed,
+ * otherwise it's updated by .atomic_enable.
+ */
+ if (crtc->state->color_mgmt_changed &&
+ !crtc->state->active_changed)
+ vop_crtc_gamma_set(vop, crtc, old_crtc_state);
+}
+
+static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct vop *vop = to_vop(crtc);
+ const struct vop_data *vop_data = vop->data;
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state);
+ struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
+ u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
+ u16 hdisplay = adjusted_mode->hdisplay;
+ u16 htotal = adjusted_mode->htotal;
+ u16 hact_st = adjusted_mode->htotal - adjusted_mode->hsync_start;
+ u16 hact_end = hact_st + hdisplay;
+ u16 vdisplay = adjusted_mode->vdisplay;
+ u16 vtotal = adjusted_mode->vtotal;
+ u16 vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start;
+ u16 vact_st = adjusted_mode->vtotal - adjusted_mode->vsync_start;
+ u16 vact_end = vact_st + vdisplay;
+ uint32_t pin_pol, val;
+ int dither_bpc = s->output_bpc ? s->output_bpc : 10;
+ int ret;
+
+ if (old_state && old_state->self_refresh_active) {
+ drm_crtc_vblank_on(crtc);
+ rockchip_drm_set_win_enabled(crtc, true);
+ return;
+ }
+
+ /*
+ * If we have a GAMMA LUT in the state, then let's make sure
+ * it's updated. We might be coming out of suspend,
+ * which means the LUT internal memory needs to be re-written.
+ */
+ if (crtc->state->gamma_lut)
+ vop_crtc_gamma_set(vop, crtc, old_state);
+
+ mutex_lock(&vop->vop_lock);
+
+ WARN_ON(vop->event);
+
+ ret = vop_enable(crtc, old_state);
+ if (ret) {
+ mutex_unlock(&vop->vop_lock);
+ DRM_DEV_ERROR(vop->dev, "Failed to enable vop (%d)\n", ret);
+ return;
+ }
+ pin_pol = (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) ?
+ BIT(HSYNC_POSITIVE) : 0;
+ pin_pol |= (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) ?
+ BIT(VSYNC_POSITIVE) : 0;
+ VOP_REG_SET(vop, output, pin_pol, pin_pol);
+ VOP_REG_SET(vop, output, mipi_dual_channel_en, 0);
+
+ switch (s->output_type) {
+ case DRM_MODE_CONNECTOR_LVDS:
+ VOP_REG_SET(vop, output, rgb_dclk_pol, 1);
+ VOP_REG_SET(vop, output, rgb_pin_pol, pin_pol);
+ VOP_REG_SET(vop, output, rgb_en, 1);
+ break;
+ case DRM_MODE_CONNECTOR_eDP:
+ VOP_REG_SET(vop, output, edp_dclk_pol, 1);
+ VOP_REG_SET(vop, output, edp_pin_pol, pin_pol);
+ VOP_REG_SET(vop, output, edp_en, 1);
+ break;
+ case DRM_MODE_CONNECTOR_HDMIA:
+ VOP_REG_SET(vop, output, hdmi_dclk_pol, 1);
+ VOP_REG_SET(vop, output, hdmi_pin_pol, pin_pol);
+ VOP_REG_SET(vop, output, hdmi_en, 1);
+ break;
+ case DRM_MODE_CONNECTOR_DSI:
+ VOP_REG_SET(vop, output, mipi_dclk_pol, 1);
+ VOP_REG_SET(vop, output, mipi_pin_pol, pin_pol);
+ VOP_REG_SET(vop, output, mipi_en, 1);
+ VOP_REG_SET(vop, output, mipi_dual_channel_en,
+ !!(s->output_flags & ROCKCHIP_OUTPUT_DSI_DUAL));
+ break;
+ case DRM_MODE_CONNECTOR_DisplayPort:
+ VOP_REG_SET(vop, output, dp_dclk_pol, 0);
+ VOP_REG_SET(vop, output, dp_pin_pol, pin_pol);
+ VOP_REG_SET(vop, output, dp_en, 1);
+ break;
+ default:
+ DRM_DEV_ERROR(vop->dev, "unsupported connector_type [%d]\n",
+ s->output_type);
+ }
+
+ /*
+ * if vop is not support RGB10 output, need force RGB10 to RGB888.
+ */
+ if (s->output_mode == ROCKCHIP_OUT_MODE_AAAA &&
+ !(vop_data->feature & VOP_FEATURE_OUTPUT_RGB10))
+ s->output_mode = ROCKCHIP_OUT_MODE_P888;
+
+ if (s->output_mode == ROCKCHIP_OUT_MODE_AAAA && dither_bpc <= 8)
+ VOP_REG_SET(vop, common, pre_dither_down, 1);
+ else
+ VOP_REG_SET(vop, common, pre_dither_down, 0);
+
+ if (dither_bpc == 6) {
+ VOP_REG_SET(vop, common, dither_down_sel, DITHER_DOWN_ALLEGRO);
+ VOP_REG_SET(vop, common, dither_down_mode, RGB888_TO_RGB666);
+ VOP_REG_SET(vop, common, dither_down_en, 1);
+ } else {
+ VOP_REG_SET(vop, common, dither_down_en, 0);
+ }
+
+ VOP_REG_SET(vop, common, out_mode, s->output_mode);
+
+ VOP_REG_SET(vop, modeset, htotal_pw, (htotal << 16) | hsync_len);
+ val = hact_st << 16;
+ val |= hact_end;
+ VOP_REG_SET(vop, modeset, hact_st_end, val);
+ VOP_REG_SET(vop, modeset, hpost_st_end, val);
+
+ VOP_REG_SET(vop, modeset, vtotal_pw, (vtotal << 16) | vsync_len);
+ val = vact_st << 16;
+ val |= vact_end;
+ VOP_REG_SET(vop, modeset, vact_st_end, val);
+ VOP_REG_SET(vop, modeset, vpost_st_end, val);
+
+ VOP_REG_SET(vop, intr, line_flag_num[0], vact_end);
+
+ clk_set_rate(vop->dclk, adjusted_mode->clock * 1000);
+
+ VOP_REG_SET(vop, common, standby, 0);
+ mutex_unlock(&vop->vop_lock);
+}
+
+static bool vop_fs_irq_is_pending(struct vop *vop)
+{
+ return VOP_INTR_GET_TYPE(vop, status, FS_INTR);
+}
+
+static void vop_wait_for_irq_handler(struct vop *vop)
+{
+ bool pending;
+ int ret;
+
+ /*
+ * Spin until frame start interrupt status bit goes low, which means
+ * that interrupt handler was invoked and cleared it. The timeout of
+ * 10 msecs is really too long, but it is just a safety measure if
+ * something goes really wrong. The wait will only happen in the very
+ * unlikely case of a vblank happening exactly at the same time and
+ * shouldn't exceed microseconds range.
+ */
+ ret = readx_poll_timeout_atomic(vop_fs_irq_is_pending, vop, pending,
+ !pending, 0, 10 * 1000);
+ if (ret)
+ DRM_DEV_ERROR(vop->dev, "VOP vblank IRQ stuck for 10 ms\n");
+
+ synchronize_irq(vop->irq);
+}
+
+static int vop_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state)
+{
+ struct vop *vop = to_vop(crtc);
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ struct rockchip_crtc_state *s;
+ int afbc_planes = 0;
+
+ if (vop->lut_regs && crtc_state->color_mgmt_changed &&
+ crtc_state->gamma_lut) {
+ unsigned int len;
+
+ len = drm_color_lut_size(crtc_state->gamma_lut);
+ if (len != crtc->gamma_size) {
+ DRM_DEBUG_KMS("Invalid LUT size; got %d, expected %d\n",
+ len, crtc->gamma_size);
+ return -EINVAL;
+ }
+ }
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state) {
+ plane_state =
+ drm_atomic_get_plane_state(crtc_state->state, plane);
+ if (IS_ERR(plane_state)) {
+ DRM_DEBUG_KMS("Cannot get plane state for plane %s\n",
+ plane->name);
+ return PTR_ERR(plane_state);
+ }
+
+ if (drm_is_afbc(plane_state->fb->modifier))
+ ++afbc_planes;
+ }
+
+ if (afbc_planes > 1) {
+ DRM_DEBUG_KMS("Invalid number of AFBC planes; got %d, expected at most 1\n", afbc_planes);
+ return -EINVAL;
+ }
+
+ s = to_rockchip_crtc_state(crtc_state);
+ s->enable_afbc = afbc_planes > 0;
+
+ return 0;
+}
+
+static void vop_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ struct drm_atomic_state *old_state = old_crtc_state->state;
+ struct drm_plane_state *old_plane_state, *new_plane_state;
+ struct vop *vop = to_vop(crtc);
+ struct drm_plane *plane;
+ struct rockchip_crtc_state *s;
+ int i;
+
+ if (WARN_ON(!vop->is_enabled))
+ return;
+
+ spin_lock(&vop->reg_lock);
+
+ /* Enable AFBC if there is some AFBC window, disable otherwise. */
+ s = to_rockchip_crtc_state(crtc->state);
+ VOP_AFBC_SET(vop, enable, s->enable_afbc);
+ vop_cfg_done(vop);
+
+ spin_unlock(&vop->reg_lock);
+
+ /*
+ * There is a (rather unlikely) possiblity that a vblank interrupt
+ * fired before we set the cfg_done bit. To avoid spuriously
+ * signalling flip completion we need to wait for it to finish.
+ */
+ vop_wait_for_irq_handler(vop);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+ WARN_ON(vop->event);
+
+ vop->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ for_each_oldnew_plane_in_state(old_state, plane, old_plane_state,
+ new_plane_state, i) {
+ if (!old_plane_state->fb)
+ continue;
+
+ if (old_plane_state->fb == new_plane_state->fb)
+ continue;
+
+ drm_framebuffer_get(old_plane_state->fb);
+ WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+ drm_flip_work_queue(&vop->fb_unref_work, old_plane_state->fb);
+ set_bit(VOP_PENDING_FB_UNREF, &vop->pending);
+ }
+}
+
+static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = {
+ .mode_fixup = vop_crtc_mode_fixup,
+ .atomic_check = vop_crtc_atomic_check,
+ .atomic_begin = vop_crtc_atomic_begin,
+ .atomic_flush = vop_crtc_atomic_flush,
+ .atomic_enable = vop_crtc_atomic_enable,
+ .atomic_disable = vop_crtc_atomic_disable,
+};
+
+static void vop_crtc_destroy(struct drm_crtc *crtc)
+{
+ drm_crtc_cleanup(crtc);
+}
+
+static struct drm_crtc_state *vop_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct rockchip_crtc_state *rockchip_state;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ rockchip_state = kmemdup(to_rockchip_crtc_state(crtc->state),
+ sizeof(*rockchip_state), GFP_KERNEL);
+ if (!rockchip_state)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &rockchip_state->base);
+ return &rockchip_state->base;
+}
+
+static void vop_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(state);
+
+ __drm_atomic_helper_crtc_destroy_state(&s->base);
+ kfree(s);
+}
+
+static void vop_crtc_reset(struct drm_crtc *crtc)
+{
+ struct rockchip_crtc_state *crtc_state =
+ kzalloc(sizeof(*crtc_state), GFP_KERNEL);
+
+ if (crtc->state)
+ vop_crtc_destroy_state(crtc, crtc->state);
+
+ if (crtc_state)
+ __drm_atomic_helper_crtc_reset(crtc, &crtc_state->base);
+ else
+ __drm_atomic_helper_crtc_reset(crtc, NULL);
+}
+
+#ifdef CONFIG_DRM_ANALOGIX_DP
+static struct drm_connector *vop_get_edp_connector(struct vop *vop)
+{
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+
+ drm_connector_list_iter_begin(vop->drm_dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+ drm_connector_list_iter_end(&conn_iter);
+ return connector;
+ }
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ return NULL;
+}
+
+static int vop_crtc_set_crc_source(struct drm_crtc *crtc,
+ const char *source_name)
+{
+ struct vop *vop = to_vop(crtc);
+ struct drm_connector *connector;
+ int ret;
+
+ connector = vop_get_edp_connector(vop);
+ if (!connector)
+ return -EINVAL;
+
+ if (source_name && strcmp(source_name, "auto") == 0)
+ ret = analogix_dp_start_crc(connector);
+ else if (!source_name)
+ ret = analogix_dp_stop_crc(connector);
+ else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int
+vop_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt)
+{
+ if (source_name && strcmp(source_name, "auto") != 0)
+ return -EINVAL;
+
+ *values_cnt = 3;
+ return 0;
+}
+
+#else
+static int vop_crtc_set_crc_source(struct drm_crtc *crtc,
+ const char *source_name)
+{
+ return -ENODEV;
+}
+
+static int
+vop_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt)
+{
+ return -ENODEV;
+}
+#endif
+
+static const struct drm_crtc_funcs vop_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .destroy = vop_crtc_destroy,
+ .reset = vop_crtc_reset,
+ .atomic_duplicate_state = vop_crtc_duplicate_state,
+ .atomic_destroy_state = vop_crtc_destroy_state,
+ .enable_vblank = vop_crtc_enable_vblank,
+ .disable_vblank = vop_crtc_disable_vblank,
+ .set_crc_source = vop_crtc_set_crc_source,
+ .verify_crc_source = vop_crtc_verify_crc_source,
+ .gamma_set = drm_atomic_helper_legacy_gamma_set,
+};
+
+static void vop_fb_unref_worker(struct drm_flip_work *work, void *val)
+{
+ struct vop *vop = container_of(work, struct vop, fb_unref_work);
+ struct drm_framebuffer *fb = val;
+
+ drm_crtc_vblank_put(&vop->crtc);
+ drm_framebuffer_put(fb);
+}
+
+static void vop_handle_vblank(struct vop *vop)
+{
+ struct drm_device *drm = vop->drm_dev;
+ struct drm_crtc *crtc = &vop->crtc;
+
+ spin_lock(&drm->event_lock);
+ if (vop->event) {
+ drm_crtc_send_vblank_event(crtc, vop->event);
+ drm_crtc_vblank_put(crtc);
+ vop->event = NULL;
+ }
+ spin_unlock(&drm->event_lock);
+
+ if (test_and_clear_bit(VOP_PENDING_FB_UNREF, &vop->pending))
+ drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq);
+}
+
+static irqreturn_t vop_isr(int irq, void *data)
+{
+ struct vop *vop = data;
+ struct drm_crtc *crtc = &vop->crtc;
+ uint32_t active_irqs;
+ int ret = IRQ_NONE;
+
+ /*
+ * The irq is shared with the iommu. If the runtime-pm state of the
+ * vop-device is disabled the irq has to be targeted at the iommu.
+ */
+ if (!pm_runtime_get_if_in_use(vop->dev))
+ return IRQ_NONE;
+
+ if (vop_core_clks_enable(vop)) {
+ DRM_DEV_ERROR_RATELIMITED(vop->dev, "couldn't enable clocks\n");
+ goto out;
+ }
+
+ /*
+ * interrupt register has interrupt status, enable and clear bits, we
+ * must hold irq_lock to avoid a race with enable/disable_vblank().
+ */
+ spin_lock(&vop->irq_lock);
+
+ active_irqs = VOP_INTR_GET_TYPE(vop, status, INTR_MASK);
+ /* Clear all active interrupt sources */
+ if (active_irqs)
+ VOP_INTR_SET_TYPE(vop, clear, active_irqs, 1);
+
+ spin_unlock(&vop->irq_lock);
+
+ /* This is expected for vop iommu irqs, since the irq is shared */
+ if (!active_irqs)
+ goto out_disable;
+
+ if (active_irqs & DSP_HOLD_VALID_INTR) {
+ complete(&vop->dsp_hold_completion);
+ active_irqs &= ~DSP_HOLD_VALID_INTR;
+ ret = IRQ_HANDLED;
+ }
+
+ if (active_irqs & LINE_FLAG_INTR) {
+ complete(&vop->line_flag_completion);
+ active_irqs &= ~LINE_FLAG_INTR;
+ ret = IRQ_HANDLED;
+ }
+
+ if (active_irqs & FS_INTR) {
+ drm_crtc_handle_vblank(crtc);
+ vop_handle_vblank(vop);
+ active_irqs &= ~FS_INTR;
+ ret = IRQ_HANDLED;
+ }
+
+ /* Unhandled irqs are spurious. */
+ if (active_irqs)
+ DRM_DEV_ERROR(vop->dev, "Unknown VOP IRQs: %#02x\n",
+ active_irqs);
+
+out_disable:
+ vop_core_clks_disable(vop);
+out:
+ pm_runtime_put(vop->dev);
+ return ret;
+}
+
+static void vop_plane_add_properties(struct drm_plane *plane,
+ const struct vop_win_data *win_data)
+{
+ unsigned int flags = 0;
+
+ flags |= VOP_WIN_HAS_REG(win_data, x_mir_en) ? DRM_MODE_REFLECT_X : 0;
+ flags |= VOP_WIN_HAS_REG(win_data, y_mir_en) ? DRM_MODE_REFLECT_Y : 0;
+ if (flags)
+ drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0,
+ DRM_MODE_ROTATE_0 | flags);
+}
+
+static int vop_create_crtc(struct vop *vop)
+{
+ const struct vop_data *vop_data = vop->data;
+ struct device *dev = vop->dev;
+ struct drm_device *drm_dev = vop->drm_dev;
+ struct drm_plane *primary = NULL, *cursor = NULL, *plane, *tmp;
+ struct drm_crtc *crtc = &vop->crtc;
+ struct device_node *port;
+ int ret;
+ int i;
+
+ /*
+ * Create drm_plane for primary and cursor planes first, since we need
+ * to pass them to drm_crtc_init_with_planes, which sets the
+ * "possible_crtcs" to the newly initialized crtc.
+ */
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win_data = vop_win->data;
+
+ if (win_data->type != DRM_PLANE_TYPE_PRIMARY &&
+ win_data->type != DRM_PLANE_TYPE_CURSOR)
+ continue;
+
+ ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
+ 0, &vop_plane_funcs,
+ win_data->phy->data_formats,
+ win_data->phy->nformats,
+ win_data->phy->format_modifiers,
+ win_data->type, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(vop->dev, "failed to init plane %d\n",
+ ret);
+ goto err_cleanup_planes;
+ }
+
+ plane = &vop_win->base;
+ drm_plane_helper_add(plane, &plane_helper_funcs);
+ vop_plane_add_properties(plane, win_data);
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ primary = plane;
+ else if (plane->type == DRM_PLANE_TYPE_CURSOR)
+ cursor = plane;
+ }
+
+ ret = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
+ &vop_crtc_funcs, NULL);
+ if (ret)
+ goto err_cleanup_planes;
+
+ drm_crtc_helper_add(crtc, &vop_crtc_helper_funcs);
+ if (vop->lut_regs) {
+ drm_mode_crtc_set_gamma_size(crtc, vop_data->lut_size);
+ drm_crtc_enable_color_mgmt(crtc, 0, false, vop_data->lut_size);
+ }
+
+ /*
+ * Create drm_planes for overlay windows with possible_crtcs restricted
+ * to the newly created crtc.
+ */
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win_data = vop_win->data;
+ unsigned long possible_crtcs = drm_crtc_mask(crtc);
+
+ if (win_data->type != DRM_PLANE_TYPE_OVERLAY)
+ continue;
+
+ ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
+ possible_crtcs,
+ &vop_plane_funcs,
+ win_data->phy->data_formats,
+ win_data->phy->nformats,
+ win_data->phy->format_modifiers,
+ win_data->type, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(vop->dev, "failed to init overlay %d\n",
+ ret);
+ goto err_cleanup_crtc;
+ }
+ drm_plane_helper_add(&vop_win->base, &plane_helper_funcs);
+ vop_plane_add_properties(&vop_win->base, win_data);
+ }
+
+ port = of_get_child_by_name(dev->of_node, "port");
+ if (!port) {
+ DRM_DEV_ERROR(vop->dev, "no port node found in %pOF\n",
+ dev->of_node);
+ ret = -ENOENT;
+ goto err_cleanup_crtc;
+ }
+
+ drm_flip_work_init(&vop->fb_unref_work, "fb_unref",
+ vop_fb_unref_worker);
+
+ init_completion(&vop->dsp_hold_completion);
+ init_completion(&vop->line_flag_completion);
+ crtc->port = port;
+
+ ret = drm_self_refresh_helper_init(crtc);
+ if (ret)
+ DRM_DEV_DEBUG_KMS(vop->dev,
+ "Failed to init %s with SR helpers %d, ignoring\n",
+ crtc->name, ret);
+
+ return 0;
+
+err_cleanup_crtc:
+ drm_crtc_cleanup(crtc);
+err_cleanup_planes:
+ list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
+ head)
+ drm_plane_cleanup(plane);
+ return ret;
+}
+
+static void vop_destroy_crtc(struct vop *vop)
+{
+ struct drm_crtc *crtc = &vop->crtc;
+ struct drm_device *drm_dev = vop->drm_dev;
+ struct drm_plane *plane, *tmp;
+
+ drm_self_refresh_helper_cleanup(crtc);
+
+ of_node_put(crtc->port);
+
+ /*
+ * We need to cleanup the planes now. Why?
+ *
+ * The planes are "&vop->win[i].base". That means the memory is
+ * all part of the big "struct vop" chunk of memory. That memory
+ * was devm allocated and associated with this component. We need to
+ * free it ourselves before vop_unbind() finishes.
+ */
+ list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
+ head)
+ vop_plane_destroy(plane);
+
+ /*
+ * Destroy CRTC after vop_plane_destroy() since vop_disable_plane()
+ * references the CRTC.
+ */
+ drm_crtc_cleanup(crtc);
+ drm_flip_work_cleanup(&vop->fb_unref_work);
+}
+
+static int vop_initial(struct vop *vop)
+{
+ struct reset_control *ahb_rst;
+ int i, ret;
+
+ vop->hclk = devm_clk_get(vop->dev, "hclk_vop");
+ if (IS_ERR(vop->hclk)) {
+ DRM_DEV_ERROR(vop->dev, "failed to get hclk source\n");
+ return PTR_ERR(vop->hclk);
+ }
+ vop->aclk = devm_clk_get(vop->dev, "aclk_vop");
+ if (IS_ERR(vop->aclk)) {
+ DRM_DEV_ERROR(vop->dev, "failed to get aclk source\n");
+ return PTR_ERR(vop->aclk);
+ }
+ vop->dclk = devm_clk_get(vop->dev, "dclk_vop");
+ if (IS_ERR(vop->dclk)) {
+ DRM_DEV_ERROR(vop->dev, "failed to get dclk source\n");
+ return PTR_ERR(vop->dclk);
+ }
+
+ ret = pm_runtime_get_sync(vop->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(vop->dev, "failed to get pm runtime: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare(vop->dclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(vop->dev, "failed to prepare dclk\n");
+ goto err_put_pm_runtime;
+ }
+
+ /* Enable both the hclk and aclk to setup the vop */
+ ret = clk_prepare_enable(vop->hclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(vop->dev, "failed to prepare/enable hclk\n");
+ goto err_unprepare_dclk;
+ }
+
+ ret = clk_prepare_enable(vop->aclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(vop->dev, "failed to prepare/enable aclk\n");
+ goto err_disable_hclk;
+ }
+
+ /*
+ * do hclk_reset, reset all vop registers.
+ */
+ ahb_rst = devm_reset_control_get(vop->dev, "ahb");
+ if (IS_ERR(ahb_rst)) {
+ DRM_DEV_ERROR(vop->dev, "failed to get ahb reset\n");
+ ret = PTR_ERR(ahb_rst);
+ goto err_disable_aclk;
+ }
+ reset_control_assert(ahb_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(ahb_rst);
+
+ VOP_INTR_SET_TYPE(vop, clear, INTR_MASK, 1);
+ VOP_INTR_SET_TYPE(vop, enable, INTR_MASK, 0);
+
+ for (i = 0; i < vop->len; i += sizeof(u32))
+ vop->regsbak[i / 4] = readl_relaxed(vop->regs + i);
+
+ VOP_REG_SET(vop, misc, global_regdone_en, 1);
+ VOP_REG_SET(vop, common, dsp_blank, 0);
+
+ for (i = 0; i < vop->data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win = vop_win->data;
+ int channel = i * 2 + 1;
+
+ VOP_WIN_SET(vop, win, channel, (channel + 1) << 4 | channel);
+ vop_win_disable(vop, vop_win);
+ VOP_WIN_SET(vop, win, gate, 1);
+ }
+
+ vop_cfg_done(vop);
+
+ /*
+ * do dclk_reset, let all config take affect.
+ */
+ vop->dclk_rst = devm_reset_control_get(vop->dev, "dclk");
+ if (IS_ERR(vop->dclk_rst)) {
+ DRM_DEV_ERROR(vop->dev, "failed to get dclk reset\n");
+ ret = PTR_ERR(vop->dclk_rst);
+ goto err_disable_aclk;
+ }
+ reset_control_assert(vop->dclk_rst);
+ usleep_range(10, 20);
+ reset_control_deassert(vop->dclk_rst);
+
+ clk_disable(vop->hclk);
+ clk_disable(vop->aclk);
+
+ vop->is_enabled = false;
+
+ pm_runtime_put_sync(vop->dev);
+
+ return 0;
+
+err_disable_aclk:
+ clk_disable_unprepare(vop->aclk);
+err_disable_hclk:
+ clk_disable_unprepare(vop->hclk);
+err_unprepare_dclk:
+ clk_unprepare(vop->dclk);
+err_put_pm_runtime:
+ pm_runtime_put_sync(vop->dev);
+ return ret;
+}
+
+/*
+ * Initialize the vop->win array elements.
+ */
+static void vop_win_init(struct vop *vop)
+{
+ const struct vop_data *vop_data = vop->data;
+ unsigned int i;
+
+ for (i = 0; i < vop_data->win_size; i++) {
+ struct vop_win *vop_win = &vop->win[i];
+ const struct vop_win_data *win_data = &vop_data->win[i];
+
+ vop_win->data = win_data;
+ vop_win->vop = vop;
+
+ if (vop_data->win_yuv2yuv)
+ vop_win->yuv2yuv_data = &vop_data->win_yuv2yuv[i];
+ }
+}
+
+/**
+ * rockchip_drm_wait_vact_end
+ * @crtc: CRTC to enable line flag
+ * @mstimeout: millisecond for timeout
+ *
+ * Wait for vact_end line flag irq or timeout.
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout)
+{
+ struct vop *vop = to_vop(crtc);
+ unsigned long jiffies_left;
+ int ret = 0;
+
+ if (!crtc || !vop->is_enabled)
+ return -ENODEV;
+
+ mutex_lock(&vop->vop_lock);
+ if (mstimeout <= 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (vop_line_flag_irq_is_enabled(vop)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ reinit_completion(&vop->line_flag_completion);
+ vop_line_flag_irq_enable(vop);
+
+ jiffies_left = wait_for_completion_timeout(&vop->line_flag_completion,
+ msecs_to_jiffies(mstimeout));
+ vop_line_flag_irq_disable(vop);
+
+ if (jiffies_left == 0) {
+ DRM_DEV_ERROR(vop->dev, "Timeout waiting for IRQ\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+out:
+ mutex_unlock(&vop->vop_lock);
+ return ret;
+}
+EXPORT_SYMBOL(rockchip_drm_wait_vact_end);
+
+static int vop_bind(struct device *dev, struct device *master, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ const struct vop_data *vop_data;
+ struct drm_device *drm_dev = data;
+ struct vop *vop;
+ struct resource *res;
+ int ret, irq;
+
+ vop_data = of_device_get_match_data(dev);
+ if (!vop_data)
+ return -ENODEV;
+
+ /* Allocate vop struct and its vop_win array */
+ vop = devm_kzalloc(dev, struct_size(vop, win, vop_data->win_size),
+ GFP_KERNEL);
+ if (!vop)
+ return -ENOMEM;
+
+ vop->dev = dev;
+ vop->data = vop_data;
+ vop->drm_dev = drm_dev;
+ dev_set_drvdata(dev, vop);
+
+ vop_win_init(vop);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ vop->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop->regs))
+ return PTR_ERR(vop->regs);
+ vop->len = resource_size(res);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ if (!vop_data->lut_size) {
+ DRM_DEV_ERROR(dev, "no gamma LUT size defined\n");
+ return -EINVAL;
+ }
+ vop->lut_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop->lut_regs))
+ return PTR_ERR(vop->lut_regs);
+ }
+
+ vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL);
+ if (!vop->regsbak)
+ return -ENOMEM;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ DRM_DEV_ERROR(dev, "cannot find irq for vop\n");
+ return irq;
+ }
+ vop->irq = (unsigned int)irq;
+
+ spin_lock_init(&vop->reg_lock);
+ spin_lock_init(&vop->irq_lock);
+ mutex_init(&vop->vop_lock);
+
+ ret = vop_create_crtc(vop);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = vop_initial(vop);
+ if (ret < 0) {
+ DRM_DEV_ERROR(&pdev->dev,
+ "cannot initial vop dev - err %d\n", ret);
+ goto err_disable_pm_runtime;
+ }
+
+ ret = devm_request_irq(dev, vop->irq, vop_isr,
+ IRQF_SHARED, dev_name(dev), vop);
+ if (ret)
+ goto err_disable_pm_runtime;
+
+ if (vop->data->feature & VOP_FEATURE_INTERNAL_RGB) {
+ vop->rgb = rockchip_rgb_init(dev, &vop->crtc, vop->drm_dev);
+ if (IS_ERR(vop->rgb)) {
+ ret = PTR_ERR(vop->rgb);
+ goto err_disable_pm_runtime;
+ }
+ }
+
+ return 0;
+
+err_disable_pm_runtime:
+ pm_runtime_disable(&pdev->dev);
+ vop_destroy_crtc(vop);
+ return ret;
+}
+
+static void vop_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct vop *vop = dev_get_drvdata(dev);
+
+ if (vop->rgb)
+ rockchip_rgb_fini(vop->rgb);
+
+ pm_runtime_disable(dev);
+ vop_destroy_crtc(vop);
+
+ clk_unprepare(vop->aclk);
+ clk_unprepare(vop->hclk);
+ clk_unprepare(vop->dclk);
+}
+
+const struct component_ops vop_component_ops = {
+ .bind = vop_bind,
+ .unbind = vop_unbind,
+};
+EXPORT_SYMBOL_GPL(vop_component_ops);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
new file mode 100644
index 000000000..857d97cdc
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -0,0 +1,413 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_DRM_VOP_H
+#define _ROCKCHIP_DRM_VOP_H
+
+/*
+ * major: IP major version, used for IP structure
+ * minor: big feature change under same structure
+ */
+#define VOP_VERSION(major, minor) ((major) << 8 | (minor))
+#define VOP_MAJOR(version) ((version) >> 8)
+#define VOP_MINOR(version) ((version) & 0xff)
+
+#define NUM_YUV2YUV_COEFFICIENTS 12
+
+/* AFBC supports a number of configurable modes. Relevant to us is block size
+ * (16x16 or 32x8), storage modifiers (SPARSE, SPLIT), and the YUV-like
+ * colourspace transform (YTR). 16x16 SPARSE mode is always used. SPLIT mode
+ * could be enabled via the hreg_block_split register, but is not currently
+ * handled. The colourspace transform is implicitly always assumed by the
+ * decoder, so consumers must use this transform as well.
+ *
+ * Failure to match modifiers will cause errors displaying AFBC buffers
+ * produced by conformant AFBC producers, including Mesa.
+ */
+#define ROCKCHIP_AFBC_MOD \
+ DRM_FORMAT_MOD_ARM_AFBC( \
+ AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | AFBC_FORMAT_MOD_SPARSE \
+ | AFBC_FORMAT_MOD_YTR \
+ )
+
+enum vop_data_format {
+ VOP_FMT_ARGB8888 = 0,
+ VOP_FMT_RGB888,
+ VOP_FMT_RGB565,
+ VOP_FMT_YUV420SP = 4,
+ VOP_FMT_YUV422SP,
+ VOP_FMT_YUV444SP,
+};
+
+struct vop_reg {
+ uint32_t mask;
+ uint16_t offset;
+ uint8_t shift;
+ bool write_mask;
+ bool relaxed;
+};
+
+struct vop_afbc {
+ struct vop_reg enable;
+ struct vop_reg win_sel;
+ struct vop_reg format;
+ struct vop_reg hreg_block_split;
+ struct vop_reg pic_size;
+ struct vop_reg hdr_ptr;
+ struct vop_reg rstn;
+};
+
+struct vop_modeset {
+ struct vop_reg htotal_pw;
+ struct vop_reg hact_st_end;
+ struct vop_reg hpost_st_end;
+ struct vop_reg vtotal_pw;
+ struct vop_reg vact_st_end;
+ struct vop_reg vpost_st_end;
+};
+
+struct vop_output {
+ struct vop_reg pin_pol;
+ struct vop_reg dp_pin_pol;
+ struct vop_reg dp_dclk_pol;
+ struct vop_reg edp_pin_pol;
+ struct vop_reg edp_dclk_pol;
+ struct vop_reg hdmi_pin_pol;
+ struct vop_reg hdmi_dclk_pol;
+ struct vop_reg mipi_pin_pol;
+ struct vop_reg mipi_dclk_pol;
+ struct vop_reg rgb_pin_pol;
+ struct vop_reg rgb_dclk_pol;
+ struct vop_reg dp_en;
+ struct vop_reg edp_en;
+ struct vop_reg hdmi_en;
+ struct vop_reg mipi_en;
+ struct vop_reg mipi_dual_channel_en;
+ struct vop_reg rgb_en;
+};
+
+struct vop_common {
+ struct vop_reg cfg_done;
+ struct vop_reg dsp_blank;
+ struct vop_reg data_blank;
+ struct vop_reg pre_dither_down;
+ struct vop_reg dither_down_sel;
+ struct vop_reg dither_down_mode;
+ struct vop_reg dither_down_en;
+ struct vop_reg dither_up;
+ struct vop_reg dsp_lut_en;
+ struct vop_reg gate_en;
+ struct vop_reg mmu_en;
+ struct vop_reg out_mode;
+ struct vop_reg standby;
+};
+
+struct vop_misc {
+ struct vop_reg global_regdone_en;
+};
+
+struct vop_intr {
+ const int *intrs;
+ uint32_t nintrs;
+
+ struct vop_reg line_flag_num[2];
+ struct vop_reg enable;
+ struct vop_reg clear;
+ struct vop_reg status;
+};
+
+struct vop_scl_extension {
+ struct vop_reg cbcr_vsd_mode;
+ struct vop_reg cbcr_vsu_mode;
+ struct vop_reg cbcr_hsd_mode;
+ struct vop_reg cbcr_ver_scl_mode;
+ struct vop_reg cbcr_hor_scl_mode;
+ struct vop_reg yrgb_vsd_mode;
+ struct vop_reg yrgb_vsu_mode;
+ struct vop_reg yrgb_hsd_mode;
+ struct vop_reg yrgb_ver_scl_mode;
+ struct vop_reg yrgb_hor_scl_mode;
+ struct vop_reg line_load_mode;
+ struct vop_reg cbcr_axi_gather_num;
+ struct vop_reg yrgb_axi_gather_num;
+ struct vop_reg vsd_cbcr_gt2;
+ struct vop_reg vsd_cbcr_gt4;
+ struct vop_reg vsd_yrgb_gt2;
+ struct vop_reg vsd_yrgb_gt4;
+ struct vop_reg bic_coe_sel;
+ struct vop_reg cbcr_axi_gather_en;
+ struct vop_reg yrgb_axi_gather_en;
+ struct vop_reg lb_mode;
+};
+
+struct vop_scl_regs {
+ const struct vop_scl_extension *ext;
+
+ struct vop_reg scale_yrgb_x;
+ struct vop_reg scale_yrgb_y;
+ struct vop_reg scale_cbcr_x;
+ struct vop_reg scale_cbcr_y;
+};
+
+struct vop_yuv2yuv_phy {
+ struct vop_reg y2r_coefficients[NUM_YUV2YUV_COEFFICIENTS];
+};
+
+struct vop_win_phy {
+ const struct vop_scl_regs *scl;
+ const uint32_t *data_formats;
+ uint32_t nformats;
+ const uint64_t *format_modifiers;
+
+ struct vop_reg enable;
+ struct vop_reg gate;
+ struct vop_reg format;
+ struct vop_reg rb_swap;
+ struct vop_reg act_info;
+ struct vop_reg dsp_info;
+ struct vop_reg dsp_st;
+ struct vop_reg yrgb_mst;
+ struct vop_reg uv_mst;
+ struct vop_reg yrgb_vir;
+ struct vop_reg uv_vir;
+ struct vop_reg y_mir_en;
+ struct vop_reg x_mir_en;
+
+ struct vop_reg dst_alpha_ctl;
+ struct vop_reg src_alpha_ctl;
+ struct vop_reg alpha_pre_mul;
+ struct vop_reg alpha_mode;
+ struct vop_reg alpha_en;
+ struct vop_reg channel;
+};
+
+struct vop_win_yuv2yuv_data {
+ uint32_t base;
+ const struct vop_yuv2yuv_phy *phy;
+ struct vop_reg y2r_en;
+};
+
+struct vop_win_data {
+ uint32_t base;
+ const struct vop_win_phy *phy;
+ enum drm_plane_type type;
+};
+
+struct vop_data {
+ uint32_t version;
+ const struct vop_intr *intr;
+ const struct vop_common *common;
+ const struct vop_misc *misc;
+ const struct vop_modeset *modeset;
+ const struct vop_output *output;
+ const struct vop_afbc *afbc;
+ const struct vop_win_yuv2yuv_data *win_yuv2yuv;
+ const struct vop_win_data *win;
+ unsigned int win_size;
+ unsigned int lut_size;
+
+#define VOP_FEATURE_OUTPUT_RGB10 BIT(0)
+#define VOP_FEATURE_INTERNAL_RGB BIT(1)
+ u64 feature;
+};
+
+/* interrupt define */
+#define DSP_HOLD_VALID_INTR (1 << 0)
+#define FS_INTR (1 << 1)
+#define LINE_FLAG_INTR (1 << 2)
+#define BUS_ERROR_INTR (1 << 3)
+
+#define INTR_MASK (DSP_HOLD_VALID_INTR | FS_INTR | \
+ LINE_FLAG_INTR | BUS_ERROR_INTR)
+
+#define DSP_HOLD_VALID_INTR_EN(x) ((x) << 4)
+#define FS_INTR_EN(x) ((x) << 5)
+#define LINE_FLAG_INTR_EN(x) ((x) << 6)
+#define BUS_ERROR_INTR_EN(x) ((x) << 7)
+#define DSP_HOLD_VALID_INTR_MASK (1 << 4)
+#define FS_INTR_MASK (1 << 5)
+#define LINE_FLAG_INTR_MASK (1 << 6)
+#define BUS_ERROR_INTR_MASK (1 << 7)
+
+#define INTR_CLR_SHIFT 8
+#define DSP_HOLD_VALID_INTR_CLR (1 << (INTR_CLR_SHIFT + 0))
+#define FS_INTR_CLR (1 << (INTR_CLR_SHIFT + 1))
+#define LINE_FLAG_INTR_CLR (1 << (INTR_CLR_SHIFT + 2))
+#define BUS_ERROR_INTR_CLR (1 << (INTR_CLR_SHIFT + 3))
+
+#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
+#define DSP_LINE_NUM_MASK (0x1fff << 12)
+
+/* src alpha ctrl define */
+#define SRC_FADING_VALUE(x) (((x) & 0xff) << 24)
+#define SRC_GLOBAL_ALPHA(x) (((x) & 0xff) << 16)
+#define SRC_FACTOR_M0(x) (((x) & 0x7) << 6)
+#define SRC_ALPHA_CAL_M0(x) (((x) & 0x1) << 5)
+#define SRC_BLEND_M0(x) (((x) & 0x3) << 3)
+#define SRC_ALPHA_M0(x) (((x) & 0x1) << 2)
+#define SRC_COLOR_M0(x) (((x) & 0x1) << 1)
+#define SRC_ALPHA_EN(x) (((x) & 0x1) << 0)
+/* dst alpha ctrl define */
+#define DST_FACTOR_M0(x) (((x) & 0x7) << 6)
+
+/*
+ * display output interface supported by rockchip lcdc
+ */
+#define ROCKCHIP_OUT_MODE_P888 0
+#define ROCKCHIP_OUT_MODE_P666 1
+#define ROCKCHIP_OUT_MODE_P565 2
+/* for use special outface */
+#define ROCKCHIP_OUT_MODE_AAAA 15
+
+/* output flags */
+#define ROCKCHIP_OUTPUT_DSI_DUAL BIT(0)
+
+enum alpha_mode {
+ ALPHA_STRAIGHT,
+ ALPHA_INVERSE,
+};
+
+enum global_blend_mode {
+ ALPHA_GLOBAL,
+ ALPHA_PER_PIX,
+ ALPHA_PER_PIX_GLOBAL,
+};
+
+enum alpha_cal_mode {
+ ALPHA_SATURATION,
+ ALPHA_NO_SATURATION,
+};
+
+enum color_mode {
+ ALPHA_SRC_PRE_MUL,
+ ALPHA_SRC_NO_PRE_MUL,
+};
+
+enum factor_mode {
+ ALPHA_ZERO,
+ ALPHA_ONE,
+ ALPHA_SRC,
+ ALPHA_SRC_INVERSE,
+ ALPHA_SRC_GLOBAL,
+};
+
+enum scale_mode {
+ SCALE_NONE = 0x0,
+ SCALE_UP = 0x1,
+ SCALE_DOWN = 0x2
+};
+
+enum lb_mode {
+ LB_YUV_3840X5 = 0x0,
+ LB_YUV_2560X8 = 0x1,
+ LB_RGB_3840X2 = 0x2,
+ LB_RGB_2560X4 = 0x3,
+ LB_RGB_1920X5 = 0x4,
+ LB_RGB_1280X8 = 0x5
+};
+
+enum sacle_up_mode {
+ SCALE_UP_BIL = 0x0,
+ SCALE_UP_BIC = 0x1
+};
+
+enum scale_down_mode {
+ SCALE_DOWN_BIL = 0x0,
+ SCALE_DOWN_AVG = 0x1
+};
+
+enum dither_down_mode {
+ RGB888_TO_RGB565 = 0x0,
+ RGB888_TO_RGB666 = 0x1
+};
+
+enum dither_down_mode_sel {
+ DITHER_DOWN_ALLEGRO = 0x0,
+ DITHER_DOWN_FRC = 0x1
+};
+
+enum vop_pol {
+ HSYNC_POSITIVE = 0,
+ VSYNC_POSITIVE = 1,
+ DEN_NEGATIVE = 2
+};
+
+#define FRAC_16_16(mult, div) (((mult) << 16) / (div))
+#define SCL_FT_DEFAULT_FIXPOINT_SHIFT 12
+#define SCL_MAX_VSKIPLINES 4
+#define MIN_SCL_FT_AFTER_VSKIP 1
+
+static inline uint16_t scl_cal_scale(int src, int dst, int shift)
+{
+ return ((src * 2 - 3) << (shift - 1)) / (dst - 1);
+}
+
+static inline uint16_t scl_cal_scale2(int src, int dst)
+{
+ return ((src - 1) << 12) / (dst - 1);
+}
+
+#define GET_SCL_FT_BILI_DN(src, dst) scl_cal_scale(src, dst, 12)
+#define GET_SCL_FT_BILI_UP(src, dst) scl_cal_scale(src, dst, 16)
+#define GET_SCL_FT_BIC(src, dst) scl_cal_scale(src, dst, 16)
+
+static inline uint16_t scl_get_bili_dn_vskip(int src_h, int dst_h,
+ int vskiplines)
+{
+ int act_height;
+
+ act_height = DIV_ROUND_UP(src_h, vskiplines);
+
+ if (act_height == dst_h)
+ return GET_SCL_FT_BILI_DN(src_h, dst_h) / vskiplines;
+
+ return GET_SCL_FT_BILI_DN(act_height, dst_h);
+}
+
+static inline enum scale_mode scl_get_scl_mode(int src, int dst)
+{
+ if (src < dst)
+ return SCALE_UP;
+ else if (src > dst)
+ return SCALE_DOWN;
+
+ return SCALE_NONE;
+}
+
+static inline int scl_get_vskiplines(uint32_t srch, uint32_t dsth)
+{
+ uint32_t vskiplines;
+
+ for (vskiplines = SCL_MAX_VSKIPLINES; vskiplines > 1; vskiplines /= 2)
+ if (srch >= vskiplines * dsth * MIN_SCL_FT_AFTER_VSKIP)
+ break;
+
+ return vskiplines;
+}
+
+static inline int scl_vop_cal_lb_mode(int width, bool is_yuv)
+{
+ int lb_mode;
+
+ if (is_yuv) {
+ if (width > 1280)
+ lb_mode = LB_YUV_3840X5;
+ else
+ lb_mode = LB_YUV_2560X8;
+ } else {
+ if (width > 2560)
+ lb_mode = LB_RGB_3840X2;
+ else if (width > 1920)
+ lb_mode = LB_RGB_2560X4;
+ else
+ lb_mode = LB_RGB_1920X5;
+ }
+
+ return lb_mode;
+}
+
+extern const struct component_ops vop_component_ops;
+#endif /* _ROCKCHIP_DRM_VOP_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
new file mode 100644
index 000000000..e2487937c
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -0,0 +1,747 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:
+ * Mark Yao <mark.yao@rock-chips.com>
+ * Sandy Huang <hjc@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <linux/pinctrl/devinfo.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+#include "rockchip_lvds.h"
+
+#define DISPLAY_OUTPUT_RGB 0
+#define DISPLAY_OUTPUT_LVDS 1
+#define DISPLAY_OUTPUT_DUAL_LVDS 2
+
+struct rockchip_lvds;
+
+#define connector_to_lvds(c) \
+ container_of(c, struct rockchip_lvds, connector)
+
+#define encoder_to_lvds(c) \
+ container_of(c, struct rockchip_lvds, encoder)
+
+/**
+ * rockchip_lvds_soc_data - rockchip lvds Soc private data
+ * @probe: LVDS platform probe function
+ * @helper_funcs: LVDS connector helper functions
+ */
+struct rockchip_lvds_soc_data {
+ int (*probe)(struct platform_device *pdev, struct rockchip_lvds *lvds);
+ const struct drm_encoder_helper_funcs *helper_funcs;
+};
+
+struct rockchip_lvds {
+ struct device *dev;
+ void __iomem *regs;
+ struct regmap *grf;
+ struct clk *pclk;
+ struct phy *dphy;
+ const struct rockchip_lvds_soc_data *soc_data;
+ int output; /* rgb lvds or dual lvds output */
+ int format; /* vesa or jeida format */
+ struct drm_device *drm_dev;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct dev_pin_info *pins;
+};
+
+static inline void rk3288_writel(struct rockchip_lvds *lvds, u32 offset,
+ u32 val)
+{
+ writel_relaxed(val, lvds->regs + offset);
+ if (lvds->output == DISPLAY_OUTPUT_LVDS)
+ return;
+ writel_relaxed(val, lvds->regs + offset + RK3288_LVDS_CH1_OFFSET);
+}
+
+static inline int rockchip_lvds_name_to_format(const char *s)
+{
+ if (strncmp(s, "jeida-18", 8) == 0)
+ return LVDS_JEIDA_18;
+ else if (strncmp(s, "jeida-24", 8) == 0)
+ return LVDS_JEIDA_24;
+ else if (strncmp(s, "vesa-24", 7) == 0)
+ return LVDS_VESA_24;
+
+ return -EINVAL;
+}
+
+static inline int rockchip_lvds_name_to_output(const char *s)
+{
+ if (strncmp(s, "rgb", 3) == 0)
+ return DISPLAY_OUTPUT_RGB;
+ else if (strncmp(s, "lvds", 4) == 0)
+ return DISPLAY_OUTPUT_LVDS;
+ else if (strncmp(s, "duallvds", 8) == 0)
+ return DISPLAY_OUTPUT_DUAL_LVDS;
+
+ return -EINVAL;
+}
+
+static const struct drm_connector_funcs rockchip_lvds_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int rockchip_lvds_connector_get_modes(struct drm_connector *connector)
+{
+ struct rockchip_lvds *lvds = connector_to_lvds(connector);
+ struct drm_panel *panel = lvds->panel;
+
+ return drm_panel_get_modes(panel, connector);
+}
+
+static const
+struct drm_connector_helper_funcs rockchip_lvds_connector_helper_funcs = {
+ .get_modes = rockchip_lvds_connector_get_modes,
+};
+
+static int
+rockchip_lvds_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+
+ s->output_mode = ROCKCHIP_OUT_MODE_P888;
+ s->output_type = DRM_MODE_CONNECTOR_LVDS;
+
+ return 0;
+}
+
+static int rk3288_lvds_poweron(struct rockchip_lvds *lvds)
+{
+ int ret;
+ u32 val;
+
+ ret = clk_enable(lvds->pclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(lvds->dev, "failed to enable lvds pclk %d\n", ret);
+ return ret;
+ }
+ ret = pm_runtime_resume_and_get(lvds->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: %d\n", ret);
+ clk_disable(lvds->pclk);
+ return ret;
+ }
+ val = RK3288_LVDS_CH0_REG0_LANE4_EN | RK3288_LVDS_CH0_REG0_LANE3_EN |
+ RK3288_LVDS_CH0_REG0_LANE2_EN | RK3288_LVDS_CH0_REG0_LANE1_EN |
+ RK3288_LVDS_CH0_REG0_LANE0_EN;
+ if (lvds->output == DISPLAY_OUTPUT_RGB) {
+ val |= RK3288_LVDS_CH0_REG0_TTL_EN |
+ RK3288_LVDS_CH0_REG0_LANECK_EN;
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG0, val);
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG2,
+ RK3288_LVDS_PLL_FBDIV_REG2(0x46));
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG4,
+ RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE |
+ RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE |
+ RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE |
+ RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE |
+ RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE |
+ RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE);
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG5,
+ RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA |
+ RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA |
+ RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA |
+ RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA |
+ RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA |
+ RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA);
+ } else {
+ val |= RK3288_LVDS_CH0_REG0_LVDS_EN |
+ RK3288_LVDS_CH0_REG0_LANECK_EN;
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG0, val);
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG1,
+ RK3288_LVDS_CH0_REG1_LANECK_BIAS |
+ RK3288_LVDS_CH0_REG1_LANE4_BIAS |
+ RK3288_LVDS_CH0_REG1_LANE3_BIAS |
+ RK3288_LVDS_CH0_REG1_LANE2_BIAS |
+ RK3288_LVDS_CH0_REG1_LANE1_BIAS |
+ RK3288_LVDS_CH0_REG1_LANE0_BIAS);
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG2,
+ RK3288_LVDS_CH0_REG2_RESERVE_ON |
+ RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE |
+ RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE |
+ RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE |
+ RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE |
+ RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE |
+ RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE |
+ RK3288_LVDS_PLL_FBDIV_REG2(0x46));
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG4, 0x00);
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG5, 0x00);
+ }
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG3,
+ RK3288_LVDS_PLL_FBDIV_REG3(0x46));
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REGD,
+ RK3288_LVDS_PLL_PREDIV_REGD(0x0a));
+ rk3288_writel(lvds, RK3288_LVDS_CH0_REG20,
+ RK3288_LVDS_CH0_REG20_LSB);
+
+ rk3288_writel(lvds, RK3288_LVDS_CFG_REGC,
+ RK3288_LVDS_CFG_REGC_PLL_ENABLE);
+ rk3288_writel(lvds, RK3288_LVDS_CFG_REG21,
+ RK3288_LVDS_CFG_REG21_TX_ENABLE);
+
+ return 0;
+}
+
+static void rk3288_lvds_poweroff(struct rockchip_lvds *lvds)
+{
+ int ret;
+ u32 val;
+
+ rk3288_writel(lvds, RK3288_LVDS_CFG_REG21,
+ RK3288_LVDS_CFG_REG21_TX_ENABLE);
+ rk3288_writel(lvds, RK3288_LVDS_CFG_REGC,
+ RK3288_LVDS_CFG_REGC_PLL_ENABLE);
+ val = LVDS_DUAL | LVDS_TTL_EN | LVDS_CH0_EN | LVDS_CH1_EN | LVDS_PWRDN;
+ val |= val << 16;
+ ret = regmap_write(lvds->grf, RK3288_LVDS_GRF_SOC_CON7, val);
+ if (ret != 0)
+ DRM_DEV_ERROR(lvds->dev, "Could not write to GRF: %d\n", ret);
+
+ pm_runtime_put(lvds->dev);
+ clk_disable(lvds->pclk);
+}
+
+static int rk3288_lvds_grf_config(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+ u8 pin_hsync = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 1 : 0;
+ u8 pin_dclk = (mode->flags & DRM_MODE_FLAG_PCSYNC) ? 1 : 0;
+ u32 val;
+ int ret;
+
+ /* iomux to LCD data/sync mode */
+ if (lvds->output == DISPLAY_OUTPUT_RGB)
+ if (lvds->pins && !IS_ERR(lvds->pins->default_state))
+ pinctrl_select_state(lvds->pins->p,
+ lvds->pins->default_state);
+ val = lvds->format | LVDS_CH0_EN;
+ if (lvds->output == DISPLAY_OUTPUT_RGB)
+ val |= LVDS_TTL_EN | LVDS_CH1_EN;
+ else if (lvds->output == DISPLAY_OUTPUT_DUAL_LVDS)
+ val |= LVDS_DUAL | LVDS_CH1_EN;
+
+ if ((mode->htotal - mode->hsync_start) & 0x01)
+ val |= LVDS_START_PHASE_RST_1;
+
+ val |= (pin_dclk << 8) | (pin_hsync << 9);
+ val |= (0xffff << 16);
+ ret = regmap_write(lvds->grf, RK3288_LVDS_GRF_SOC_CON7, val);
+ if (ret)
+ DRM_DEV_ERROR(lvds->dev, "Could not write to GRF: %d\n", ret);
+
+ return ret;
+}
+
+static int rk3288_lvds_set_vop_source(struct rockchip_lvds *lvds,
+ struct drm_encoder *encoder)
+{
+ u32 val;
+ int ret;
+
+ ret = drm_of_encoder_active_endpoint_id(lvds->dev->of_node, encoder);
+ if (ret < 0)
+ return ret;
+
+ val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16;
+ if (ret)
+ val |= RK3288_LVDS_SOC_CON6_SEL_VOP_LIT;
+
+ ret = regmap_write(lvds->grf, RK3288_LVDS_GRF_SOC_CON6, val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void rk3288_lvds_encoder_enable(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+ struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
+ int ret;
+
+ drm_panel_prepare(lvds->panel);
+
+ ret = rk3288_lvds_poweron(lvds);
+ if (ret < 0) {
+ DRM_DEV_ERROR(lvds->dev, "failed to power on LVDS: %d\n", ret);
+ drm_panel_unprepare(lvds->panel);
+ return;
+ }
+
+ ret = rk3288_lvds_grf_config(encoder, mode);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to configure LVDS: %d\n", ret);
+ drm_panel_unprepare(lvds->panel);
+ return;
+ }
+
+ ret = rk3288_lvds_set_vop_source(lvds, encoder);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to set VOP source: %d\n", ret);
+ drm_panel_unprepare(lvds->panel);
+ return;
+ }
+
+ drm_panel_enable(lvds->panel);
+}
+
+static void rk3288_lvds_encoder_disable(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+
+ drm_panel_disable(lvds->panel);
+ rk3288_lvds_poweroff(lvds);
+ drm_panel_unprepare(lvds->panel);
+}
+
+static int px30_lvds_poweron(struct rockchip_lvds *lvds)
+{
+ int ret;
+
+ ret = pm_runtime_resume_and_get(lvds->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: %d\n", ret);
+ return ret;
+ }
+
+ /* Enable LVDS mode */
+ ret = regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
+ PX30_LVDS_MODE_EN(1) | PX30_LVDS_P2S_EN(1),
+ PX30_LVDS_MODE_EN(1) | PX30_LVDS_P2S_EN(1));
+ if (ret)
+ pm_runtime_put(lvds->dev);
+
+ return ret;
+}
+
+static void px30_lvds_poweroff(struct rockchip_lvds *lvds)
+{
+ regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
+ PX30_LVDS_MODE_EN(1) | PX30_LVDS_P2S_EN(1),
+ PX30_LVDS_MODE_EN(0) | PX30_LVDS_P2S_EN(0));
+
+ pm_runtime_put(lvds->dev);
+}
+
+static int px30_lvds_grf_config(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+
+ if (lvds->output != DISPLAY_OUTPUT_LVDS) {
+ DRM_DEV_ERROR(lvds->dev, "Unsupported display output %d\n",
+ lvds->output);
+ return -EINVAL;
+ }
+
+ /* Set format */
+ return regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
+ PX30_LVDS_FORMAT(lvds->format),
+ PX30_LVDS_FORMAT(lvds->format));
+}
+
+static int px30_lvds_set_vop_source(struct rockchip_lvds *lvds,
+ struct drm_encoder *encoder)
+{
+ int vop;
+
+ vop = drm_of_encoder_active_endpoint_id(lvds->dev->of_node, encoder);
+ if (vop < 0)
+ return vop;
+
+ return regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
+ PX30_LVDS_VOP_SEL(1),
+ PX30_LVDS_VOP_SEL(vop));
+}
+
+static void px30_lvds_encoder_enable(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+ struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
+ int ret;
+
+ drm_panel_prepare(lvds->panel);
+
+ ret = px30_lvds_poweron(lvds);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to power on LVDS: %d\n", ret);
+ drm_panel_unprepare(lvds->panel);
+ return;
+ }
+
+ ret = px30_lvds_grf_config(encoder, mode);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to configure LVDS: %d\n", ret);
+ drm_panel_unprepare(lvds->panel);
+ return;
+ }
+
+ ret = px30_lvds_set_vop_source(lvds, encoder);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to set VOP source: %d\n", ret);
+ drm_panel_unprepare(lvds->panel);
+ return;
+ }
+
+ drm_panel_enable(lvds->panel);
+}
+
+static void px30_lvds_encoder_disable(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+
+ drm_panel_disable(lvds->panel);
+ px30_lvds_poweroff(lvds);
+ drm_panel_unprepare(lvds->panel);
+}
+
+static const
+struct drm_encoder_helper_funcs rk3288_lvds_encoder_helper_funcs = {
+ .enable = rk3288_lvds_encoder_enable,
+ .disable = rk3288_lvds_encoder_disable,
+ .atomic_check = rockchip_lvds_encoder_atomic_check,
+};
+
+static const
+struct drm_encoder_helper_funcs px30_lvds_encoder_helper_funcs = {
+ .enable = px30_lvds_encoder_enable,
+ .disable = px30_lvds_encoder_disable,
+ .atomic_check = rockchip_lvds_encoder_atomic_check,
+};
+
+static int rk3288_lvds_probe(struct platform_device *pdev,
+ struct rockchip_lvds *lvds)
+{
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ lvds->regs = devm_ioremap_resource(lvds->dev, res);
+ if (IS_ERR(lvds->regs))
+ return PTR_ERR(lvds->regs);
+
+ lvds->pclk = devm_clk_get(lvds->dev, "pclk_lvds");
+ if (IS_ERR(lvds->pclk)) {
+ DRM_DEV_ERROR(lvds->dev, "could not get pclk_lvds\n");
+ return PTR_ERR(lvds->pclk);
+ }
+
+ lvds->pins = devm_kzalloc(lvds->dev, sizeof(*lvds->pins),
+ GFP_KERNEL);
+ if (!lvds->pins)
+ return -ENOMEM;
+
+ lvds->pins->p = devm_pinctrl_get(lvds->dev);
+ if (IS_ERR(lvds->pins->p)) {
+ DRM_DEV_ERROR(lvds->dev, "no pinctrl handle\n");
+ devm_kfree(lvds->dev, lvds->pins);
+ lvds->pins = NULL;
+ } else {
+ lvds->pins->default_state =
+ pinctrl_lookup_state(lvds->pins->p, "lcdc");
+ if (IS_ERR(lvds->pins->default_state)) {
+ DRM_DEV_ERROR(lvds->dev, "no default pinctrl state\n");
+ devm_kfree(lvds->dev, lvds->pins);
+ lvds->pins = NULL;
+ }
+ }
+
+ ret = clk_prepare(lvds->pclk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(lvds->dev, "failed to prepare pclk_lvds\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int px30_lvds_probe(struct platform_device *pdev,
+ struct rockchip_lvds *lvds)
+{
+ int ret;
+
+ /* MSB */
+ ret = regmap_update_bits(lvds->grf, PX30_LVDS_GRF_PD_VO_CON1,
+ PX30_LVDS_MSBSEL(1),
+ PX30_LVDS_MSBSEL(1));
+ if (ret)
+ return ret;
+
+ /* PHY */
+ lvds->dphy = devm_phy_get(&pdev->dev, "dphy");
+ if (IS_ERR(lvds->dphy))
+ return PTR_ERR(lvds->dphy);
+
+ ret = phy_init(lvds->dphy);
+ if (ret)
+ return ret;
+
+ ret = phy_set_mode(lvds->dphy, PHY_MODE_LVDS);
+ if (ret)
+ return ret;
+
+ return phy_power_on(lvds->dphy);
+}
+
+static const struct rockchip_lvds_soc_data rk3288_lvds_data = {
+ .probe = rk3288_lvds_probe,
+ .helper_funcs = &rk3288_lvds_encoder_helper_funcs,
+};
+
+static const struct rockchip_lvds_soc_data px30_lvds_data = {
+ .probe = px30_lvds_probe,
+ .helper_funcs = &px30_lvds_encoder_helper_funcs,
+};
+
+static const struct of_device_id rockchip_lvds_dt_ids[] = {
+ {
+ .compatible = "rockchip,rk3288-lvds",
+ .data = &rk3288_lvds_data
+ },
+ {
+ .compatible = "rockchip,px30-lvds",
+ .data = &px30_lvds_data
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rockchip_lvds_dt_ids);
+
+static int rockchip_lvds_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rockchip_lvds *lvds = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+ struct device_node *remote = NULL;
+ struct device_node *port, *endpoint;
+ int ret = 0, child_count = 0;
+ const char *name;
+ u32 endpoint_id = 0;
+
+ lvds->drm_dev = drm_dev;
+ port = of_graph_get_port_by_id(dev->of_node, 1);
+ if (!port) {
+ DRM_DEV_ERROR(dev,
+ "can't found port point, please init lvds panel port!\n");
+ return -EINVAL;
+ }
+ for_each_child_of_node(port, endpoint) {
+ child_count++;
+ of_property_read_u32(endpoint, "reg", &endpoint_id);
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, endpoint_id,
+ &lvds->panel, &lvds->bridge);
+ if (!ret) {
+ of_node_put(endpoint);
+ break;
+ }
+ }
+ if (!child_count) {
+ DRM_DEV_ERROR(dev, "lvds port does not have any children\n");
+ ret = -EINVAL;
+ goto err_put_port;
+ } else if (ret) {
+ DRM_DEV_ERROR(dev, "failed to find panel and bridge node\n");
+ ret = -EPROBE_DEFER;
+ goto err_put_port;
+ }
+ if (lvds->panel)
+ remote = lvds->panel->dev->of_node;
+ else
+ remote = lvds->bridge->of_node;
+ if (of_property_read_string(dev->of_node, "rockchip,output", &name))
+ /* default set it as output rgb */
+ lvds->output = DISPLAY_OUTPUT_RGB;
+ else
+ lvds->output = rockchip_lvds_name_to_output(name);
+
+ if (lvds->output < 0) {
+ DRM_DEV_ERROR(dev, "invalid output type [%s]\n", name);
+ ret = lvds->output;
+ goto err_put_remote;
+ }
+
+ if (of_property_read_string(remote, "data-mapping", &name))
+ /* default set it as format vesa 18 */
+ lvds->format = LVDS_VESA_18;
+ else
+ lvds->format = rockchip_lvds_name_to_format(name);
+
+ if (lvds->format < 0) {
+ DRM_DEV_ERROR(dev, "invalid data-mapping format [%s]\n", name);
+ ret = lvds->format;
+ goto err_put_remote;
+ }
+
+ encoder = &lvds->encoder;
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+ dev->of_node);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_LVDS);
+ if (ret < 0) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "failed to initialize encoder: %d\n", ret);
+ goto err_put_remote;
+ }
+
+ drm_encoder_helper_add(encoder, lvds->soc_data->helper_funcs);
+
+ if (lvds->panel) {
+ connector = &lvds->connector;
+ connector->dpms = DRM_MODE_DPMS_OFF;
+ ret = drm_connector_init(drm_dev, connector,
+ &rockchip_lvds_connector_funcs,
+ DRM_MODE_CONNECTOR_LVDS);
+ if (ret < 0) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "failed to initialize connector: %d\n", ret);
+ goto err_free_encoder;
+ }
+
+ drm_connector_helper_add(connector,
+ &rockchip_lvds_connector_helper_funcs);
+
+ ret = drm_connector_attach_encoder(connector, encoder);
+ if (ret < 0) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "failed to attach encoder: %d\n", ret);
+ goto err_free_connector;
+ }
+ } else {
+ ret = drm_bridge_attach(encoder, lvds->bridge, NULL, 0);
+ if (ret) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "failed to attach bridge: %d\n", ret);
+ goto err_free_encoder;
+ }
+ }
+
+ pm_runtime_enable(dev);
+ of_node_put(remote);
+ of_node_put(port);
+
+ return 0;
+
+err_free_connector:
+ drm_connector_cleanup(connector);
+err_free_encoder:
+ drm_encoder_cleanup(encoder);
+err_put_remote:
+ of_node_put(remote);
+err_put_port:
+ of_node_put(port);
+
+ return ret;
+}
+
+static void rockchip_lvds_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct rockchip_lvds *lvds = dev_get_drvdata(dev);
+ const struct drm_encoder_helper_funcs *encoder_funcs;
+
+ encoder_funcs = lvds->soc_data->helper_funcs;
+ encoder_funcs->disable(&lvds->encoder);
+ pm_runtime_disable(dev);
+ drm_connector_cleanup(&lvds->connector);
+ drm_encoder_cleanup(&lvds->encoder);
+}
+
+static const struct component_ops rockchip_lvds_component_ops = {
+ .bind = rockchip_lvds_bind,
+ .unbind = rockchip_lvds_unbind,
+};
+
+static int rockchip_lvds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rockchip_lvds *lvds;
+ const struct of_device_id *match;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+ if (!lvds)
+ return -ENOMEM;
+
+ lvds->dev = dev;
+ match = of_match_node(rockchip_lvds_dt_ids, dev->of_node);
+ if (!match)
+ return -ENODEV;
+ lvds->soc_data = match->data;
+
+ lvds->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(lvds->grf)) {
+ DRM_DEV_ERROR(dev, "missing rockchip,grf property\n");
+ return PTR_ERR(lvds->grf);
+ }
+
+ ret = lvds->soc_data->probe(pdev, lvds);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Platform initialization failed\n");
+ return ret;
+ }
+
+ dev_set_drvdata(dev, lvds);
+
+ ret = component_add(&pdev->dev, &rockchip_lvds_component_ops);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "failed to add component\n");
+ clk_unprepare(lvds->pclk);
+ }
+
+ return ret;
+}
+
+static int rockchip_lvds_remove(struct platform_device *pdev)
+{
+ struct rockchip_lvds *lvds = dev_get_drvdata(&pdev->dev);
+
+ component_del(&pdev->dev, &rockchip_lvds_component_ops);
+ clk_unprepare(lvds->pclk);
+
+ return 0;
+}
+
+struct platform_driver rockchip_lvds_driver = {
+ .probe = rockchip_lvds_probe,
+ .remove = rockchip_lvds_remove,
+ .driver = {
+ .name = "rockchip-lvds",
+ .of_match_table = of_match_ptr(rockchip_lvds_dt_ids),
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.h b/drivers/gpu/drm/rockchip/rockchip_lvds.h
new file mode 100644
index 000000000..4ce967d23
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:
+ * Sandy Huang <hjc@rock-chips.com>
+ * Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_LVDS_
+#define _ROCKCHIP_LVDS_
+
+#define RK3288_LVDS_CH0_REG0 0x00
+#define RK3288_LVDS_CH0_REG0_LVDS_EN BIT(7)
+#define RK3288_LVDS_CH0_REG0_TTL_EN BIT(6)
+#define RK3288_LVDS_CH0_REG0_LANECK_EN BIT(5)
+#define RK3288_LVDS_CH0_REG0_LANE4_EN BIT(4)
+#define RK3288_LVDS_CH0_REG0_LANE3_EN BIT(3)
+#define RK3288_LVDS_CH0_REG0_LANE2_EN BIT(2)
+#define RK3288_LVDS_CH0_REG0_LANE1_EN BIT(1)
+#define RK3288_LVDS_CH0_REG0_LANE0_EN BIT(0)
+
+#define RK3288_LVDS_CH0_REG1 0x04
+#define RK3288_LVDS_CH0_REG1_LANECK_BIAS BIT(5)
+#define RK3288_LVDS_CH0_REG1_LANE4_BIAS BIT(4)
+#define RK3288_LVDS_CH0_REG1_LANE3_BIAS BIT(3)
+#define RK3288_LVDS_CH0_REG1_LANE2_BIAS BIT(2)
+#define RK3288_LVDS_CH0_REG1_LANE1_BIAS BIT(1)
+#define RK3288_LVDS_CH0_REG1_LANE0_BIAS BIT(0)
+
+#define RK3288_LVDS_CH0_REG2 0x08
+#define RK3288_LVDS_CH0_REG2_RESERVE_ON BIT(7)
+#define RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE BIT(6)
+#define RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE BIT(5)
+#define RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE BIT(4)
+#define RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE BIT(3)
+#define RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE BIT(2)
+#define RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE BIT(1)
+#define RK3288_LVDS_CH0_REG2_PLL_FBDIV8 BIT(0)
+
+#define RK3288_LVDS_CH0_REG3 0x0c
+#define RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK 0xff
+
+#define RK3288_LVDS_CH0_REG4 0x10
+#define RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE BIT(5)
+#define RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE BIT(4)
+#define RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE BIT(3)
+#define RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE BIT(2)
+#define RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE BIT(1)
+#define RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE BIT(0)
+
+#define RK3288_LVDS_CH0_REG5 0x14
+#define RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA BIT(5)
+#define RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA BIT(4)
+#define RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA BIT(3)
+#define RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA BIT(2)
+#define RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA BIT(1)
+#define RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA BIT(0)
+
+#define RK3288_LVDS_CFG_REGC 0x30
+#define RK3288_LVDS_CFG_REGC_PLL_ENABLE 0x00
+#define RK3288_LVDS_CFG_REGC_PLL_DISABLE 0xff
+
+#define RK3288_LVDS_CH0_REGD 0x34
+#define RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK 0x1f
+
+#define RK3288_LVDS_CH0_REG20 0x80
+#define RK3288_LVDS_CH0_REG20_MSB 0x45
+#define RK3288_LVDS_CH0_REG20_LSB 0x44
+
+#define RK3288_LVDS_CFG_REG21 0x84
+#define RK3288_LVDS_CFG_REG21_TX_ENABLE 0x92
+#define RK3288_LVDS_CFG_REG21_TX_DISABLE 0x00
+#define RK3288_LVDS_CH1_OFFSET 0x100
+
+#define RK3288_LVDS_GRF_SOC_CON6 0x025C
+#define RK3288_LVDS_GRF_SOC_CON7 0x0260
+
+/* fbdiv value is split over 2 registers, with bit8 in reg2 */
+#define RK3288_LVDS_PLL_FBDIV_REG2(_fbd) \
+ (_fbd & BIT(8) ? RK3288_LVDS_CH0_REG2_PLL_FBDIV8 : 0)
+#define RK3288_LVDS_PLL_FBDIV_REG3(_fbd) \
+ (_fbd & RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK)
+#define RK3288_LVDS_PLL_PREDIV_REGD(_pd) \
+ (_pd & RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK)
+
+#define RK3288_LVDS_SOC_CON6_SEL_VOP_LIT BIT(3)
+
+#define LVDS_FMT_MASK (0x07 << 16)
+#define LVDS_MSB BIT(3)
+#define LVDS_DUAL BIT(4)
+#define LVDS_FMT_1 BIT(5)
+#define LVDS_TTL_EN BIT(6)
+#define LVDS_START_PHASE_RST_1 BIT(7)
+#define LVDS_DCLK_INV BIT(8)
+#define LVDS_CH0_EN BIT(11)
+#define LVDS_CH1_EN BIT(12)
+#define LVDS_PWRDN BIT(15)
+
+#define LVDS_24BIT (0 << 1)
+#define LVDS_18BIT (1 << 1)
+#define LVDS_FORMAT_VESA (0 << 0)
+#define LVDS_FORMAT_JEIDA (1 << 0)
+
+#define LVDS_VESA_24 0
+#define LVDS_JEIDA_24 1
+#define LVDS_VESA_18 2
+#define LVDS_JEIDA_18 3
+
+#define HIWORD_UPDATE(v, h, l) ((GENMASK(h, l) << 16) | ((v) << (l)))
+
+#define PX30_LVDS_GRF_PD_VO_CON0 0x434
+#define PX30_LVDS_TIE_CLKS(val) HIWORD_UPDATE(val, 8, 8)
+#define PX30_LVDS_INVERT_CLKS(val) HIWORD_UPDATE(val, 9, 9)
+#define PX30_LVDS_INVERT_DCLK(val) HIWORD_UPDATE(val, 5, 5)
+
+#define PX30_LVDS_GRF_PD_VO_CON1 0x438
+#define PX30_LVDS_FORMAT(val) HIWORD_UPDATE(val, 14, 13)
+#define PX30_LVDS_MODE_EN(val) HIWORD_UPDATE(val, 12, 12)
+#define PX30_LVDS_MSBSEL(val) HIWORD_UPDATE(val, 11, 11)
+#define PX30_LVDS_P2S_EN(val) HIWORD_UPDATE(val, 6, 6)
+#define PX30_LVDS_VOP_SEL(val) HIWORD_UPDATE(val, 1, 1)
+
+#endif /* _ROCKCHIP_LVDS_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
new file mode 100644
index 000000000..9a771af5d
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:
+ * Sandy Huang <hjc@rock-chips.com>
+ */
+
+#include <linux/component.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_vop.h"
+
+#define encoder_to_rgb(c) container_of(c, struct rockchip_rgb, encoder)
+
+struct rockchip_rgb {
+ struct device *dev;
+ struct drm_device *drm_dev;
+ struct drm_bridge *bridge;
+ struct drm_encoder encoder;
+ int output_mode;
+};
+
+static int
+rockchip_rgb_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+ struct drm_connector *connector = conn_state->connector;
+ struct drm_display_info *info = &connector->display_info;
+ u32 bus_format;
+
+ if (info->num_bus_formats)
+ bus_format = info->bus_formats[0];
+ else
+ bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ s->output_mode = ROCKCHIP_OUT_MODE_P666;
+ break;
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ s->output_mode = ROCKCHIP_OUT_MODE_P565;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+ default:
+ s->output_mode = ROCKCHIP_OUT_MODE_P888;
+ break;
+ }
+
+ s->output_type = DRM_MODE_CONNECTOR_LVDS;
+
+ return 0;
+}
+
+static const
+struct drm_encoder_helper_funcs rockchip_rgb_encoder_helper_funcs = {
+ .atomic_check = rockchip_rgb_encoder_atomic_check,
+};
+
+struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
+ struct drm_crtc *crtc,
+ struct drm_device *drm_dev)
+{
+ struct rockchip_rgb *rgb;
+ struct drm_encoder *encoder;
+ struct device_node *port, *endpoint;
+ u32 endpoint_id;
+ int ret = 0, child_count = 0;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+
+ rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
+ if (!rgb)
+ return ERR_PTR(-ENOMEM);
+
+ rgb->dev = dev;
+ rgb->drm_dev = drm_dev;
+
+ port = of_graph_get_port_by_id(dev->of_node, 0);
+ if (!port)
+ return ERR_PTR(-EINVAL);
+
+ for_each_child_of_node(port, endpoint) {
+ if (of_property_read_u32(endpoint, "reg", &endpoint_id))
+ endpoint_id = 0;
+
+ /* if subdriver (> 0) or error case (< 0), ignore entry */
+ if (rockchip_drm_endpoint_is_subdriver(endpoint) != 0)
+ continue;
+
+ child_count++;
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 0, endpoint_id,
+ &panel, &bridge);
+ if (!ret) {
+ of_node_put(endpoint);
+ break;
+ }
+ }
+
+ of_node_put(port);
+
+ /* if the rgb output is not connected to anything, just return */
+ if (!child_count)
+ return NULL;
+
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dev, "failed to find panel or bridge %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ encoder = &rgb->encoder;
+ encoder->possible_crtcs = drm_crtc_mask(crtc);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_NONE);
+ if (ret < 0) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "failed to initialize encoder: %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ drm_encoder_helper_add(encoder, &rockchip_rgb_encoder_helper_funcs);
+
+ if (panel) {
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_LVDS);
+ if (IS_ERR(bridge))
+ return ERR_CAST(bridge);
+ }
+
+ rgb->bridge = bridge;
+
+ ret = drm_bridge_attach(encoder, rgb->bridge, NULL, 0);
+ if (ret) {
+ DRM_DEV_ERROR(drm_dev->dev,
+ "failed to attach bridge: %d\n", ret);
+ goto err_free_encoder;
+ }
+
+ return rgb;
+
+err_free_encoder:
+ drm_encoder_cleanup(encoder);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(rockchip_rgb_init);
+
+void rockchip_rgb_fini(struct rockchip_rgb *rgb)
+{
+ drm_panel_bridge_remove(rgb->bridge);
+ drm_encoder_cleanup(&rgb->encoder);
+}
+EXPORT_SYMBOL_GPL(rockchip_rgb_fini);
diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.h b/drivers/gpu/drm/rockchip/rockchip_rgb.h
new file mode 100644
index 000000000..27b963512
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_rgb.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:
+ * Sandy Huang <hjc@rock-chips.com>
+ */
+
+#ifdef CONFIG_ROCKCHIP_RGB
+struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
+ struct drm_crtc *crtc,
+ struct drm_device *drm_dev);
+void rockchip_rgb_fini(struct rockchip_rgb *rgb);
+#else
+static inline struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
+ struct drm_crtc *crtc,
+ struct drm_device *drm_dev)
+{
+ return NULL;
+}
+
+static inline void rockchip_rgb_fini(struct rockchip_rgb *rgb)
+{
+}
+#endif
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
new file mode 100644
index 000000000..39e1e1ebe
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -0,0 +1,1102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "rockchip_drm_vop.h"
+#include "rockchip_vop_reg.h"
+#include "rockchip_drm_drv.h"
+
+#define _VOP_REG(off, _mask, _shift, _write_mask, _relaxed) \
+ { \
+ .offset = off, \
+ .mask = _mask, \
+ .shift = _shift, \
+ .write_mask = _write_mask, \
+ .relaxed = _relaxed, \
+ }
+
+#define VOP_REG(off, _mask, _shift) \
+ _VOP_REG(off, _mask, _shift, false, true)
+
+#define VOP_REG_SYNC(off, _mask, _shift) \
+ _VOP_REG(off, _mask, _shift, false, false)
+
+#define VOP_REG_MASK_SYNC(off, _mask, _shift) \
+ _VOP_REG(off, _mask, _shift, true, false)
+
+static const uint32_t formats_win_full[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+};
+
+static const uint64_t format_modifiers_win_full[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const uint64_t format_modifiers_win_full_afbc[] = {
+ ROCKCHIP_AFBC_MOD,
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const uint32_t formats_win_lite[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+};
+
+static const uint64_t format_modifiers_win_lite[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const struct vop_scl_regs rk3036_win_scl = {
+ .scale_yrgb_x = VOP_REG(RK3036_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
+ .scale_yrgb_y = VOP_REG(RK3036_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
+ .scale_cbcr_x = VOP_REG(RK3036_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
+ .scale_cbcr_y = VOP_REG(RK3036_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
+};
+
+static const struct vop_win_phy rk3036_win0_data = {
+ .scl = &rk3036_win_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(RK3036_SYS_CTRL, 0x1, 0),
+ .format = VOP_REG(RK3036_SYS_CTRL, 0x7, 3),
+ .rb_swap = VOP_REG(RK3036_SYS_CTRL, 0x1, 15),
+ .act_info = VOP_REG(RK3036_WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3036_WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3036_WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3036_WIN0_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3036_WIN0_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3036_WIN0_VIR, 0xffff, 0),
+ .uv_vir = VOP_REG(RK3036_WIN0_VIR, 0x1fff, 16),
+};
+
+static const struct vop_win_phy rk3036_win1_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .enable = VOP_REG(RK3036_SYS_CTRL, 0x1, 1),
+ .format = VOP_REG(RK3036_SYS_CTRL, 0x7, 6),
+ .rb_swap = VOP_REG(RK3036_SYS_CTRL, 0x1, 19),
+ .act_info = VOP_REG(RK3036_WIN1_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3036_WIN1_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3036_WIN1_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3036_WIN1_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3036_WIN1_VIR, 0xffff, 0),
+};
+
+static const struct vop_win_data rk3036_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3036_win0_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x00, .phy = &rk3036_win1_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const int rk3036_vop_intrs[] = {
+ DSP_HOLD_VALID_INTR,
+ FS_INTR,
+ LINE_FLAG_INTR,
+ BUS_ERROR_INTR,
+};
+
+static const struct vop_intr rk3036_intr = {
+ .intrs = rk3036_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3036_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3036_INT_STATUS, 0xfff, 12),
+ .status = VOP_REG_SYNC(RK3036_INT_STATUS, 0xf, 0),
+ .enable = VOP_REG_SYNC(RK3036_INT_STATUS, 0xf, 4),
+ .clear = VOP_REG_SYNC(RK3036_INT_STATUS, 0xf, 8),
+};
+
+static const struct vop_modeset rk3036_modeset = {
+ .htotal_pw = VOP_REG(RK3036_DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
+ .hact_st_end = VOP_REG(RK3036_DSP_HACT_ST_END, 0x1fff1fff, 0),
+ .vtotal_pw = VOP_REG(RK3036_DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
+ .vact_st_end = VOP_REG(RK3036_DSP_VACT_ST_END, 0x1fff1fff, 0),
+};
+
+static const struct vop_output rk3036_output = {
+ .pin_pol = VOP_REG(RK3036_DSP_CTRL0, 0xf, 4),
+};
+
+static const struct vop_common rk3036_common = {
+ .standby = VOP_REG_SYNC(RK3036_SYS_CTRL, 0x1, 30),
+ .out_mode = VOP_REG(RK3036_DSP_CTRL0, 0xf, 0),
+ .dsp_blank = VOP_REG(RK3036_DSP_CTRL1, 0x1, 24),
+ .dither_down_sel = VOP_REG(RK3036_DSP_CTRL0, 0x1, 27),
+ .dither_down_en = VOP_REG(RK3036_DSP_CTRL0, 0x1, 11),
+ .dither_down_mode = VOP_REG(RK3036_DSP_CTRL0, 0x1, 10),
+ .cfg_done = VOP_REG_SYNC(RK3036_REG_CFG_DONE, 0x1, 0),
+};
+
+static const struct vop_data rk3036_vop = {
+ .intr = &rk3036_intr,
+ .common = &rk3036_common,
+ .modeset = &rk3036_modeset,
+ .output = &rk3036_output,
+ .win = rk3036_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3036_vop_win_data),
+};
+
+static const struct vop_win_phy rk3126_win1_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .enable = VOP_REG(RK3036_SYS_CTRL, 0x1, 1),
+ .format = VOP_REG(RK3036_SYS_CTRL, 0x7, 6),
+ .rb_swap = VOP_REG(RK3036_SYS_CTRL, 0x1, 19),
+ .dsp_info = VOP_REG(RK3126_WIN1_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3126_WIN1_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3126_WIN1_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3036_WIN1_VIR, 0xffff, 0),
+};
+
+static const struct vop_win_data rk3126_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3036_win0_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x00, .phy = &rk3126_win1_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const struct vop_data rk3126_vop = {
+ .intr = &rk3036_intr,
+ .common = &rk3036_common,
+ .modeset = &rk3036_modeset,
+ .output = &rk3036_output,
+ .win = rk3126_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3126_vop_win_data),
+};
+
+static const int px30_vop_intrs[] = {
+ FS_INTR,
+ 0, 0,
+ LINE_FLAG_INTR,
+ 0,
+ BUS_ERROR_INTR,
+ 0, 0,
+ DSP_HOLD_VALID_INTR,
+};
+
+static const struct vop_intr px30_intr = {
+ .intrs = px30_vop_intrs,
+ .nintrs = ARRAY_SIZE(px30_vop_intrs),
+ .line_flag_num[0] = VOP_REG(PX30_LINE_FLAG, 0xfff, 0),
+ .status = VOP_REG_MASK_SYNC(PX30_INTR_STATUS, 0xffff, 0),
+ .enable = VOP_REG_MASK_SYNC(PX30_INTR_EN, 0xffff, 0),
+ .clear = VOP_REG_MASK_SYNC(PX30_INTR_CLEAR, 0xffff, 0),
+};
+
+static const struct vop_common px30_common = {
+ .standby = VOP_REG_SYNC(PX30_SYS_CTRL2, 0x1, 1),
+ .out_mode = VOP_REG(PX30_DSP_CTRL2, 0xf, 16),
+ .dsp_blank = VOP_REG(PX30_DSP_CTRL2, 0x1, 14),
+ .dither_down_en = VOP_REG(PX30_DSP_CTRL2, 0x1, 8),
+ .dither_down_sel = VOP_REG(PX30_DSP_CTRL2, 0x1, 7),
+ .dither_down_mode = VOP_REG(PX30_DSP_CTRL2, 0x1, 6),
+ .cfg_done = VOP_REG_SYNC(PX30_REG_CFG_DONE, 0x1, 0),
+};
+
+static const struct vop_modeset px30_modeset = {
+ .htotal_pw = VOP_REG(PX30_DSP_HTOTAL_HS_END, 0x0fff0fff, 0),
+ .hact_st_end = VOP_REG(PX30_DSP_HACT_ST_END, 0x0fff0fff, 0),
+ .vtotal_pw = VOP_REG(PX30_DSP_VTOTAL_VS_END, 0x0fff0fff, 0),
+ .vact_st_end = VOP_REG(PX30_DSP_VACT_ST_END, 0x0fff0fff, 0),
+};
+
+static const struct vop_output px30_output = {
+ .rgb_dclk_pol = VOP_REG(PX30_DSP_CTRL0, 0x1, 1),
+ .rgb_pin_pol = VOP_REG(PX30_DSP_CTRL0, 0x7, 2),
+ .rgb_en = VOP_REG(PX30_DSP_CTRL0, 0x1, 0),
+ .mipi_dclk_pol = VOP_REG(PX30_DSP_CTRL0, 0x1, 25),
+ .mipi_pin_pol = VOP_REG(PX30_DSP_CTRL0, 0x7, 26),
+ .mipi_en = VOP_REG(PX30_DSP_CTRL0, 0x1, 24),
+};
+
+static const struct vop_scl_regs px30_win_scl = {
+ .scale_yrgb_x = VOP_REG(PX30_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
+ .scale_yrgb_y = VOP_REG(PX30_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
+ .scale_cbcr_x = VOP_REG(PX30_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
+ .scale_cbcr_y = VOP_REG(PX30_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
+};
+
+static const struct vop_win_phy px30_win0_data = {
+ .scl = &px30_win_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(PX30_WIN0_CTRL0, 0x1, 0),
+ .format = VOP_REG(PX30_WIN0_CTRL0, 0x7, 1),
+ .rb_swap = VOP_REG(PX30_WIN0_CTRL0, 0x1, 12),
+ .act_info = VOP_REG(PX30_WIN0_ACT_INFO, 0xffffffff, 0),
+ .dsp_info = VOP_REG(PX30_WIN0_DSP_INFO, 0xffffffff, 0),
+ .dsp_st = VOP_REG(PX30_WIN0_DSP_ST, 0xffffffff, 0),
+ .yrgb_mst = VOP_REG(PX30_WIN0_YRGB_MST0, 0xffffffff, 0),
+ .uv_mst = VOP_REG(PX30_WIN0_CBR_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(PX30_WIN0_VIR, 0x1fff, 0),
+ .uv_vir = VOP_REG(PX30_WIN0_VIR, 0x1fff, 16),
+ .alpha_pre_mul = VOP_REG(PX30_WIN0_ALPHA_CTRL, 0x1, 2),
+ .alpha_mode = VOP_REG(PX30_WIN0_ALPHA_CTRL, 0x1, 1),
+ .alpha_en = VOP_REG(PX30_WIN0_ALPHA_CTRL, 0x1, 0),
+};
+
+static const struct vop_win_phy px30_win1_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .enable = VOP_REG(PX30_WIN1_CTRL0, 0x1, 0),
+ .format = VOP_REG(PX30_WIN1_CTRL0, 0x7, 4),
+ .rb_swap = VOP_REG(PX30_WIN1_CTRL0, 0x1, 12),
+ .dsp_info = VOP_REG(PX30_WIN1_DSP_INFO, 0xffffffff, 0),
+ .dsp_st = VOP_REG(PX30_WIN1_DSP_ST, 0xffffffff, 0),
+ .yrgb_mst = VOP_REG(PX30_WIN1_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(PX30_WIN1_VIR, 0x1fff, 0),
+ .alpha_pre_mul = VOP_REG(PX30_WIN1_ALPHA_CTRL, 0x1, 2),
+ .alpha_mode = VOP_REG(PX30_WIN1_ALPHA_CTRL, 0x1, 1),
+ .alpha_en = VOP_REG(PX30_WIN1_ALPHA_CTRL, 0x1, 0),
+};
+
+static const struct vop_win_phy px30_win2_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .gate = VOP_REG(PX30_WIN2_CTRL0, 0x1, 4),
+ .enable = VOP_REG(PX30_WIN2_CTRL0, 0x1, 0),
+ .format = VOP_REG(PX30_WIN2_CTRL0, 0x3, 5),
+ .rb_swap = VOP_REG(PX30_WIN2_CTRL0, 0x1, 20),
+ .dsp_info = VOP_REG(PX30_WIN2_DSP_INFO0, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(PX30_WIN2_DSP_ST0, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(PX30_WIN2_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(PX30_WIN2_VIR0_1, 0x1fff, 0),
+ .alpha_pre_mul = VOP_REG(PX30_WIN2_ALPHA_CTRL, 0x1, 2),
+ .alpha_mode = VOP_REG(PX30_WIN2_ALPHA_CTRL, 0x1, 1),
+ .alpha_en = VOP_REG(PX30_WIN2_ALPHA_CTRL, 0x1, 0),
+};
+
+static const struct vop_win_data px30_vop_big_win_data[] = {
+ { .base = 0x00, .phy = &px30_win0_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x00, .phy = &px30_win1_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x00, .phy = &px30_win2_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const struct vop_data px30_vop_big = {
+ .intr = &px30_intr,
+ .feature = VOP_FEATURE_INTERNAL_RGB,
+ .common = &px30_common,
+ .modeset = &px30_modeset,
+ .output = &px30_output,
+ .win = px30_vop_big_win_data,
+ .win_size = ARRAY_SIZE(px30_vop_big_win_data),
+};
+
+static const struct vop_win_data px30_vop_lit_win_data[] = {
+ { .base = 0x00, .phy = &px30_win1_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+};
+
+static const struct vop_data px30_vop_lit = {
+ .intr = &px30_intr,
+ .feature = VOP_FEATURE_INTERNAL_RGB,
+ .common = &px30_common,
+ .modeset = &px30_modeset,
+ .output = &px30_output,
+ .win = px30_vop_lit_win_data,
+ .win_size = ARRAY_SIZE(px30_vop_lit_win_data),
+};
+
+static const struct vop_scl_regs rk3066_win_scl = {
+ .scale_yrgb_x = VOP_REG(RK3066_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
+ .scale_yrgb_y = VOP_REG(RK3066_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
+ .scale_cbcr_x = VOP_REG(RK3066_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
+ .scale_cbcr_y = VOP_REG(RK3066_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
+};
+
+static const struct vop_win_phy rk3066_win0_data = {
+ .scl = &rk3066_win_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 0),
+ .format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 4),
+ .rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 19),
+ .act_info = VOP_REG(RK3066_WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3066_WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3066_WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3066_WIN0_YRGB_MST0, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3066_WIN0_CBR_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3066_WIN0_VIR, 0xffff, 0),
+ .uv_vir = VOP_REG(RK3066_WIN0_VIR, 0x1fff, 16),
+};
+
+static const struct vop_win_phy rk3066_win1_data = {
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 1),
+ .format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 7),
+ .rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 23),
+ .act_info = VOP_REG(RK3066_WIN1_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3066_WIN1_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3066_WIN1_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3066_WIN1_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3066_WIN1_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3066_WIN1_VIR, 0xffff, 0),
+ .uv_vir = VOP_REG(RK3066_WIN1_VIR, 0x1fff, 16),
+};
+
+static const struct vop_win_phy rk3066_win2_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 2),
+ .format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 10),
+ .rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 27),
+ .dsp_info = VOP_REG(RK3066_WIN2_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3066_WIN2_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3066_WIN2_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3066_WIN2_VIR, 0xffff, 0),
+};
+
+static const struct vop_modeset rk3066_modeset = {
+ .htotal_pw = VOP_REG(RK3066_DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
+ .hact_st_end = VOP_REG(RK3066_DSP_HACT_ST_END, 0x1fff1fff, 0),
+ .vtotal_pw = VOP_REG(RK3066_DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
+ .vact_st_end = VOP_REG(RK3066_DSP_VACT_ST_END, 0x1fff1fff, 0),
+};
+
+static const struct vop_output rk3066_output = {
+ .pin_pol = VOP_REG(RK3066_DSP_CTRL0, 0x7, 4),
+};
+
+static const struct vop_common rk3066_common = {
+ .standby = VOP_REG(RK3066_SYS_CTRL0, 0x1, 1),
+ .out_mode = VOP_REG(RK3066_DSP_CTRL0, 0xf, 0),
+ .cfg_done = VOP_REG(RK3066_REG_CFG_DONE, 0x1, 0),
+ .dither_down_en = VOP_REG(RK3066_DSP_CTRL0, 0x1, 11),
+ .dither_down_mode = VOP_REG(RK3066_DSP_CTRL0, 0x1, 10),
+ .dsp_blank = VOP_REG(RK3066_DSP_CTRL1, 0x1, 24),
+ .dither_up = VOP_REG(RK3066_DSP_CTRL0, 0x1, 9),
+ .dsp_lut_en = VOP_REG(RK3066_SYS_CTRL1, 0x1, 31),
+ .data_blank = VOP_REG(RK3066_DSP_CTRL1, 0x1, 25),
+};
+
+static const struct vop_win_data rk3066_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3066_win0_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x00, .phy = &rk3066_win1_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x00, .phy = &rk3066_win2_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const int rk3066_vop_intrs[] = {
+ /*
+ * hs_start interrupt fires at frame-start, so serves
+ * the same purpose as dsp_hold in the driver.
+ */
+ DSP_HOLD_VALID_INTR,
+ FS_INTR,
+ LINE_FLAG_INTR,
+ BUS_ERROR_INTR,
+};
+
+static const struct vop_intr rk3066_intr = {
+ .intrs = rk3066_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3066_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3066_INT_STATUS, 0xfff, 12),
+ .status = VOP_REG(RK3066_INT_STATUS, 0xf, 0),
+ .enable = VOP_REG(RK3066_INT_STATUS, 0xf, 4),
+ .clear = VOP_REG(RK3066_INT_STATUS, 0xf, 8),
+};
+
+static const struct vop_data rk3066_vop = {
+ .version = VOP_VERSION(2, 1),
+ .intr = &rk3066_intr,
+ .common = &rk3066_common,
+ .modeset = &rk3066_modeset,
+ .output = &rk3066_output,
+ .win = rk3066_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3066_vop_win_data),
+};
+
+static const struct vop_scl_regs rk3188_win_scl = {
+ .scale_yrgb_x = VOP_REG(RK3188_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
+ .scale_yrgb_y = VOP_REG(RK3188_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
+ .scale_cbcr_x = VOP_REG(RK3188_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
+ .scale_cbcr_y = VOP_REG(RK3188_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
+};
+
+static const struct vop_win_phy rk3188_win0_data = {
+ .scl = &rk3188_win_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(RK3188_SYS_CTRL, 0x1, 0),
+ .format = VOP_REG(RK3188_SYS_CTRL, 0x7, 3),
+ .rb_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 15),
+ .act_info = VOP_REG(RK3188_WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3188_WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3188_WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3188_WIN0_YRGB_MST0, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3188_WIN0_CBR_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3188_WIN_VIR, 0x1fff, 0),
+};
+
+static const struct vop_win_phy rk3188_win1_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .enable = VOP_REG(RK3188_SYS_CTRL, 0x1, 1),
+ .format = VOP_REG(RK3188_SYS_CTRL, 0x7, 6),
+ .rb_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 19),
+ /* no act_info on window1 */
+ .dsp_info = VOP_REG(RK3188_WIN1_DSP_INFO, 0x07ff07ff, 0),
+ .dsp_st = VOP_REG(RK3188_WIN1_DSP_ST, 0x0fff0fff, 0),
+ .yrgb_mst = VOP_REG(RK3188_WIN1_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3188_WIN_VIR, 0x1fff, 16),
+};
+
+static const struct vop_modeset rk3188_modeset = {
+ .htotal_pw = VOP_REG(RK3188_DSP_HTOTAL_HS_END, 0x0fff0fff, 0),
+ .hact_st_end = VOP_REG(RK3188_DSP_HACT_ST_END, 0x0fff0fff, 0),
+ .vtotal_pw = VOP_REG(RK3188_DSP_VTOTAL_VS_END, 0x0fff0fff, 0),
+ .vact_st_end = VOP_REG(RK3188_DSP_VACT_ST_END, 0x0fff0fff, 0),
+};
+
+static const struct vop_output rk3188_output = {
+ .pin_pol = VOP_REG(RK3188_DSP_CTRL0, 0xf, 4),
+};
+
+static const struct vop_common rk3188_common = {
+ .gate_en = VOP_REG(RK3188_SYS_CTRL, 0x1, 31),
+ .standby = VOP_REG(RK3188_SYS_CTRL, 0x1, 30),
+ .out_mode = VOP_REG(RK3188_DSP_CTRL0, 0xf, 0),
+ .cfg_done = VOP_REG(RK3188_REG_CFG_DONE, 0x1, 0),
+ .dither_down_sel = VOP_REG(RK3188_DSP_CTRL0, 0x1, 27),
+ .dither_down_en = VOP_REG(RK3188_DSP_CTRL0, 0x1, 11),
+ .dither_down_mode = VOP_REG(RK3188_DSP_CTRL0, 0x1, 10),
+ .dsp_blank = VOP_REG(RK3188_DSP_CTRL1, 0x1, 24),
+ .dither_up = VOP_REG(RK3188_DSP_CTRL0, 0x1, 9),
+ .dsp_lut_en = VOP_REG(RK3188_SYS_CTRL, 0x1, 28),
+ .data_blank = VOP_REG(RK3188_DSP_CTRL1, 0x1, 25),
+};
+
+static const struct vop_win_data rk3188_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3188_win0_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x00, .phy = &rk3188_win1_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const int rk3188_vop_intrs[] = {
+ /*
+ * hs_start interrupt fires at frame-start, so serves
+ * the same purpose as dsp_hold in the driver.
+ */
+ DSP_HOLD_VALID_INTR,
+ FS_INTR,
+ LINE_FLAG_INTR,
+ BUS_ERROR_INTR,
+};
+
+static const struct vop_intr rk3188_vop_intr = {
+ .intrs = rk3188_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3188_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3188_INT_STATUS, 0xfff, 12),
+ .status = VOP_REG(RK3188_INT_STATUS, 0xf, 0),
+ .enable = VOP_REG(RK3188_INT_STATUS, 0xf, 4),
+ .clear = VOP_REG(RK3188_INT_STATUS, 0xf, 8),
+};
+
+static const struct vop_data rk3188_vop = {
+ .intr = &rk3188_vop_intr,
+ .common = &rk3188_common,
+ .modeset = &rk3188_modeset,
+ .output = &rk3188_output,
+ .win = rk3188_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3188_vop_win_data),
+ .feature = VOP_FEATURE_INTERNAL_RGB,
+};
+
+static const struct vop_scl_extension rk3288_win_full_scl_ext = {
+ .cbcr_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 31),
+ .cbcr_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 30),
+ .cbcr_hsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 28),
+ .cbcr_ver_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 26),
+ .cbcr_hor_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 24),
+ .yrgb_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 23),
+ .yrgb_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 22),
+ .yrgb_hsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 20),
+ .yrgb_ver_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 18),
+ .yrgb_hor_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 16),
+ .line_load_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 15),
+ .cbcr_axi_gather_num = VOP_REG(RK3288_WIN0_CTRL1, 0x7, 12),
+ .yrgb_axi_gather_num = VOP_REG(RK3288_WIN0_CTRL1, 0xf, 8),
+ .vsd_cbcr_gt2 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 7),
+ .vsd_cbcr_gt4 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 6),
+ .vsd_yrgb_gt2 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 5),
+ .vsd_yrgb_gt4 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 4),
+ .bic_coe_sel = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 2),
+ .cbcr_axi_gather_en = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 1),
+ .yrgb_axi_gather_en = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 0),
+ .lb_mode = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 5),
+};
+
+static const struct vop_scl_regs rk3288_win_full_scl = {
+ .ext = &rk3288_win_full_scl_ext,
+ .scale_yrgb_x = VOP_REG(RK3288_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
+ .scale_yrgb_y = VOP_REG(RK3288_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
+ .scale_cbcr_x = VOP_REG(RK3288_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
+ .scale_cbcr_y = VOP_REG(RK3288_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
+};
+
+static const struct vop_win_phy rk3288_win01_data = {
+ .scl = &rk3288_win_full_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0),
+ .format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1),
+ .rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12),
+ .act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3288_WIN0_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3288_WIN0_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 0),
+ .uv_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 16),
+ .src_alpha_ctl = VOP_REG(RK3288_WIN0_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(RK3288_WIN0_DST_ALPHA_CTRL, 0xff, 0),
+ .channel = VOP_REG(RK3288_WIN0_CTRL2, 0xff, 0),
+};
+
+static const struct vop_win_phy rk3288_win23_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .enable = VOP_REG(RK3288_WIN2_CTRL0, 0x1, 4),
+ .gate = VOP_REG(RK3288_WIN2_CTRL0, 0x1, 0),
+ .format = VOP_REG(RK3288_WIN2_CTRL0, 0x7, 1),
+ .rb_swap = VOP_REG(RK3288_WIN2_CTRL0, 0x1, 12),
+ .dsp_info = VOP_REG(RK3288_WIN2_DSP_INFO0, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3288_WIN2_DSP_ST0, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3288_WIN2_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3288_WIN2_VIR0_1, 0x1fff, 0),
+ .src_alpha_ctl = VOP_REG(RK3288_WIN2_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(RK3288_WIN2_DST_ALPHA_CTRL, 0xff, 0),
+};
+
+static const struct vop_modeset rk3288_modeset = {
+ .htotal_pw = VOP_REG(RK3288_DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
+ .hact_st_end = VOP_REG(RK3288_DSP_HACT_ST_END, 0x1fff1fff, 0),
+ .vtotal_pw = VOP_REG(RK3288_DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
+ .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0),
+ .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0),
+ .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
+};
+
+static const struct vop_output rk3288_output = {
+ .pin_pol = VOP_REG(RK3288_DSP_CTRL0, 0xf, 4),
+ .rgb_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 12),
+ .hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),
+ .edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14),
+ .mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15),
+};
+
+static const struct vop_common rk3288_common = {
+ .standby = VOP_REG_SYNC(RK3288_SYS_CTRL, 0x1, 22),
+ .gate_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 23),
+ .mmu_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 20),
+ .dither_down_sel = VOP_REG(RK3288_DSP_CTRL1, 0x1, 4),
+ .dither_down_mode = VOP_REG(RK3288_DSP_CTRL1, 0x1, 3),
+ .dither_down_en = VOP_REG(RK3288_DSP_CTRL1, 0x1, 2),
+ .pre_dither_down = VOP_REG(RK3288_DSP_CTRL1, 0x1, 1),
+ .dither_up = VOP_REG(RK3288_DSP_CTRL1, 0x1, 6),
+ .dsp_lut_en = VOP_REG(RK3288_DSP_CTRL1, 0x1, 0),
+ .data_blank = VOP_REG(RK3288_DSP_CTRL0, 0x1, 19),
+ .dsp_blank = VOP_REG(RK3288_DSP_CTRL0, 0x3, 18),
+ .out_mode = VOP_REG(RK3288_DSP_CTRL0, 0xf, 0),
+ .cfg_done = VOP_REG_SYNC(RK3288_REG_CFG_DONE, 0x1, 0),
+};
+
+/*
+ * Note: rk3288 has a dedicated 'cursor' window, however, that window requires
+ * special support to get alpha blending working. For now, just use overlay
+ * window 3 for the drm cursor.
+ *
+ */
+static const struct vop_win_data rk3288_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3288_win01_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x40, .phy = &rk3288_win01_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x00, .phy = &rk3288_win23_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x50, .phy = &rk3288_win23_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const int rk3288_vop_intrs[] = {
+ DSP_HOLD_VALID_INTR,
+ FS_INTR,
+ LINE_FLAG_INTR,
+ BUS_ERROR_INTR,
+};
+
+static const struct vop_intr rk3288_vop_intr = {
+ .intrs = rk3288_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3288_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3288_INTR_CTRL0, 0x1fff, 12),
+ .status = VOP_REG(RK3288_INTR_CTRL0, 0xf, 0),
+ .enable = VOP_REG(RK3288_INTR_CTRL0, 0xf, 4),
+ .clear = VOP_REG(RK3288_INTR_CTRL0, 0xf, 8),
+};
+
+static const struct vop_data rk3288_vop = {
+ .version = VOP_VERSION(3, 1),
+ .feature = VOP_FEATURE_OUTPUT_RGB10,
+ .intr = &rk3288_vop_intr,
+ .common = &rk3288_common,
+ .modeset = &rk3288_modeset,
+ .output = &rk3288_output,
+ .win = rk3288_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3288_vop_win_data),
+ .lut_size = 1024,
+};
+
+static const int rk3368_vop_intrs[] = {
+ FS_INTR,
+ 0, 0,
+ LINE_FLAG_INTR,
+ 0,
+ BUS_ERROR_INTR,
+ 0, 0, 0, 0, 0, 0, 0,
+ DSP_HOLD_VALID_INTR,
+};
+
+static const struct vop_intr rk3368_vop_intr = {
+ .intrs = rk3368_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3368_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3368_LINE_FLAG, 0xffff, 0),
+ .line_flag_num[1] = VOP_REG(RK3368_LINE_FLAG, 0xffff, 16),
+ .status = VOP_REG_MASK_SYNC(RK3368_INTR_STATUS, 0x3fff, 0),
+ .enable = VOP_REG_MASK_SYNC(RK3368_INTR_EN, 0x3fff, 0),
+ .clear = VOP_REG_MASK_SYNC(RK3368_INTR_CLEAR, 0x3fff, 0),
+};
+
+static const struct vop_win_phy rk3368_win01_data = {
+ .scl = &rk3288_win_full_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full,
+ .enable = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 0),
+ .format = VOP_REG(RK3368_WIN0_CTRL0, 0x7, 1),
+ .rb_swap = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 12),
+ .x_mir_en = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 21),
+ .y_mir_en = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 22),
+ .act_info = VOP_REG(RK3368_WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3368_WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3368_WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3368_WIN0_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3368_WIN0_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3368_WIN0_VIR, 0x3fff, 0),
+ .uv_vir = VOP_REG(RK3368_WIN0_VIR, 0x3fff, 16),
+ .src_alpha_ctl = VOP_REG(RK3368_WIN0_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(RK3368_WIN0_DST_ALPHA_CTRL, 0xff, 0),
+ .channel = VOP_REG(RK3368_WIN0_CTRL2, 0xff, 0),
+};
+
+static const struct vop_win_phy rk3368_win23_data = {
+ .data_formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers_win_lite,
+ .gate = VOP_REG(RK3368_WIN2_CTRL0, 0x1, 0),
+ .enable = VOP_REG(RK3368_WIN2_CTRL0, 0x1, 4),
+ .format = VOP_REG(RK3368_WIN2_CTRL0, 0x3, 5),
+ .rb_swap = VOP_REG(RK3368_WIN2_CTRL0, 0x1, 20),
+ .y_mir_en = VOP_REG(RK3368_WIN2_CTRL1, 0x1, 15),
+ .dsp_info = VOP_REG(RK3368_WIN2_DSP_INFO0, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3368_WIN2_DSP_ST0, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3368_WIN2_MST0, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3368_WIN2_VIR0_1, 0x1fff, 0),
+ .src_alpha_ctl = VOP_REG(RK3368_WIN2_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(RK3368_WIN2_DST_ALPHA_CTRL, 0xff, 0),
+};
+
+static const struct vop_win_data rk3368_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x40, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x00, .phy = &rk3368_win23_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x50, .phy = &rk3368_win23_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const struct vop_output rk3368_output = {
+ .rgb_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 19),
+ .hdmi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 23),
+ .edp_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 27),
+ .mipi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 31),
+ .rgb_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 16),
+ .hdmi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 20),
+ .edp_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 24),
+ .mipi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 28),
+ .rgb_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 12),
+ .hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),
+ .edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14),
+ .mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15),
+};
+
+static const struct vop_misc rk3368_misc = {
+ .global_regdone_en = VOP_REG(RK3368_SYS_CTRL, 0x1, 11),
+};
+
+static const struct vop_data rk3368_vop = {
+ .version = VOP_VERSION(3, 2),
+ .intr = &rk3368_vop_intr,
+ .common = &rk3288_common,
+ .modeset = &rk3288_modeset,
+ .output = &rk3368_output,
+ .misc = &rk3368_misc,
+ .win = rk3368_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3368_vop_win_data),
+};
+
+static const struct vop_intr rk3366_vop_intr = {
+ .intrs = rk3368_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3368_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3366_LINE_FLAG, 0xffff, 0),
+ .line_flag_num[1] = VOP_REG(RK3366_LINE_FLAG, 0xffff, 16),
+ .status = VOP_REG_MASK_SYNC(RK3366_INTR_STATUS0, 0xffff, 0),
+ .enable = VOP_REG_MASK_SYNC(RK3366_INTR_EN0, 0xffff, 0),
+ .clear = VOP_REG_MASK_SYNC(RK3366_INTR_CLEAR0, 0xffff, 0),
+};
+
+static const struct vop_data rk3366_vop = {
+ .version = VOP_VERSION(3, 4),
+ .intr = &rk3366_vop_intr,
+ .common = &rk3288_common,
+ .modeset = &rk3288_modeset,
+ .output = &rk3368_output,
+ .misc = &rk3368_misc,
+ .win = rk3368_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3368_vop_win_data),
+};
+
+static const struct vop_output rk3399_output = {
+ .dp_dclk_pol = VOP_REG(RK3399_DSP_CTRL1, 0x1, 19),
+ .rgb_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 19),
+ .hdmi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 23),
+ .edp_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 27),
+ .mipi_dclk_pol = VOP_REG(RK3368_DSP_CTRL1, 0x1, 31),
+ .dp_pin_pol = VOP_REG(RK3399_DSP_CTRL1, 0x7, 16),
+ .rgb_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 16),
+ .hdmi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 20),
+ .edp_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 24),
+ .mipi_pin_pol = VOP_REG(RK3368_DSP_CTRL1, 0x7, 28),
+ .dp_en = VOP_REG(RK3399_SYS_CTRL, 0x1, 11),
+ .rgb_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 12),
+ .hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13),
+ .edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14),
+ .mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15),
+ .mipi_dual_channel_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 3),
+};
+
+static const struct vop_yuv2yuv_phy rk3399_yuv2yuv_win01_data = {
+ .y2r_coefficients = {
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 0, 0xffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 0, 0xffff, 16),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 4, 0xffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 4, 0xffff, 16),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 8, 0xffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 8, 0xffff, 16),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 12, 0xffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 12, 0xffff, 16),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 16, 0xffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 20, 0xffffffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 24, 0xffffffff, 0),
+ VOP_REG(RK3399_WIN0_YUV2YUV_Y2R + 28, 0xffffffff, 0),
+ },
+};
+
+static const struct vop_yuv2yuv_phy rk3399_yuv2yuv_win23_data = { };
+
+static const struct vop_win_yuv2yuv_data rk3399_vop_big_win_yuv2yuv_data[] = {
+ { .base = 0x00, .phy = &rk3399_yuv2yuv_win01_data,
+ .y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 1) },
+ { .base = 0x60, .phy = &rk3399_yuv2yuv_win01_data,
+ .y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 9) },
+ { .base = 0xC0, .phy = &rk3399_yuv2yuv_win23_data },
+ { .base = 0x120, .phy = &rk3399_yuv2yuv_win23_data },
+
+};
+
+static const struct vop_win_phy rk3399_win01_data = {
+ .scl = &rk3288_win_full_scl,
+ .data_formats = formats_win_full,
+ .nformats = ARRAY_SIZE(formats_win_full),
+ .format_modifiers = format_modifiers_win_full_afbc,
+ .enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0),
+ .format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1),
+ .rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12),
+ .x_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 21),
+ .y_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 22),
+ .act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0),
+ .dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0),
+ .dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0),
+ .yrgb_mst = VOP_REG(RK3288_WIN0_YRGB_MST, 0xffffffff, 0),
+ .uv_mst = VOP_REG(RK3288_WIN0_CBR_MST, 0xffffffff, 0),
+ .yrgb_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 0),
+ .uv_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 16),
+ .src_alpha_ctl = VOP_REG(RK3288_WIN0_SRC_ALPHA_CTRL, 0xff, 0),
+ .dst_alpha_ctl = VOP_REG(RK3288_WIN0_DST_ALPHA_CTRL, 0xff, 0),
+ .channel = VOP_REG(RK3288_WIN0_CTRL2, 0xff, 0),
+};
+
+/*
+ * rk3399 vop big windows register layout is same as rk3288, but we
+ * have a separate rk3399 win data array here so that we can advertise
+ * AFBC on the primary plane.
+ */
+static const struct vop_win_data rk3399_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3399_win01_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x40, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x00, .phy = &rk3368_win23_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x50, .phy = &rk3368_win23_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const struct vop_afbc rk3399_vop_afbc = {
+ .rstn = VOP_REG(RK3399_AFBCD0_CTRL, 0x1, 3),
+ .enable = VOP_REG(RK3399_AFBCD0_CTRL, 0x1, 0),
+ .win_sel = VOP_REG(RK3399_AFBCD0_CTRL, 0x3, 1),
+ .format = VOP_REG(RK3399_AFBCD0_CTRL, 0x1f, 16),
+ .hreg_block_split = VOP_REG(RK3399_AFBCD0_CTRL, 0x1, 21),
+ .hdr_ptr = VOP_REG(RK3399_AFBCD0_HDR_PTR, 0xffffffff, 0),
+ .pic_size = VOP_REG(RK3399_AFBCD0_PIC_SIZE, 0xffffffff, 0),
+};
+
+static const struct vop_data rk3399_vop_big = {
+ .version = VOP_VERSION(3, 5),
+ .feature = VOP_FEATURE_OUTPUT_RGB10,
+ .intr = &rk3366_vop_intr,
+ .common = &rk3288_common,
+ .modeset = &rk3288_modeset,
+ .output = &rk3399_output,
+ .afbc = &rk3399_vop_afbc,
+ .misc = &rk3368_misc,
+ .win = rk3399_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3399_vop_win_data),
+ .win_yuv2yuv = rk3399_vop_big_win_yuv2yuv_data,
+};
+
+static const struct vop_win_data rk3399_vop_lit_win_data[] = {
+ { .base = 0x00, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x00, .phy = &rk3368_win23_data,
+ .type = DRM_PLANE_TYPE_CURSOR},
+};
+
+static const struct vop_win_yuv2yuv_data rk3399_vop_lit_win_yuv2yuv_data[] = {
+ { .base = 0x00, .phy = &rk3399_yuv2yuv_win01_data,
+ .y2r_en = VOP_REG(RK3399_YUV2YUV_WIN, 0x1, 1)},
+ { .base = 0x60, .phy = &rk3399_yuv2yuv_win23_data },
+};
+
+static const struct vop_data rk3399_vop_lit = {
+ .version = VOP_VERSION(3, 6),
+ .intr = &rk3366_vop_intr,
+ .common = &rk3288_common,
+ .modeset = &rk3288_modeset,
+ .output = &rk3399_output,
+ .misc = &rk3368_misc,
+ .win = rk3399_vop_lit_win_data,
+ .win_size = ARRAY_SIZE(rk3399_vop_lit_win_data),
+ .win_yuv2yuv = rk3399_vop_lit_win_yuv2yuv_data,
+};
+
+static const struct vop_win_data rk3228_vop_win_data[] = {
+ { .base = 0x00, .phy = &rk3288_win01_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x40, .phy = &rk3288_win01_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const struct vop_data rk3228_vop = {
+ .version = VOP_VERSION(3, 7),
+ .feature = VOP_FEATURE_OUTPUT_RGB10,
+ .intr = &rk3366_vop_intr,
+ .common = &rk3288_common,
+ .modeset = &rk3288_modeset,
+ .output = &rk3399_output,
+ .misc = &rk3368_misc,
+ .win = rk3228_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3228_vop_win_data),
+};
+
+static const struct vop_modeset rk3328_modeset = {
+ .htotal_pw = VOP_REG(RK3328_DSP_HTOTAL_HS_END, 0x1fff1fff, 0),
+ .hact_st_end = VOP_REG(RK3328_DSP_HACT_ST_END, 0x1fff1fff, 0),
+ .vtotal_pw = VOP_REG(RK3328_DSP_VTOTAL_VS_END, 0x1fff1fff, 0),
+ .vact_st_end = VOP_REG(RK3328_DSP_VACT_ST_END, 0x1fff1fff, 0),
+ .hpost_st_end = VOP_REG(RK3328_POST_DSP_HACT_INFO, 0x1fff1fff, 0),
+ .vpost_st_end = VOP_REG(RK3328_POST_DSP_VACT_INFO, 0x1fff1fff, 0),
+};
+
+static const struct vop_output rk3328_output = {
+ .rgb_dclk_pol = VOP_REG(RK3328_DSP_CTRL1, 0x1, 19),
+ .hdmi_dclk_pol = VOP_REG(RK3328_DSP_CTRL1, 0x1, 23),
+ .edp_dclk_pol = VOP_REG(RK3328_DSP_CTRL1, 0x1, 27),
+ .mipi_dclk_pol = VOP_REG(RK3328_DSP_CTRL1, 0x1, 31),
+ .rgb_en = VOP_REG(RK3328_SYS_CTRL, 0x1, 12),
+ .hdmi_en = VOP_REG(RK3328_SYS_CTRL, 0x1, 13),
+ .edp_en = VOP_REG(RK3328_SYS_CTRL, 0x1, 14),
+ .mipi_en = VOP_REG(RK3328_SYS_CTRL, 0x1, 15),
+ .rgb_pin_pol = VOP_REG(RK3328_DSP_CTRL1, 0x7, 16),
+ .hdmi_pin_pol = VOP_REG(RK3328_DSP_CTRL1, 0x7, 20),
+ .edp_pin_pol = VOP_REG(RK3328_DSP_CTRL1, 0x7, 24),
+ .mipi_pin_pol = VOP_REG(RK3328_DSP_CTRL1, 0x7, 28),
+};
+
+static const struct vop_misc rk3328_misc = {
+ .global_regdone_en = VOP_REG(RK3328_SYS_CTRL, 0x1, 11),
+};
+
+static const struct vop_common rk3328_common = {
+ .standby = VOP_REG_SYNC(RK3328_SYS_CTRL, 0x1, 22),
+ .dither_down_sel = VOP_REG(RK3328_DSP_CTRL1, 0x1, 4),
+ .dither_down_mode = VOP_REG(RK3328_DSP_CTRL1, 0x1, 3),
+ .dither_down_en = VOP_REG(RK3328_DSP_CTRL1, 0x1, 2),
+ .pre_dither_down = VOP_REG(RK3328_DSP_CTRL1, 0x1, 1),
+ .dither_up = VOP_REG(RK3328_DSP_CTRL1, 0x1, 6),
+ .dsp_blank = VOP_REG(RK3328_DSP_CTRL0, 0x3, 18),
+ .out_mode = VOP_REG(RK3328_DSP_CTRL0, 0xf, 0),
+ .cfg_done = VOP_REG_SYNC(RK3328_REG_CFG_DONE, 0x1, 0),
+};
+
+static const struct vop_intr rk3328_vop_intr = {
+ .intrs = rk3368_vop_intrs,
+ .nintrs = ARRAY_SIZE(rk3368_vop_intrs),
+ .line_flag_num[0] = VOP_REG(RK3328_LINE_FLAG, 0xffff, 0),
+ .line_flag_num[1] = VOP_REG(RK3328_LINE_FLAG, 0xffff, 16),
+ .status = VOP_REG_MASK_SYNC(RK3328_INTR_STATUS0, 0xffff, 0),
+ .enable = VOP_REG_MASK_SYNC(RK3328_INTR_EN0, 0xffff, 0),
+ .clear = VOP_REG_MASK_SYNC(RK3328_INTR_CLEAR0, 0xffff, 0),
+};
+
+static const struct vop_win_data rk3328_vop_win_data[] = {
+ { .base = 0xd0, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_PRIMARY },
+ { .base = 0x1d0, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_OVERLAY },
+ { .base = 0x2d0, .phy = &rk3368_win01_data,
+ .type = DRM_PLANE_TYPE_CURSOR },
+};
+
+static const struct vop_data rk3328_vop = {
+ .version = VOP_VERSION(3, 8),
+ .feature = VOP_FEATURE_OUTPUT_RGB10,
+ .intr = &rk3328_vop_intr,
+ .common = &rk3328_common,
+ .modeset = &rk3328_modeset,
+ .output = &rk3328_output,
+ .misc = &rk3328_misc,
+ .win = rk3328_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3328_vop_win_data),
+};
+
+static const struct of_device_id vop_driver_dt_match[] = {
+ { .compatible = "rockchip,rk3036-vop",
+ .data = &rk3036_vop },
+ { .compatible = "rockchip,rk3126-vop",
+ .data = &rk3126_vop },
+ { .compatible = "rockchip,px30-vop-big",
+ .data = &px30_vop_big },
+ { .compatible = "rockchip,px30-vop-lit",
+ .data = &px30_vop_lit },
+ { .compatible = "rockchip,rk3066-vop",
+ .data = &rk3066_vop },
+ { .compatible = "rockchip,rk3188-vop",
+ .data = &rk3188_vop },
+ { .compatible = "rockchip,rk3288-vop",
+ .data = &rk3288_vop },
+ { .compatible = "rockchip,rk3368-vop",
+ .data = &rk3368_vop },
+ { .compatible = "rockchip,rk3366-vop",
+ .data = &rk3366_vop },
+ { .compatible = "rockchip,rk3399-vop-big",
+ .data = &rk3399_vop_big },
+ { .compatible = "rockchip,rk3399-vop-lit",
+ .data = &rk3399_vop_lit },
+ { .compatible = "rockchip,rk3228-vop",
+ .data = &rk3228_vop },
+ { .compatible = "rockchip,rk3328-vop",
+ .data = &rk3328_vop },
+ {},
+};
+MODULE_DEVICE_TABLE(of, vop_driver_dt_match);
+
+static int vop_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->of_node) {
+ DRM_DEV_ERROR(dev, "can't find vop devices\n");
+ return -ENODEV;
+ }
+
+ return component_add(dev, &vop_component_ops);
+}
+
+static int vop_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &vop_component_ops);
+
+ return 0;
+}
+
+struct platform_driver vop_platform_driver = {
+ .probe = vop_probe,
+ .remove = vop_remove,
+ .driver = {
+ .name = "rockchip-vop",
+ .of_match_table = of_match_ptr(vop_driver_dt_match),
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.h b/drivers/gpu/drm/rockchip/rockchip_vop_reg.h
new file mode 100644
index 000000000..6e9fa5815
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.h
@@ -0,0 +1,1031 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_VOP_REG_H
+#define _ROCKCHIP_VOP_REG_H
+
+/* rk3288 register definition */
+#define RK3288_REG_CFG_DONE 0x0000
+#define RK3288_VERSION_INFO 0x0004
+#define RK3288_SYS_CTRL 0x0008
+#define RK3288_SYS_CTRL1 0x000c
+#define RK3288_DSP_CTRL0 0x0010
+#define RK3288_DSP_CTRL1 0x0014
+#define RK3288_DSP_BG 0x0018
+#define RK3288_MCU_CTRL 0x001c
+#define RK3288_INTR_CTRL0 0x0020
+#define RK3288_INTR_CTRL1 0x0024
+#define RK3288_WIN0_CTRL0 0x0030
+#define RK3288_WIN0_CTRL1 0x0034
+#define RK3288_WIN0_COLOR_KEY 0x0038
+#define RK3288_WIN0_VIR 0x003c
+#define RK3288_WIN0_YRGB_MST 0x0040
+#define RK3288_WIN0_CBR_MST 0x0044
+#define RK3288_WIN0_ACT_INFO 0x0048
+#define RK3288_WIN0_DSP_INFO 0x004c
+#define RK3288_WIN0_DSP_ST 0x0050
+#define RK3288_WIN0_SCL_FACTOR_YRGB 0x0054
+#define RK3288_WIN0_SCL_FACTOR_CBR 0x0058
+#define RK3288_WIN0_SCL_OFFSET 0x005c
+#define RK3288_WIN0_SRC_ALPHA_CTRL 0x0060
+#define RK3288_WIN0_DST_ALPHA_CTRL 0x0064
+#define RK3288_WIN0_FADING_CTRL 0x0068
+#define RK3288_WIN0_CTRL2 0x006c
+
+/* win1 register */
+#define RK3288_WIN1_CTRL0 0x0070
+#define RK3288_WIN1_CTRL1 0x0074
+#define RK3288_WIN1_COLOR_KEY 0x0078
+#define RK3288_WIN1_VIR 0x007c
+#define RK3288_WIN1_YRGB_MST 0x0080
+#define RK3288_WIN1_CBR_MST 0x0084
+#define RK3288_WIN1_ACT_INFO 0x0088
+#define RK3288_WIN1_DSP_INFO 0x008c
+#define RK3288_WIN1_DSP_ST 0x0090
+#define RK3288_WIN1_SCL_FACTOR_YRGB 0x0094
+#define RK3288_WIN1_SCL_FACTOR_CBR 0x0098
+#define RK3288_WIN1_SCL_OFFSET 0x009c
+#define RK3288_WIN1_SRC_ALPHA_CTRL 0x00a0
+#define RK3288_WIN1_DST_ALPHA_CTRL 0x00a4
+#define RK3288_WIN1_FADING_CTRL 0x00a8
+/* win2 register */
+#define RK3288_WIN2_CTRL0 0x00b0
+#define RK3288_WIN2_CTRL1 0x00b4
+#define RK3288_WIN2_VIR0_1 0x00b8
+#define RK3288_WIN2_VIR2_3 0x00bc
+#define RK3288_WIN2_MST0 0x00c0
+#define RK3288_WIN2_DSP_INFO0 0x00c4
+#define RK3288_WIN2_DSP_ST0 0x00c8
+#define RK3288_WIN2_COLOR_KEY 0x00cc
+#define RK3288_WIN2_MST1 0x00d0
+#define RK3288_WIN2_DSP_INFO1 0x00d4
+#define RK3288_WIN2_DSP_ST1 0x00d8
+#define RK3288_WIN2_SRC_ALPHA_CTRL 0x00dc
+#define RK3288_WIN2_MST2 0x00e0
+#define RK3288_WIN2_DSP_INFO2 0x00e4
+#define RK3288_WIN2_DSP_ST2 0x00e8
+#define RK3288_WIN2_DST_ALPHA_CTRL 0x00ec
+#define RK3288_WIN2_MST3 0x00f0
+#define RK3288_WIN2_DSP_INFO3 0x00f4
+#define RK3288_WIN2_DSP_ST3 0x00f8
+#define RK3288_WIN2_FADING_CTRL 0x00fc
+/* win3 register */
+#define RK3288_WIN3_CTRL0 0x0100
+#define RK3288_WIN3_CTRL1 0x0104
+#define RK3288_WIN3_VIR0_1 0x0108
+#define RK3288_WIN3_VIR2_3 0x010c
+#define RK3288_WIN3_MST0 0x0110
+#define RK3288_WIN3_DSP_INFO0 0x0114
+#define RK3288_WIN3_DSP_ST0 0x0118
+#define RK3288_WIN3_COLOR_KEY 0x011c
+#define RK3288_WIN3_MST1 0x0120
+#define RK3288_WIN3_DSP_INFO1 0x0124
+#define RK3288_WIN3_DSP_ST1 0x0128
+#define RK3288_WIN3_SRC_ALPHA_CTRL 0x012c
+#define RK3288_WIN3_MST2 0x0130
+#define RK3288_WIN3_DSP_INFO2 0x0134
+#define RK3288_WIN3_DSP_ST2 0x0138
+#define RK3288_WIN3_DST_ALPHA_CTRL 0x013c
+#define RK3288_WIN3_MST3 0x0140
+#define RK3288_WIN3_DSP_INFO3 0x0144
+#define RK3288_WIN3_DSP_ST3 0x0148
+#define RK3288_WIN3_FADING_CTRL 0x014c
+/* hwc register */
+#define RK3288_HWC_CTRL0 0x0150
+#define RK3288_HWC_CTRL1 0x0154
+#define RK3288_HWC_MST 0x0158
+#define RK3288_HWC_DSP_ST 0x015c
+#define RK3288_HWC_SRC_ALPHA_CTRL 0x0160
+#define RK3288_HWC_DST_ALPHA_CTRL 0x0164
+#define RK3288_HWC_FADING_CTRL 0x0168
+/* post process register */
+#define RK3288_POST_DSP_HACT_INFO 0x0170
+#define RK3288_POST_DSP_VACT_INFO 0x0174
+#define RK3288_POST_SCL_FACTOR_YRGB 0x0178
+#define RK3288_POST_SCL_CTRL 0x0180
+#define RK3288_POST_DSP_VACT_INFO_F1 0x0184
+#define RK3288_DSP_HTOTAL_HS_END 0x0188
+#define RK3288_DSP_HACT_ST_END 0x018c
+#define RK3288_DSP_VTOTAL_VS_END 0x0190
+#define RK3288_DSP_VACT_ST_END 0x0194
+#define RK3288_DSP_VS_ST_END_F1 0x0198
+#define RK3288_DSP_VACT_ST_END_F1 0x019c
+/* register definition end */
+
+/* rk3368 register definition */
+#define RK3368_REG_CFG_DONE 0x0000
+#define RK3368_VERSION_INFO 0x0004
+#define RK3368_SYS_CTRL 0x0008
+#define RK3368_SYS_CTRL1 0x000c
+#define RK3368_DSP_CTRL0 0x0010
+#define RK3368_DSP_CTRL1 0x0014
+#define RK3368_DSP_BG 0x0018
+#define RK3368_MCU_CTRL 0x001c
+#define RK3368_LINE_FLAG 0x0020
+#define RK3368_INTR_EN 0x0024
+#define RK3368_INTR_CLEAR 0x0028
+#define RK3368_INTR_STATUS 0x002c
+#define RK3368_WIN0_CTRL0 0x0030
+#define RK3368_WIN0_CTRL1 0x0034
+#define RK3368_WIN0_COLOR_KEY 0x0038
+#define RK3368_WIN0_VIR 0x003c
+#define RK3368_WIN0_YRGB_MST 0x0040
+#define RK3368_WIN0_CBR_MST 0x0044
+#define RK3368_WIN0_ACT_INFO 0x0048
+#define RK3368_WIN0_DSP_INFO 0x004c
+#define RK3368_WIN0_DSP_ST 0x0050
+#define RK3368_WIN0_SCL_FACTOR_YRGB 0x0054
+#define RK3368_WIN0_SCL_FACTOR_CBR 0x0058
+#define RK3368_WIN0_SCL_OFFSET 0x005c
+#define RK3368_WIN0_SRC_ALPHA_CTRL 0x0060
+#define RK3368_WIN0_DST_ALPHA_CTRL 0x0064
+#define RK3368_WIN0_FADING_CTRL 0x0068
+#define RK3368_WIN0_CTRL2 0x006c
+#define RK3368_WIN1_CTRL0 0x0070
+#define RK3368_WIN1_CTRL1 0x0074
+#define RK3368_WIN1_COLOR_KEY 0x0078
+#define RK3368_WIN1_VIR 0x007c
+#define RK3368_WIN1_YRGB_MST 0x0080
+#define RK3368_WIN1_CBR_MST 0x0084
+#define RK3368_WIN1_ACT_INFO 0x0088
+#define RK3368_WIN1_DSP_INFO 0x008c
+#define RK3368_WIN1_DSP_ST 0x0090
+#define RK3368_WIN1_SCL_FACTOR_YRGB 0x0094
+#define RK3368_WIN1_SCL_FACTOR_CBR 0x0098
+#define RK3368_WIN1_SCL_OFFSET 0x009c
+#define RK3368_WIN1_SRC_ALPHA_CTRL 0x00a0
+#define RK3368_WIN1_DST_ALPHA_CTRL 0x00a4
+#define RK3368_WIN1_FADING_CTRL 0x00a8
+#define RK3368_WIN1_CTRL2 0x00ac
+#define RK3368_WIN2_CTRL0 0x00b0
+#define RK3368_WIN2_CTRL1 0x00b4
+#define RK3368_WIN2_VIR0_1 0x00b8
+#define RK3368_WIN2_VIR2_3 0x00bc
+#define RK3368_WIN2_MST0 0x00c0
+#define RK3368_WIN2_DSP_INFO0 0x00c4
+#define RK3368_WIN2_DSP_ST0 0x00c8
+#define RK3368_WIN2_COLOR_KEY 0x00cc
+#define RK3368_WIN2_MST1 0x00d0
+#define RK3368_WIN2_DSP_INFO1 0x00d4
+#define RK3368_WIN2_DSP_ST1 0x00d8
+#define RK3368_WIN2_SRC_ALPHA_CTRL 0x00dc
+#define RK3368_WIN2_MST2 0x00e0
+#define RK3368_WIN2_DSP_INFO2 0x00e4
+#define RK3368_WIN2_DSP_ST2 0x00e8
+#define RK3368_WIN2_DST_ALPHA_CTRL 0x00ec
+#define RK3368_WIN2_MST3 0x00f0
+#define RK3368_WIN2_DSP_INFO3 0x00f4
+#define RK3368_WIN2_DSP_ST3 0x00f8
+#define RK3368_WIN2_FADING_CTRL 0x00fc
+#define RK3368_WIN3_CTRL0 0x0100
+#define RK3368_WIN3_CTRL1 0x0104
+#define RK3368_WIN3_VIR0_1 0x0108
+#define RK3368_WIN3_VIR2_3 0x010c
+#define RK3368_WIN3_MST0 0x0110
+#define RK3368_WIN3_DSP_INFO0 0x0114
+#define RK3368_WIN3_DSP_ST0 0x0118
+#define RK3368_WIN3_COLOR_KEY 0x011c
+#define RK3368_WIN3_MST1 0x0120
+#define RK3368_WIN3_DSP_INFO1 0x0124
+#define RK3368_WIN3_DSP_ST1 0x0128
+#define RK3368_WIN3_SRC_ALPHA_CTRL 0x012c
+#define RK3368_WIN3_MST2 0x0130
+#define RK3368_WIN3_DSP_INFO2 0x0134
+#define RK3368_WIN3_DSP_ST2 0x0138
+#define RK3368_WIN3_DST_ALPHA_CTRL 0x013c
+#define RK3368_WIN3_MST3 0x0140
+#define RK3368_WIN3_DSP_INFO3 0x0144
+#define RK3368_WIN3_DSP_ST3 0x0148
+#define RK3368_WIN3_FADING_CTRL 0x014c
+#define RK3368_HWC_CTRL0 0x0150
+#define RK3368_HWC_CTRL1 0x0154
+#define RK3368_HWC_MST 0x0158
+#define RK3368_HWC_DSP_ST 0x015c
+#define RK3368_HWC_SRC_ALPHA_CTRL 0x0160
+#define RK3368_HWC_DST_ALPHA_CTRL 0x0164
+#define RK3368_HWC_FADING_CTRL 0x0168
+#define RK3368_HWC_RESERVED1 0x016c
+#define RK3368_POST_DSP_HACT_INFO 0x0170
+#define RK3368_POST_DSP_VACT_INFO 0x0174
+#define RK3368_POST_SCL_FACTOR_YRGB 0x0178
+#define RK3368_POST_RESERVED 0x017c
+#define RK3368_POST_SCL_CTRL 0x0180
+#define RK3368_POST_DSP_VACT_INFO_F1 0x0184
+#define RK3368_DSP_HTOTAL_HS_END 0x0188
+#define RK3368_DSP_HACT_ST_END 0x018c
+#define RK3368_DSP_VTOTAL_VS_END 0x0190
+#define RK3368_DSP_VACT_ST_END 0x0194
+#define RK3368_DSP_VS_ST_END_F1 0x0198
+#define RK3368_DSP_VACT_ST_END_F1 0x019c
+#define RK3368_PWM_CTRL 0x01a0
+#define RK3368_PWM_PERIOD_HPR 0x01a4
+#define RK3368_PWM_DUTY_LPR 0x01a8
+#define RK3368_PWM_CNT 0x01ac
+#define RK3368_BCSH_COLOR_BAR 0x01b0
+#define RK3368_BCSH_BCS 0x01b4
+#define RK3368_BCSH_H 0x01b8
+#define RK3368_BCSH_CTRL 0x01bc
+#define RK3368_CABC_CTRL0 0x01c0
+#define RK3368_CABC_CTRL1 0x01c4
+#define RK3368_CABC_CTRL2 0x01c8
+#define RK3368_CABC_CTRL3 0x01cc
+#define RK3368_CABC_GAUSS_LINE0_0 0x01d0
+#define RK3368_CABC_GAUSS_LINE0_1 0x01d4
+#define RK3368_CABC_GAUSS_LINE1_0 0x01d8
+#define RK3368_CABC_GAUSS_LINE1_1 0x01dc
+#define RK3368_CABC_GAUSS_LINE2_0 0x01e0
+#define RK3368_CABC_GAUSS_LINE2_1 0x01e4
+#define RK3368_FRC_LOWER01_0 0x01e8
+#define RK3368_FRC_LOWER01_1 0x01ec
+#define RK3368_FRC_LOWER10_0 0x01f0
+#define RK3368_FRC_LOWER10_1 0x01f4
+#define RK3368_FRC_LOWER11_0 0x01f8
+#define RK3368_FRC_LOWER11_1 0x01fc
+#define RK3368_IFBDC_CTRL 0x0200
+#define RK3368_IFBDC_TILES_NUM 0x0204
+#define RK3368_IFBDC_FRAME_RST_CYCLE 0x0208
+#define RK3368_IFBDC_BASE_ADDR 0x020c
+#define RK3368_IFBDC_MB_SIZE 0x0210
+#define RK3368_IFBDC_CMP_INDEX_INIT 0x0214
+#define RK3368_IFBDC_VIR 0x0220
+#define RK3368_IFBDC_DEBUG0 0x0230
+#define RK3368_IFBDC_DEBUG1 0x0234
+#define RK3368_LATENCY_CTRL0 0x0250
+#define RK3368_RD_MAX_LATENCY_NUM0 0x0254
+#define RK3368_RD_LATENCY_THR_NUM0 0x0258
+#define RK3368_RD_LATENCY_SAMP_NUM0 0x025c
+#define RK3368_WIN0_DSP_BG 0x0260
+#define RK3368_WIN1_DSP_BG 0x0264
+#define RK3368_WIN2_DSP_BG 0x0268
+#define RK3368_WIN3_DSP_BG 0x026c
+#define RK3368_SCAN_LINE_NUM 0x0270
+#define RK3368_CABC_DEBUG0 0x0274
+#define RK3368_CABC_DEBUG1 0x0278
+#define RK3368_CABC_DEBUG2 0x027c
+#define RK3368_DBG_REG_000 0x0280
+#define RK3368_DBG_REG_001 0x0284
+#define RK3368_DBG_REG_002 0x0288
+#define RK3368_DBG_REG_003 0x028c
+#define RK3368_DBG_REG_004 0x0290
+#define RK3368_DBG_REG_005 0x0294
+#define RK3368_DBG_REG_006 0x0298
+#define RK3368_DBG_REG_007 0x029c
+#define RK3368_DBG_REG_008 0x02a0
+#define RK3368_DBG_REG_016 0x02c0
+#define RK3368_DBG_REG_017 0x02c4
+#define RK3368_DBG_REG_018 0x02c8
+#define RK3368_DBG_REG_019 0x02cc
+#define RK3368_DBG_REG_020 0x02d0
+#define RK3368_DBG_REG_021 0x02d4
+#define RK3368_DBG_REG_022 0x02d8
+#define RK3368_DBG_REG_023 0x02dc
+#define RK3368_DBG_REG_028 0x02f0
+#define RK3368_MMU_DTE_ADDR 0x0300
+#define RK3368_MMU_STATUS 0x0304
+#define RK3368_MMU_COMMAND 0x0308
+#define RK3368_MMU_PAGE_FAULT_ADDR 0x030c
+#define RK3368_MMU_ZAP_ONE_LINE 0x0310
+#define RK3368_MMU_INT_RAWSTAT 0x0314
+#define RK3368_MMU_INT_CLEAR 0x0318
+#define RK3368_MMU_INT_MASK 0x031c
+#define RK3368_MMU_INT_STATUS 0x0320
+#define RK3368_MMU_AUTO_GATING 0x0324
+#define RK3368_WIN2_LUT_ADDR 0x0400
+#define RK3368_WIN3_LUT_ADDR 0x0800
+#define RK3368_HWC_LUT_ADDR 0x0c00
+#define RK3368_GAMMA_LUT_ADDR 0x1000
+#define RK3368_CABC_GAMMA_LUT_ADDR 0x1800
+#define RK3368_MCU_BYPASS_WPORT 0x2200
+#define RK3368_MCU_BYPASS_RPORT 0x2300
+/* rk3368 register definition end */
+
+#define RK3366_REG_CFG_DONE 0x0000
+#define RK3366_VERSION_INFO 0x0004
+#define RK3366_SYS_CTRL 0x0008
+#define RK3366_SYS_CTRL1 0x000c
+#define RK3366_DSP_CTRL0 0x0010
+#define RK3366_DSP_CTRL1 0x0014
+#define RK3366_DSP_BG 0x0018
+#define RK3366_MCU_CTRL 0x001c
+#define RK3366_WB_CTRL0 0x0020
+#define RK3366_WB_CTRL1 0x0024
+#define RK3366_WB_YRGB_MST 0x0028
+#define RK3366_WB_CBR_MST 0x002c
+#define RK3366_WIN0_CTRL0 0x0030
+#define RK3366_WIN0_CTRL1 0x0034
+#define RK3366_WIN0_COLOR_KEY 0x0038
+#define RK3366_WIN0_VIR 0x003c
+#define RK3366_WIN0_YRGB_MST 0x0040
+#define RK3366_WIN0_CBR_MST 0x0044
+#define RK3366_WIN0_ACT_INFO 0x0048
+#define RK3366_WIN0_DSP_INFO 0x004c
+#define RK3366_WIN0_DSP_ST 0x0050
+#define RK3366_WIN0_SCL_FACTOR_YRGB 0x0054
+#define RK3366_WIN0_SCL_FACTOR_CBR 0x0058
+#define RK3366_WIN0_SCL_OFFSET 0x005c
+#define RK3366_WIN0_SRC_ALPHA_CTRL 0x0060
+#define RK3366_WIN0_DST_ALPHA_CTRL 0x0064
+#define RK3366_WIN0_FADING_CTRL 0x0068
+#define RK3366_WIN0_CTRL2 0x006c
+#define RK3366_WIN1_CTRL0 0x0070
+#define RK3366_WIN1_CTRL1 0x0074
+#define RK3366_WIN1_COLOR_KEY 0x0078
+#define RK3366_WIN1_VIR 0x007c
+#define RK3366_WIN1_YRGB_MST 0x0080
+#define RK3366_WIN1_CBR_MST 0x0084
+#define RK3366_WIN1_ACT_INFO 0x0088
+#define RK3366_WIN1_DSP_INFO 0x008c
+#define RK3366_WIN1_DSP_ST 0x0090
+#define RK3366_WIN1_SCL_FACTOR_YRGB 0x0094
+#define RK3366_WIN1_SCL_FACTOR_CBR 0x0098
+#define RK3366_WIN1_SCL_OFFSET 0x009c
+#define RK3366_WIN1_SRC_ALPHA_CTRL 0x00a0
+#define RK3366_WIN1_DST_ALPHA_CTRL 0x00a4
+#define RK3366_WIN1_FADING_CTRL 0x00a8
+#define RK3366_WIN1_CTRL2 0x00ac
+#define RK3366_WIN2_CTRL0 0x00b0
+#define RK3366_WIN2_CTRL1 0x00b4
+#define RK3366_WIN2_VIR0_1 0x00b8
+#define RK3366_WIN2_VIR2_3 0x00bc
+#define RK3366_WIN2_MST0 0x00c0
+#define RK3366_WIN2_DSP_INFO0 0x00c4
+#define RK3366_WIN2_DSP_ST0 0x00c8
+#define RK3366_WIN2_COLOR_KEY 0x00cc
+#define RK3366_WIN2_MST1 0x00d0
+#define RK3366_WIN2_DSP_INFO1 0x00d4
+#define RK3366_WIN2_DSP_ST1 0x00d8
+#define RK3366_WIN2_SRC_ALPHA_CTRL 0x00dc
+#define RK3366_WIN2_MST2 0x00e0
+#define RK3366_WIN2_DSP_INFO2 0x00e4
+#define RK3366_WIN2_DSP_ST2 0x00e8
+#define RK3366_WIN2_DST_ALPHA_CTRL 0x00ec
+#define RK3366_WIN2_MST3 0x00f0
+#define RK3366_WIN2_DSP_INFO3 0x00f4
+#define RK3366_WIN2_DSP_ST3 0x00f8
+#define RK3366_WIN2_FADING_CTRL 0x00fc
+#define RK3366_WIN3_CTRL0 0x0100
+#define RK3366_WIN3_CTRL1 0x0104
+#define RK3366_WIN3_VIR0_1 0x0108
+#define RK3366_WIN3_VIR2_3 0x010c
+#define RK3366_WIN3_MST0 0x0110
+#define RK3366_WIN3_DSP_INFO0 0x0114
+#define RK3366_WIN3_DSP_ST0 0x0118
+#define RK3366_WIN3_COLOR_KEY 0x011c
+#define RK3366_WIN3_MST1 0x0120
+#define RK3366_WIN3_DSP_INFO1 0x0124
+#define RK3366_WIN3_DSP_ST1 0x0128
+#define RK3366_WIN3_SRC_ALPHA_CTRL 0x012c
+#define RK3366_WIN3_MST2 0x0130
+#define RK3366_WIN3_DSP_INFO2 0x0134
+#define RK3366_WIN3_DSP_ST2 0x0138
+#define RK3366_WIN3_DST_ALPHA_CTRL 0x013c
+#define RK3366_WIN3_MST3 0x0140
+#define RK3366_WIN3_DSP_INFO3 0x0144
+#define RK3366_WIN3_DSP_ST3 0x0148
+#define RK3366_WIN3_FADING_CTRL 0x014c
+#define RK3366_HWC_CTRL0 0x0150
+#define RK3366_HWC_CTRL1 0x0154
+#define RK3366_HWC_MST 0x0158
+#define RK3366_HWC_DSP_ST 0x015c
+#define RK3366_HWC_SRC_ALPHA_CTRL 0x0160
+#define RK3366_HWC_DST_ALPHA_CTRL 0x0164
+#define RK3366_HWC_FADING_CTRL 0x0168
+#define RK3366_HWC_RESERVED1 0x016c
+#define RK3366_POST_DSP_HACT_INFO 0x0170
+#define RK3366_POST_DSP_VACT_INFO 0x0174
+#define RK3366_POST_SCL_FACTOR_YRGB 0x0178
+#define RK3366_POST_RESERVED 0x017c
+#define RK3366_POST_SCL_CTRL 0x0180
+#define RK3366_POST_DSP_VACT_INFO_F1 0x0184
+#define RK3366_DSP_HTOTAL_HS_END 0x0188
+#define RK3366_DSP_HACT_ST_END 0x018c
+#define RK3366_DSP_VTOTAL_VS_END 0x0190
+#define RK3366_DSP_VACT_ST_END 0x0194
+#define RK3366_DSP_VS_ST_END_F1 0x0198
+#define RK3366_DSP_VACT_ST_END_F1 0x019c
+#define RK3366_PWM_CTRL 0x01a0
+#define RK3366_PWM_PERIOD_HPR 0x01a4
+#define RK3366_PWM_DUTY_LPR 0x01a8
+#define RK3366_PWM_CNT 0x01ac
+#define RK3366_BCSH_COLOR_BAR 0x01b0
+#define RK3366_BCSH_BCS 0x01b4
+#define RK3366_BCSH_H 0x01b8
+#define RK3366_BCSH_CTRL 0x01bc
+#define RK3366_CABC_CTRL0 0x01c0
+#define RK3366_CABC_CTRL1 0x01c4
+#define RK3366_CABC_CTRL2 0x01c8
+#define RK3366_CABC_CTRL3 0x01cc
+#define RK3366_CABC_GAUSS_LINE0_0 0x01d0
+#define RK3366_CABC_GAUSS_LINE0_1 0x01d4
+#define RK3366_CABC_GAUSS_LINE1_0 0x01d8
+#define RK3366_CABC_GAUSS_LINE1_1 0x01dc
+#define RK3366_CABC_GAUSS_LINE2_0 0x01e0
+#define RK3366_CABC_GAUSS_LINE2_1 0x01e4
+#define RK3366_FRC_LOWER01_0 0x01e8
+#define RK3366_FRC_LOWER01_1 0x01ec
+#define RK3366_FRC_LOWER10_0 0x01f0
+#define RK3366_FRC_LOWER10_1 0x01f4
+#define RK3366_FRC_LOWER11_0 0x01f8
+#define RK3366_FRC_LOWER11_1 0x01fc
+#define RK3366_INTR_EN0 0x0280
+#define RK3366_INTR_CLEAR0 0x0284
+#define RK3366_INTR_STATUS0 0x0288
+#define RK3366_INTR_RAW_STATUS0 0x028c
+#define RK3366_INTR_EN1 0x0290
+#define RK3366_INTR_CLEAR1 0x0294
+#define RK3366_INTR_STATUS1 0x0298
+#define RK3366_INTR_RAW_STATUS1 0x029c
+#define RK3366_LINE_FLAG 0x02a0
+#define RK3366_VOP_STATUS 0x02a4
+#define RK3366_BLANKING_VALUE 0x02a8
+#define RK3366_WIN0_DSP_BG 0x02b0
+#define RK3366_WIN1_DSP_BG 0x02b4
+#define RK3366_WIN2_DSP_BG 0x02b8
+#define RK3366_WIN3_DSP_BG 0x02bc
+#define RK3366_WIN2_LUT_ADDR 0x0400
+#define RK3366_WIN3_LUT_ADDR 0x0800
+#define RK3366_HWC_LUT_ADDR 0x0c00
+#define RK3366_GAMMA0_LUT_ADDR 0x1000
+#define RK3366_GAMMA1_LUT_ADDR 0x1400
+#define RK3366_CABC_GAMMA_LUT_ADDR 0x1800
+#define RK3366_MCU_BYPASS_WPORT 0x2200
+#define RK3366_MCU_BYPASS_RPORT 0x2300
+#define RK3366_MMU_DTE_ADDR 0x2400
+#define RK3366_MMU_STATUS 0x2404
+#define RK3366_MMU_COMMAND 0x2408
+#define RK3366_MMU_PAGE_FAULT_ADDR 0x240c
+#define RK3366_MMU_ZAP_ONE_LINE 0x2410
+#define RK3366_MMU_INT_RAWSTAT 0x2414
+#define RK3366_MMU_INT_CLEAR 0x2418
+#define RK3366_MMU_INT_MASK 0x241c
+#define RK3366_MMU_INT_STATUS 0x2420
+#define RK3366_MMU_AUTO_GATING 0x2424
+
+/* rk3399 register definition */
+#define RK3399_REG_CFG_DONE 0x0000
+#define RK3399_VERSION_INFO 0x0004
+#define RK3399_SYS_CTRL 0x0008
+#define RK3399_SYS_CTRL1 0x000c
+#define RK3399_DSP_CTRL0 0x0010
+#define RK3399_DSP_CTRL1 0x0014
+#define RK3399_DSP_BG 0x0018
+#define RK3399_MCU_CTRL 0x001c
+#define RK3399_WB_CTRL0 0x0020
+#define RK3399_WB_CTRL1 0x0024
+#define RK3399_WB_YRGB_MST 0x0028
+#define RK3399_WB_CBR_MST 0x002c
+#define RK3399_WIN0_CTRL0 0x0030
+#define RK3399_WIN0_CTRL1 0x0034
+#define RK3399_WIN0_COLOR_KEY 0x0038
+#define RK3399_WIN0_VIR 0x003c
+#define RK3399_WIN0_YRGB_MST 0x0040
+#define RK3399_WIN0_CBR_MST 0x0044
+#define RK3399_WIN0_ACT_INFO 0x0048
+#define RK3399_WIN0_DSP_INFO 0x004c
+#define RK3399_WIN0_DSP_ST 0x0050
+#define RK3399_WIN0_SCL_FACTOR_YRGB 0x0054
+#define RK3399_WIN0_SCL_FACTOR_CBR 0x0058
+#define RK3399_WIN0_SCL_OFFSET 0x005c
+#define RK3399_WIN0_SRC_ALPHA_CTRL 0x0060
+#define RK3399_WIN0_DST_ALPHA_CTRL 0x0064
+#define RK3399_WIN0_FADING_CTRL 0x0068
+#define RK3399_WIN0_CTRL2 0x006c
+#define RK3399_WIN1_CTRL0 0x0070
+#define RK3399_WIN1_CTRL1 0x0074
+#define RK3399_WIN1_COLOR_KEY 0x0078
+#define RK3399_WIN1_VIR 0x007c
+#define RK3399_WIN1_YRGB_MST 0x0080
+#define RK3399_WIN1_CBR_MST 0x0084
+#define RK3399_WIN1_ACT_INFO 0x0088
+#define RK3399_WIN1_DSP_INFO 0x008c
+#define RK3399_WIN1_DSP_ST 0x0090
+#define RK3399_WIN1_SCL_FACTOR_YRGB 0x0094
+#define RK3399_WIN1_SCL_FACTOR_CBR 0x0098
+#define RK3399_WIN1_SCL_OFFSET 0x009c
+#define RK3399_WIN1_SRC_ALPHA_CTRL 0x00a0
+#define RK3399_WIN1_DST_ALPHA_CTRL 0x00a4
+#define RK3399_WIN1_FADING_CTRL 0x00a8
+#define RK3399_WIN1_CTRL2 0x00ac
+#define RK3399_WIN2_CTRL0 0x00b0
+#define RK3399_WIN2_CTRL1 0x00b4
+#define RK3399_WIN2_VIR0_1 0x00b8
+#define RK3399_WIN2_VIR2_3 0x00bc
+#define RK3399_WIN2_MST0 0x00c0
+#define RK3399_WIN2_DSP_INFO0 0x00c4
+#define RK3399_WIN2_DSP_ST0 0x00c8
+#define RK3399_WIN2_COLOR_KEY 0x00cc
+#define RK3399_WIN2_MST1 0x00d0
+#define RK3399_WIN2_DSP_INFO1 0x00d4
+#define RK3399_WIN2_DSP_ST1 0x00d8
+#define RK3399_WIN2_SRC_ALPHA_CTRL 0x00dc
+#define RK3399_WIN2_MST2 0x00e0
+#define RK3399_WIN2_DSP_INFO2 0x00e4
+#define RK3399_WIN2_DSP_ST2 0x00e8
+#define RK3399_WIN2_DST_ALPHA_CTRL 0x00ec
+#define RK3399_WIN2_MST3 0x00f0
+#define RK3399_WIN2_DSP_INFO3 0x00f4
+#define RK3399_WIN2_DSP_ST3 0x00f8
+#define RK3399_WIN2_FADING_CTRL 0x00fc
+#define RK3399_WIN3_CTRL0 0x0100
+#define RK3399_WIN3_CTRL1 0x0104
+#define RK3399_WIN3_VIR0_1 0x0108
+#define RK3399_WIN3_VIR2_3 0x010c
+#define RK3399_WIN3_MST0 0x0110
+#define RK3399_WIN3_DSP_INFO0 0x0114
+#define RK3399_WIN3_DSP_ST0 0x0118
+#define RK3399_WIN3_COLOR_KEY 0x011c
+#define RK3399_WIN3_MST1 0x0120
+#define RK3399_WIN3_DSP_INFO1 0x0124
+#define RK3399_WIN3_DSP_ST1 0x0128
+#define RK3399_WIN3_SRC_ALPHA_CTRL 0x012c
+#define RK3399_WIN3_MST2 0x0130
+#define RK3399_WIN3_DSP_INFO2 0x0134
+#define RK3399_WIN3_DSP_ST2 0x0138
+#define RK3399_WIN3_DST_ALPHA_CTRL 0x013c
+#define RK3399_WIN3_MST3 0x0140
+#define RK3399_WIN3_DSP_INFO3 0x0144
+#define RK3399_WIN3_DSP_ST3 0x0148
+#define RK3399_WIN3_FADING_CTRL 0x014c
+#define RK3399_HWC_CTRL0 0x0150
+#define RK3399_HWC_CTRL1 0x0154
+#define RK3399_HWC_MST 0x0158
+#define RK3399_HWC_DSP_ST 0x015c
+#define RK3399_HWC_SRC_ALPHA_CTRL 0x0160
+#define RK3399_HWC_DST_ALPHA_CTRL 0x0164
+#define RK3399_HWC_FADING_CTRL 0x0168
+#define RK3399_HWC_RESERVED1 0x016c
+#define RK3399_POST_DSP_HACT_INFO 0x0170
+#define RK3399_POST_DSP_VACT_INFO 0x0174
+#define RK3399_POST_SCL_FACTOR_YRGB 0x0178
+#define RK3399_POST_RESERVED 0x017c
+#define RK3399_POST_SCL_CTRL 0x0180
+#define RK3399_POST_DSP_VACT_INFO_F1 0x0184
+#define RK3399_DSP_HTOTAL_HS_END 0x0188
+#define RK3399_DSP_HACT_ST_END 0x018c
+#define RK3399_DSP_VTOTAL_VS_END 0x0190
+#define RK3399_DSP_VACT_ST_END 0x0194
+#define RK3399_DSP_VS_ST_END_F1 0x0198
+#define RK3399_DSP_VACT_ST_END_F1 0x019c
+#define RK3399_PWM_CTRL 0x01a0
+#define RK3399_PWM_PERIOD_HPR 0x01a4
+#define RK3399_PWM_DUTY_LPR 0x01a8
+#define RK3399_PWM_CNT 0x01ac
+#define RK3399_BCSH_COLOR_BAR 0x01b0
+#define RK3399_BCSH_BCS 0x01b4
+#define RK3399_BCSH_H 0x01b8
+#define RK3399_BCSH_CTRL 0x01bc
+#define RK3399_CABC_CTRL0 0x01c0
+#define RK3399_CABC_CTRL1 0x01c4
+#define RK3399_CABC_CTRL2 0x01c8
+#define RK3399_CABC_CTRL3 0x01cc
+#define RK3399_CABC_GAUSS_LINE0_0 0x01d0
+#define RK3399_CABC_GAUSS_LINE0_1 0x01d4
+#define RK3399_CABC_GAUSS_LINE1_0 0x01d8
+#define RK3399_CABC_GAUSS_LINE1_1 0x01dc
+#define RK3399_CABC_GAUSS_LINE2_0 0x01e0
+#define RK3399_CABC_GAUSS_LINE2_1 0x01e4
+#define RK3399_FRC_LOWER01_0 0x01e8
+#define RK3399_FRC_LOWER01_1 0x01ec
+#define RK3399_FRC_LOWER10_0 0x01f0
+#define RK3399_FRC_LOWER10_1 0x01f4
+#define RK3399_FRC_LOWER11_0 0x01f8
+#define RK3399_FRC_LOWER11_1 0x01fc
+#define RK3399_AFBCD0_CTRL 0x0200
+#define RK3399_AFBCD0_HDR_PTR 0x0204
+#define RK3399_AFBCD0_PIC_SIZE 0x0208
+#define RK3399_AFBCD0_STATUS 0x020c
+#define RK3399_AFBCD1_CTRL 0x0220
+#define RK3399_AFBCD1_HDR_PTR 0x0224
+#define RK3399_AFBCD1_PIC_SIZE 0x0228
+#define RK3399_AFBCD1_STATUS 0x022c
+#define RK3399_AFBCD2_CTRL 0x0240
+#define RK3399_AFBCD2_HDR_PTR 0x0244
+#define RK3399_AFBCD2_PIC_SIZE 0x0248
+#define RK3399_AFBCD2_STATUS 0x024c
+#define RK3399_AFBCD3_CTRL 0x0260
+#define RK3399_AFBCD3_HDR_PTR 0x0264
+#define RK3399_AFBCD3_PIC_SIZE 0x0268
+#define RK3399_AFBCD3_STATUS 0x026c
+#define RK3399_INTR_EN0 0x0280
+#define RK3399_INTR_CLEAR0 0x0284
+#define RK3399_INTR_STATUS0 0x0288
+#define RK3399_INTR_RAW_STATUS0 0x028c
+#define RK3399_INTR_EN1 0x0290
+#define RK3399_INTR_CLEAR1 0x0294
+#define RK3399_INTR_STATUS1 0x0298
+#define RK3399_INTR_RAW_STATUS1 0x029c
+#define RK3399_LINE_FLAG 0x02a0
+#define RK3399_VOP_STATUS 0x02a4
+#define RK3399_BLANKING_VALUE 0x02a8
+#define RK3399_MCU_BYPASS_PORT 0x02ac
+#define RK3399_WIN0_DSP_BG 0x02b0
+#define RK3399_WIN1_DSP_BG 0x02b4
+#define RK3399_WIN2_DSP_BG 0x02b8
+#define RK3399_WIN3_DSP_BG 0x02bc
+#define RK3399_YUV2YUV_WIN 0x02c0
+#define RK3399_YUV2YUV_POST 0x02c4
+#define RK3399_AUTO_GATING_EN 0x02cc
+#define RK3399_WIN0_CSC_COE 0x03a0
+#define RK3399_WIN1_CSC_COE 0x03c0
+#define RK3399_WIN2_CSC_COE 0x03e0
+#define RK3399_WIN3_CSC_COE 0x0400
+#define RK3399_HWC_CSC_COE 0x0420
+#define RK3399_BCSH_R2Y_CSC_COE 0x0440
+#define RK3399_BCSH_Y2R_CSC_COE 0x0460
+#define RK3399_POST_YUV2YUV_Y2R_COE 0x0480
+#define RK3399_POST_YUV2YUV_3X3_COE 0x04a0
+#define RK3399_POST_YUV2YUV_R2Y_COE 0x04c0
+#define RK3399_WIN0_YUV2YUV_Y2R 0x04e0
+#define RK3399_WIN0_YUV2YUV_3X3 0x0500
+#define RK3399_WIN0_YUV2YUV_R2Y 0x0520
+#define RK3399_WIN1_YUV2YUV_Y2R 0x0540
+#define RK3399_WIN1_YUV2YUV_3X3 0x0560
+#define RK3399_WIN1_YUV2YUV_R2Y 0x0580
+#define RK3399_WIN2_YUV2YUV_Y2R 0x05a0
+#define RK3399_WIN2_YUV2YUV_3X3 0x05c0
+#define RK3399_WIN2_YUV2YUV_R2Y 0x05e0
+#define RK3399_WIN3_YUV2YUV_Y2R 0x0600
+#define RK3399_WIN3_YUV2YUV_3X3 0x0620
+#define RK3399_WIN3_YUV2YUV_R2Y 0x0640
+#define RK3399_WIN2_LUT_ADDR 0x1000
+#define RK3399_WIN3_LUT_ADDR 0x1400
+#define RK3399_HWC_LUT_ADDR 0x1800
+#define RK3399_CABC_GAMMA_LUT_ADDR 0x1c00
+#define RK3399_GAMMA_LUT_ADDR 0x2000
+/* rk3399 register definition end */
+
+/* rk3328 register definition end */
+#define RK3328_REG_CFG_DONE 0x00000000
+#define RK3328_VERSION_INFO 0x00000004
+#define RK3328_SYS_CTRL 0x00000008
+#define RK3328_SYS_CTRL1 0x0000000c
+#define RK3328_DSP_CTRL0 0x00000010
+#define RK3328_DSP_CTRL1 0x00000014
+#define RK3328_DSP_BG 0x00000018
+#define RK3328_AUTO_GATING_EN 0x0000003c
+#define RK3328_LINE_FLAG 0x00000040
+#define RK3328_VOP_STATUS 0x00000044
+#define RK3328_BLANKING_VALUE 0x00000048
+#define RK3328_WIN0_DSP_BG 0x00000050
+#define RK3328_WIN1_DSP_BG 0x00000054
+#define RK3328_DBG_PERF_LATENCY_CTRL0 0x000000c0
+#define RK3328_DBG_PERF_RD_MAX_LATENCY_NUM0 0x000000c4
+#define RK3328_DBG_PERF_RD_LATENCY_THR_NUM0 0x000000c8
+#define RK3328_DBG_PERF_RD_LATENCY_SAMP_NUM0 0x000000cc
+#define RK3328_INTR_EN0 0x000000e0
+#define RK3328_INTR_CLEAR0 0x000000e4
+#define RK3328_INTR_STATUS0 0x000000e8
+#define RK3328_INTR_RAW_STATUS0 0x000000ec
+#define RK3328_INTR_EN1 0x000000f0
+#define RK3328_INTR_CLEAR1 0x000000f4
+#define RK3328_INTR_STATUS1 0x000000f8
+#define RK3328_INTR_RAW_STATUS1 0x000000fc
+#define RK3328_WIN0_CTRL0 0x00000100
+#define RK3328_WIN0_CTRL1 0x00000104
+#define RK3328_WIN0_COLOR_KEY 0x00000108
+#define RK3328_WIN0_VIR 0x0000010c
+#define RK3328_WIN0_YRGB_MST 0x00000110
+#define RK3328_WIN0_CBR_MST 0x00000114
+#define RK3328_WIN0_ACT_INFO 0x00000118
+#define RK3328_WIN0_DSP_INFO 0x0000011c
+#define RK3328_WIN0_DSP_ST 0x00000120
+#define RK3328_WIN0_SCL_FACTOR_YRGB 0x00000124
+#define RK3328_WIN0_SCL_FACTOR_CBR 0x00000128
+#define RK3328_WIN0_SCL_OFFSET 0x0000012c
+#define RK3328_WIN0_SRC_ALPHA_CTRL 0x00000130
+#define RK3328_WIN0_DST_ALPHA_CTRL 0x00000134
+#define RK3328_WIN0_FADING_CTRL 0x00000138
+#define RK3328_WIN0_CTRL2 0x0000013c
+#define RK3328_DBG_WIN0_REG0 0x000001f0
+#define RK3328_DBG_WIN0_REG1 0x000001f4
+#define RK3328_DBG_WIN0_REG2 0x000001f8
+#define RK3328_DBG_WIN0_RESERVED 0x000001fc
+#define RK3328_WIN1_CTRL0 0x00000200
+#define RK3328_WIN1_CTRL1 0x00000204
+#define RK3328_WIN1_COLOR_KEY 0x00000208
+#define RK3328_WIN1_VIR 0x0000020c
+#define RK3328_WIN1_YRGB_MST 0x00000210
+#define RK3328_WIN1_CBR_MST 0x00000214
+#define RK3328_WIN1_ACT_INFO 0x00000218
+#define RK3328_WIN1_DSP_INFO 0x0000021c
+#define RK3328_WIN1_DSP_ST 0x00000220
+#define RK3328_WIN1_SCL_FACTOR_YRGB 0x00000224
+#define RK3328_WIN1_SCL_FACTOR_CBR 0x00000228
+#define RK3328_WIN1_SCL_OFFSET 0x0000022c
+#define RK3328_WIN1_SRC_ALPHA_CTRL 0x00000230
+#define RK3328_WIN1_DST_ALPHA_CTRL 0x00000234
+#define RK3328_WIN1_FADING_CTRL 0x00000238
+#define RK3328_WIN1_CTRL2 0x0000023c
+#define RK3328_DBG_WIN1_REG0 0x000002f0
+#define RK3328_DBG_WIN1_REG1 0x000002f4
+#define RK3328_DBG_WIN1_REG2 0x000002f8
+#define RK3328_DBG_WIN1_RESERVED 0x000002fc
+#define RK3328_WIN2_CTRL0 0x00000300
+#define RK3328_WIN2_CTRL1 0x00000304
+#define RK3328_WIN2_COLOR_KEY 0x00000308
+#define RK3328_WIN2_VIR 0x0000030c
+#define RK3328_WIN2_YRGB_MST 0x00000310
+#define RK3328_WIN2_CBR_MST 0x00000314
+#define RK3328_WIN2_ACT_INFO 0x00000318
+#define RK3328_WIN2_DSP_INFO 0x0000031c
+#define RK3328_WIN2_DSP_ST 0x00000320
+#define RK3328_WIN2_SCL_FACTOR_YRGB 0x00000324
+#define RK3328_WIN2_SCL_FACTOR_CBR 0x00000328
+#define RK3328_WIN2_SCL_OFFSET 0x0000032c
+#define RK3328_WIN2_SRC_ALPHA_CTRL 0x00000330
+#define RK3328_WIN2_DST_ALPHA_CTRL 0x00000334
+#define RK3328_WIN2_FADING_CTRL 0x00000338
+#define RK3328_WIN2_CTRL2 0x0000033c
+#define RK3328_DBG_WIN2_REG0 0x000003f0
+#define RK3328_DBG_WIN2_REG1 0x000003f4
+#define RK3328_DBG_WIN2_REG2 0x000003f8
+#define RK3328_DBG_WIN2_RESERVED 0x000003fc
+#define RK3328_WIN3_CTRL0 0x00000400
+#define RK3328_WIN3_CTRL1 0x00000404
+#define RK3328_WIN3_COLOR_KEY 0x00000408
+#define RK3328_WIN3_VIR 0x0000040c
+#define RK3328_WIN3_YRGB_MST 0x00000410
+#define RK3328_WIN3_CBR_MST 0x00000414
+#define RK3328_WIN3_ACT_INFO 0x00000418
+#define RK3328_WIN3_DSP_INFO 0x0000041c
+#define RK3328_WIN3_DSP_ST 0x00000420
+#define RK3328_WIN3_SCL_FACTOR_YRGB 0x00000424
+#define RK3328_WIN3_SCL_FACTOR_CBR 0x00000428
+#define RK3328_WIN3_SCL_OFFSET 0x0000042c
+#define RK3328_WIN3_SRC_ALPHA_CTRL 0x00000430
+#define RK3328_WIN3_DST_ALPHA_CTRL 0x00000434
+#define RK3328_WIN3_FADING_CTRL 0x00000438
+#define RK3328_WIN3_CTRL2 0x0000043c
+#define RK3328_DBG_WIN3_REG0 0x000004f0
+#define RK3328_DBG_WIN3_REG1 0x000004f4
+#define RK3328_DBG_WIN3_REG2 0x000004f8
+#define RK3328_DBG_WIN3_RESERVED 0x000004fc
+
+#define RK3328_HWC_CTRL0 0x00000500
+#define RK3328_HWC_CTRL1 0x00000504
+#define RK3328_HWC_MST 0x00000508
+#define RK3328_HWC_DSP_ST 0x0000050c
+#define RK3328_HWC_SRC_ALPHA_CTRL 0x00000510
+#define RK3328_HWC_DST_ALPHA_CTRL 0x00000514
+#define RK3328_HWC_FADING_CTRL 0x00000518
+#define RK3328_HWC_RESERVED1 0x0000051c
+#define RK3328_POST_DSP_HACT_INFO 0x00000600
+#define RK3328_POST_DSP_VACT_INFO 0x00000604
+#define RK3328_POST_SCL_FACTOR_YRGB 0x00000608
+#define RK3328_POST_RESERVED 0x0000060c
+#define RK3328_POST_SCL_CTRL 0x00000610
+#define RK3328_POST_DSP_VACT_INFO_F1 0x00000614
+#define RK3328_DSP_HTOTAL_HS_END 0x00000618
+#define RK3328_DSP_HACT_ST_END 0x0000061c
+#define RK3328_DSP_VTOTAL_VS_END 0x00000620
+#define RK3328_DSP_VACT_ST_END 0x00000624
+#define RK3328_DSP_VS_ST_END_F1 0x00000628
+#define RK3328_DSP_VACT_ST_END_F1 0x0000062c
+#define RK3328_BCSH_COLOR_BAR 0x00000640
+#define RK3328_BCSH_BCS 0x00000644
+#define RK3328_BCSH_H 0x00000648
+#define RK3328_BCSH_CTRL 0x0000064c
+#define RK3328_FRC_LOWER01_0 0x00000678
+#define RK3328_FRC_LOWER01_1 0x0000067c
+#define RK3328_FRC_LOWER10_0 0x00000680
+#define RK3328_FRC_LOWER10_1 0x00000684
+#define RK3328_FRC_LOWER11_0 0x00000688
+#define RK3328_FRC_LOWER11_1 0x0000068c
+#define RK3328_DBG_POST_REG0 0x000006e8
+#define RK3328_DBG_POST_RESERVED 0x000006ec
+#define RK3328_DBG_DATAO 0x000006f0
+#define RK3328_DBG_DATAO_2 0x000006f4
+
+/* sdr to hdr */
+#define RK3328_SDR2HDR_CTRL 0x00000700
+#define RK3328_EOTF_OETF_Y0 0x00000704
+#define RK3328_RESERVED0001 0x00000708
+#define RK3328_RESERVED0002 0x0000070c
+#define RK3328_EOTF_OETF_Y1 0x00000710
+#define RK3328_EOTF_OETF_Y64 0x0000080c
+#define RK3328_OETF_DX_DXPOW1 0x00000810
+#define RK3328_OETF_DX_DXPOW64 0x0000090c
+#define RK3328_OETF_XN1 0x00000910
+#define RK3328_OETF_XN63 0x00000a08
+
+/* hdr to sdr */
+#define RK3328_HDR2SDR_CTRL 0x00000a10
+#define RK3328_HDR2SDR_SRC_RANGE 0x00000a14
+#define RK3328_HDR2SDR_NORMFACEETF 0x00000a18
+#define RK3328_RESERVED0003 0x00000a1c
+#define RK3328_HDR2SDR_DST_RANGE 0x00000a20
+#define RK3328_HDR2SDR_NORMFACCGAMMA 0x00000a24
+#define RK3328_EETF_OETF_Y0 0x00000a28
+#define RK3328_SAT_Y0 0x00000a2c
+#define RK3328_EETF_OETF_Y1 0x00000a30
+#define RK3328_SAT_Y1 0x00000ab0
+#define RK3328_SAT_Y8 0x00000acc
+
+#define RK3328_HWC_LUT_ADDR 0x00000c00
+
+/* rk3036 register definition */
+#define RK3036_SYS_CTRL 0x00
+#define RK3036_DSP_CTRL0 0x04
+#define RK3036_DSP_CTRL1 0x08
+#define RK3036_INT_STATUS 0x10
+#define RK3036_ALPHA_CTRL 0x14
+#define RK3036_WIN0_COLOR_KEY 0x18
+#define RK3036_WIN1_COLOR_KEY 0x1c
+#define RK3036_WIN0_YRGB_MST 0x20
+#define RK3036_WIN0_CBR_MST 0x24
+#define RK3036_WIN1_VIR 0x28
+#define RK3036_AXI_BUS_CTRL 0x2c
+#define RK3036_WIN0_VIR 0x30
+#define RK3036_WIN0_ACT_INFO 0x34
+#define RK3036_WIN0_DSP_INFO 0x38
+#define RK3036_WIN0_DSP_ST 0x3c
+#define RK3036_WIN0_SCL_FACTOR_YRGB 0x40
+#define RK3036_WIN0_SCL_FACTOR_CBR 0x44
+#define RK3036_WIN0_SCL_OFFSET 0x48
+#define RK3036_HWC_MST 0x58
+#define RK3036_HWC_DSP_ST 0x5c
+#define RK3036_DSP_HTOTAL_HS_END 0x6c
+#define RK3036_DSP_HACT_ST_END 0x70
+#define RK3036_DSP_VTOTAL_VS_END 0x74
+#define RK3036_DSP_VACT_ST_END 0x78
+#define RK3036_DSP_VS_ST_END_F1 0x7c
+#define RK3036_DSP_VACT_ST_END_F1 0x80
+#define RK3036_GATHER_TRANSFER 0x84
+#define RK3036_VERSION_INFO 0x94
+#define RK3036_REG_CFG_DONE 0x90
+#define RK3036_WIN1_MST 0xa0
+#define RK3036_WIN1_ACT_INFO 0xb4
+#define RK3036_WIN1_DSP_INFO 0xb8
+#define RK3036_WIN1_DSP_ST 0xbc
+#define RK3036_WIN1_SCL_FACTOR_YRGB 0xc0
+#define RK3036_WIN1_SCL_OFFSET 0xc8
+#define RK3036_BCSH_CTRL 0xd0
+#define RK3036_BCSH_COLOR_BAR 0xd4
+#define RK3036_BCSH_BCS 0xd8
+#define RK3036_BCSH_H 0xdc
+#define RK3036_WIN1_LUT_ADDR 0x400
+#define RK3036_HWC_LUT_ADDR 0x800
+/* rk3036 register definition end */
+
+/* rk3126 register definition */
+#define RK3126_WIN1_MST 0x4c
+#define RK3126_WIN1_DSP_INFO 0x50
+#define RK3126_WIN1_DSP_ST 0x54
+/* rk3126 register definition end */
+
+/* px30 register definition */
+#define PX30_REG_CFG_DONE 0x00000
+#define PX30_VERSION 0x00004
+#define PX30_DSP_BG 0x00008
+#define PX30_MCU_CTRL 0x0000c
+#define PX30_SYS_CTRL0 0x00010
+#define PX30_SYS_CTRL1 0x00014
+#define PX30_SYS_CTRL2 0x00018
+#define PX30_DSP_CTRL0 0x00020
+#define PX30_DSP_CTRL2 0x00028
+#define PX30_VOP_STATUS 0x0002c
+#define PX30_LINE_FLAG 0x00030
+#define PX30_INTR_EN 0x00034
+#define PX30_INTR_CLEAR 0x00038
+#define PX30_INTR_STATUS 0x0003c
+#define PX30_WIN0_CTRL0 0x00050
+#define PX30_WIN0_CTRL1 0x00054
+#define PX30_WIN0_COLOR_KEY 0x00058
+#define PX30_WIN0_VIR 0x0005c
+#define PX30_WIN0_YRGB_MST0 0x00060
+#define PX30_WIN0_CBR_MST0 0x00064
+#define PX30_WIN0_ACT_INFO 0x00068
+#define PX30_WIN0_DSP_INFO 0x0006c
+#define PX30_WIN0_DSP_ST 0x00070
+#define PX30_WIN0_SCL_FACTOR_YRGB 0x00074
+#define PX30_WIN0_SCL_FACTOR_CBR 0x00078
+#define PX30_WIN0_SCL_OFFSET 0x0007c
+#define PX30_WIN0_ALPHA_CTRL 0x00080
+#define PX30_WIN1_CTRL0 0x00090
+#define PX30_WIN1_CTRL1 0x00094
+#define PX30_WIN1_VIR 0x00098
+#define PX30_WIN1_MST 0x000a0
+#define PX30_WIN1_DSP_INFO 0x000a4
+#define PX30_WIN1_DSP_ST 0x000a8
+#define PX30_WIN1_COLOR_KEY 0x000ac
+#define PX30_WIN1_ALPHA_CTRL 0x000bc
+#define PX30_HWC_CTRL0 0x000e0
+#define PX30_HWC_CTRL1 0x000e4
+#define PX30_HWC_MST 0x000e8
+#define PX30_HWC_DSP_ST 0x000ec
+#define PX30_HWC_ALPHA_CTRL 0x000f0
+#define PX30_DSP_HTOTAL_HS_END 0x00100
+#define PX30_DSP_HACT_ST_END 0x00104
+#define PX30_DSP_VTOTAL_VS_END 0x00108
+#define PX30_DSP_VACT_ST_END 0x0010c
+#define PX30_DSP_VS_ST_END_F1 0x00110
+#define PX30_DSP_VACT_ST_END_F1 0x00114
+#define PX30_BCSH_CTRL 0x00160
+#define PX30_BCSH_COL_BAR 0x00164
+#define PX30_BCSH_BCS 0x00168
+#define PX30_BCSH_H 0x0016c
+#define PX30_FRC_LOWER01_0 0x00170
+#define PX30_FRC_LOWER01_1 0x00174
+#define PX30_FRC_LOWER10_0 0x00178
+#define PX30_FRC_LOWER10_1 0x0017c
+#define PX30_FRC_LOWER11_0 0x00180
+#define PX30_FRC_LOWER11_1 0x00184
+#define PX30_MCU_RW_BYPASS_PORT 0x0018c
+#define PX30_WIN2_CTRL0 0x00190
+#define PX30_WIN2_CTRL1 0x00194
+#define PX30_WIN2_VIR0_1 0x00198
+#define PX30_WIN2_VIR2_3 0x0019c
+#define PX30_WIN2_MST0 0x001a0
+#define PX30_WIN2_DSP_INFO0 0x001a4
+#define PX30_WIN2_DSP_ST0 0x001a8
+#define PX30_WIN2_COLOR_KEY 0x001ac
+#define PX30_WIN2_ALPHA_CTRL 0x001bc
+#define PX30_BLANKING_VALUE 0x001f4
+#define PX30_FLAG_REG_FRM_VALID 0x001f8
+#define PX30_FLAG_REG 0x001fc
+#define PX30_HWC_LUT_ADDR 0x00600
+#define PX30_GAMMA_LUT_ADDR 0x00a00
+/* px30 register definition end */
+
+/* rk3188 register definition */
+#define RK3188_SYS_CTRL 0x00
+#define RK3188_DSP_CTRL0 0x04
+#define RK3188_DSP_CTRL1 0x08
+#define RK3188_INT_STATUS 0x10
+#define RK3188_WIN0_YRGB_MST0 0x20
+#define RK3188_WIN0_CBR_MST0 0x24
+#define RK3188_WIN0_YRGB_MST1 0x28
+#define RK3188_WIN0_CBR_MST1 0x2c
+#define RK3188_WIN_VIR 0x30
+#define RK3188_WIN0_ACT_INFO 0x34
+#define RK3188_WIN0_DSP_INFO 0x38
+#define RK3188_WIN0_DSP_ST 0x3c
+#define RK3188_WIN0_SCL_FACTOR_YRGB 0x40
+#define RK3188_WIN0_SCL_FACTOR_CBR 0x44
+#define RK3188_WIN1_MST 0x4c
+#define RK3188_WIN1_DSP_INFO 0x50
+#define RK3188_WIN1_DSP_ST 0x54
+#define RK3188_DSP_HTOTAL_HS_END 0x6c
+#define RK3188_DSP_HACT_ST_END 0x70
+#define RK3188_DSP_VTOTAL_VS_END 0x74
+#define RK3188_DSP_VACT_ST_END 0x78
+#define RK3188_REG_CFG_DONE 0x90
+/* rk3188 register definition end */
+
+/* rk3066 register definition */
+#define RK3066_SYS_CTRL0 0x00
+#define RK3066_SYS_CTRL1 0x04
+#define RK3066_DSP_CTRL0 0x08
+#define RK3066_DSP_CTRL1 0x0c
+#define RK3066_INT_STATUS 0x10
+#define RK3066_MCU_CTRL 0x14
+#define RK3066_BLEND_CTRL 0x18
+#define RK3066_WIN0_COLOR_KEY_CTRL 0x1c
+#define RK3066_WIN1_COLOR_KEY_CTRL 0x20
+#define RK3066_WIN2_COLOR_KEY_CTRL 0x24
+#define RK3066_WIN0_YRGB_MST0 0x28
+#define RK3066_WIN0_CBR_MST0 0x2c
+#define RK3066_WIN0_YRGB_MST1 0x30
+#define RK3066_WIN0_CBR_MST1 0x34
+#define RK3066_WIN0_VIR 0x38
+#define RK3066_WIN0_ACT_INFO 0x3c
+#define RK3066_WIN0_DSP_INFO 0x40
+#define RK3066_WIN0_DSP_ST 0x44
+#define RK3066_WIN0_SCL_FACTOR_YRGB 0x48
+#define RK3066_WIN0_SCL_FACTOR_CBR 0x4c
+#define RK3066_WIN0_SCL_OFFSET 0x50
+#define RK3066_WIN1_YRGB_MST 0x54
+#define RK3066_WIN1_CBR_MST 0x58
+#define RK3066_WIN1_VIR 0x5c
+#define RK3066_WIN1_ACT_INFO 0x60
+#define RK3066_WIN1_DSP_INFO 0x64
+#define RK3066_WIN1_DSP_ST 0x68
+#define RK3066_WIN1_SCL_FACTOR_YRGB 0x6c
+#define RK3066_WIN1_SCL_FACTOR_CBR 0x70
+#define RK3066_WIN1_SCL_OFFSET 0x74
+#define RK3066_WIN2_MST 0x78
+#define RK3066_WIN2_VIR 0x7c
+#define RK3066_WIN2_DSP_INFO 0x80
+#define RK3066_WIN2_DSP_ST 0x84
+#define RK3066_HWC_MST 0x88
+#define RK3066_HWC_DSP_ST 0x8c
+#define RK3066_HWC_COLOR_LUT0 0x90
+#define RK3066_HWC_COLOR_LUT1 0x94
+#define RK3066_HWC_COLOR_LUT2 0x98
+#define RK3066_DSP_HTOTAL_HS_END 0x9c
+#define RK3066_DSP_HACT_ST_END 0xa0
+#define RK3066_DSP_VTOTAL_VS_END 0xa4
+#define RK3066_DSP_VACT_ST_END 0xa8
+#define RK3066_DSP_VS_ST_END_F1 0xac
+#define RK3066_DSP_VACT_ST_END_F1 0xb0
+#define RK3066_REG_CFG_DONE 0xc0
+#define RK3066_MCU_BYPASS_WPORT 0x100
+#define RK3066_MCU_BYPASS_RPORT 0x200
+#define RK3066_WIN2_LUT_ADDR 0x400
+#define RK3066_DSP_LUT_ADDR 0x800
+/* rk3066 register definition end */
+
+#endif /* _ROCKCHIP_VOP_REG_H */