@@ -315,6 +315,8 @@ source "drivers/gpu/drm/tve200/Kconfig"
source "drivers/gpu/drm/xen/Kconfig"
+source "drivers/gpu/drm/tidss/Kconfig"
+
# Keep legacy drivers last
menuconfig DRM_LEGACY
@@ -107,3 +107,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
obj-$(CONFIG_DRM_PL111) += pl111/
obj-$(CONFIG_DRM_TVE200) += tve200/
obj-$(CONFIG_DRM_XEN) += xen/
+obj-$(CONFIG_DRM_TIDSS) += tidss/
new file mode 100644
@@ -0,0 +1,14 @@
+config DRM_TIDSS
+ tristate "DRM Support for TI Keystone"
+ depends on DRM && OF
+ depends on ARM || ARM64 || COMPILE_TEST
+ select DRM_KMS_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_GEM_CMA_HELPER
+ select VIDEOMODE_HELPERS
+ help
+ The TI Keystone family SoCs introduced a new generation of
+ Display SubSystem. They are called DSS6 and DSS7 and at the
+ time of writing this these DSSs are found on 66AK2Gx and
+ AM6x SoCs. Set this to Y or M to add display support for
+ TI Keystone family platforms.
new file mode 100644
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+tidss-y := tidss_crtc.o \
+ tidss_dispc6.o \
+ tidss_drv.o \
+ tidss_encoder.o \
+ tidss_irq.o \
+ tidss_kms.o \
+ tidss_plane.o
+
+obj-$(CONFIG_DRM_TIDSS) += tidss.o
new file mode 100644
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_dispc.h"
+#include "tidss_drv.h"
+#include "tidss_irq.h"
+
+/* -----------------------------------------------------------------------------
+ * Page Flip
+ */
+
+static void tidss_crtc_finish_page_flip(struct tidss_crtc *tcrtc)
+{
+ struct drm_device *ddev = tcrtc->crtc.dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ struct drm_pending_vblank_event *event;
+ unsigned long flags;
+ bool busy;
+
+ /*
+ * New settings are taken into use at VFP, and GO bit is cleared at
+ * the same time. This happens before the vertical blank interrupt.
+ * So there is a small change that the driver sets GO bit after VFP, but
+ * before vblank, and we have to check for that case here.
+ */
+ busy = tidss->dispc_ops->vp_go_busy(tidss->dispc, tcrtc->hw_videoport);
+ if (busy)
+ return;
+
+ spin_lock_irqsave(&ddev->event_lock, flags);
+
+ event = tcrtc->event;
+ tcrtc->event = NULL;
+
+ if (!event) {
+ spin_unlock_irqrestore(&ddev->event_lock, flags);
+ return;
+ }
+
+ drm_crtc_send_vblank_event(&tcrtc->crtc, event);
+
+ spin_unlock_irqrestore(&ddev->event_lock, flags);
+
+ drm_crtc_vblank_put(&tcrtc->crtc);
+}
+
+void tidss_crtc_vblank_irq(struct drm_crtc *crtc)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+ drm_crtc_handle_vblank(crtc);
+
+ tidss_crtc_finish_page_flip(tcrtc);
+}
+
+void tidss_crtc_framedone_irq(struct drm_crtc *crtc)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+ complete(&tcrtc->framedone_completion);
+}
+
+void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+ dev_err_ratelimited(crtc->dev->dev, "CRTC%u SYNC LOST: (irq %llx)\n",
+ tcrtc->hw_videoport, irqstatus);
+}
+
+/* -----------------------------------------------------------------------------
+ * CRTC Functions
+ */
+
+static int tidss_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ int r;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ if (!state->enable)
+ return 0;
+
+ r = tidss->dispc_ops->vp_check(tidss->dispc, tcrtc->hw_videoport,
+ state);
+
+ return r;
+}
+
+static void tidss_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_crtc_state)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+
+ dev_dbg(ddev->dev, "%s, crtc enabled %d, event %p\n",
+ __func__, tcrtc->enabled, crtc->state->event);
+
+ /* Only flush the CRTC if it is currently enabled. */
+ if (!tcrtc->enabled)
+ return;
+
+ /* If the GO bit is stuck we better quit here. */
+ if (WARN_ON(tidss->dispc_ops->vp_go_busy(tidss->dispc,
+ tcrtc->hw_videoport)))
+ return;
+
+ /* We should have event if CRTC is enabled through out this commit. */
+ WARN_ON(!crtc->state->event);
+
+ tidss->dispc_ops->vp_setup(tidss->dispc,
+ tcrtc->hw_videoport,
+ crtc->state);
+
+ WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+ spin_lock_irq(&ddev->event_lock);
+ tidss->dispc_ops->vp_go(tidss->dispc, tcrtc->hw_videoport);
+
+ if (crtc->state->event) {
+ tcrtc->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+
+ spin_unlock_irq(&ddev->event_lock);
+}
+
+static void tidss_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ int r;
+
+ dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event);
+
+ tidss->dispc_ops->runtime_get(tidss->dispc);
+
+ r = tidss->dispc_ops->vp_set_clk_rate(tidss->dispc, tcrtc->hw_videoport,
+ mode->clock * 1000);
+ if (r != 0)
+ return;
+
+ r = tidss->dispc_ops->vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+ if (r != 0)
+ return;
+
+ tidss->dispc_ops->vp_setup(tidss->dispc, tcrtc->hw_videoport,
+ crtc->state);
+
+ /* Turn vertical blanking interrupt reporting on. */
+ drm_crtc_vblank_on(crtc);
+
+ if (tidss->dispc_ops->vp_prepare)
+ tidss->dispc_ops->vp_prepare(tidss->dispc, tcrtc->hw_videoport,
+ crtc->state);
+
+ tcrtc->enabled = true;
+
+ tidss->dispc_ops->vp_enable(tidss->dispc, tcrtc->hw_videoport,
+ crtc->state);
+
+ spin_lock_irq(&ddev->event_lock);
+
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+
+ spin_unlock_irq(&ddev->event_lock);
+}
+
+static void tidss_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+
+ dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event);
+
+ reinit_completion(&tcrtc->framedone_completion);
+
+ tidss->dispc_ops->vp_disable(tidss->dispc, tcrtc->hw_videoport);
+
+ if (!wait_for_completion_timeout(&tcrtc->framedone_completion,
+ msecs_to_jiffies(500)))
+ dev_err(tidss->dev, "Timeout waiting for framedone on crtc %d",
+ tcrtc->hw_videoport);
+
+ if (tidss->dispc_ops->vp_unprepare)
+ tidss->dispc_ops->vp_unprepare(tidss->dispc,
+ tcrtc->hw_videoport);
+
+ spin_lock_irq(&ddev->event_lock);
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&ddev->event_lock);
+
+ tcrtc->enabled = false;
+
+ drm_crtc_vblank_off(crtc);
+
+ tidss->dispc_ops->vp_disable_clk(tidss->dispc, tcrtc->hw_videoport);
+
+ tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static
+enum drm_mode_status tidss_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+
+ return tidss->dispc_ops->vp_mode_valid(tidss->dispc,
+ tcrtc->hw_videoport,
+ mode);
+}
+
+static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+ .atomic_check = tidss_crtc_atomic_check,
+ .atomic_flush = tidss_crtc_atomic_flush,
+ .atomic_enable = tidss_crtc_atomic_enable,
+ .atomic_disable = tidss_crtc_atomic_disable,
+
+ .mode_valid = tidss_crtc_mode_valid,
+};
+
+static void tidss_crtc_reset(struct drm_crtc *crtc)
+{
+ struct tidss_crtc_state *tcrtc;
+
+ if (crtc->state)
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+ kfree(crtc->state);
+
+ tcrtc = kzalloc(sizeof(*tcrtc), GFP_KERNEL);
+ if (!tcrtc) {
+ crtc->state = NULL;
+ return;
+ }
+
+ crtc->state = &tcrtc->base;
+ crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *
+tidss_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct tidss_crtc_state *state, *current_state;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ current_state = to_tidss_crtc_state(crtc->state);
+
+ state = kmalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+ state->bus_format = current_state->bus_format;
+ state->bus_flags = current_state->bus_flags;
+
+ return &state->base;
+}
+
+
+static int tidss_crtc_atomic_set_property(struct drm_crtc *crtc,
+ struct drm_crtc_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ return -EINVAL;
+}
+
+static int tidss_crtc_atomic_get_property(struct drm_crtc *crtc,
+ const struct drm_crtc_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ return -EINVAL;
+}
+
+static int tidss_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ tidss->dispc_ops->runtime_get(tidss->dispc);
+
+ tidss_irq_enable_vblank(crtc);
+
+ return 0;
+}
+
+static void tidss_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ tidss_irq_disable_vblank(crtc);
+
+ tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static const struct drm_crtc_funcs crtc_funcs = {
+ .reset = tidss_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_duplicate_state = tidss_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .atomic_set_property = tidss_crtc_atomic_set_property,
+ .atomic_get_property = tidss_crtc_atomic_get_property,
+ .enable_vblank = tidss_crtc_enable_vblank,
+ .disable_vblank = tidss_crtc_disable_vblank,
+};
+
+static void tidss_crtc_install_properties(struct tidss_device *tidss,
+ const struct tidss_vp_feat *vp_feat,
+ struct drm_crtc *crtc)
+{
+}
+
+struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, u32 hw_videoport,
+ struct drm_plane *primary)
+{
+ struct tidss_crtc *tcrtc;
+ struct drm_crtc *crtc;
+ const struct tidss_vp_feat *vp_feat;
+ unsigned int gamma_lut_size = 0;
+ int ret;
+
+ vp_feat = tidss->dispc_ops->vp_feat(tidss->dispc, hw_videoport);
+
+ tcrtc = devm_kzalloc(tidss->dev, sizeof(*tcrtc), GFP_KERNEL);
+ if (!tcrtc)
+ return ERR_PTR(-ENOMEM);
+
+ tcrtc->hw_videoport = hw_videoport;
+ init_completion(&tcrtc->framedone_completion);
+
+ crtc = &tcrtc->crtc;
+
+ ret = drm_crtc_init_with_planes(tidss->ddev, crtc, primary,
+ NULL, &crtc_funcs, NULL);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+ /*
+ * The dispc API adapts to what ever size we ask from it no
+ * matter what HW supports. X-server assumes 256 element gamma
+ * tables so lets use that. Size of HW gamma table size is
+ * found from struct tidss_vp_feat that is extracted with
+ * dispc_vp_feats(). If gamma_size is 0 gamma table is not
+ * supported.
+ */
+ if (vp_feat->color.gamma_size)
+ gamma_lut_size = 256;
+
+ drm_crtc_enable_color_mgmt(crtc, 0, vp_feat->color.has_ctm,
+ gamma_lut_size);
+ if (gamma_lut_size)
+ drm_mode_crtc_set_gamma_size(crtc, gamma_lut_size);
+
+ tidss_crtc_install_properties(tidss, vp_feat, crtc);
+
+ return tcrtc;
+}
new file mode 100644
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_CRTC_H__
+#define __TIDSS_CRTC_H__
+
+#include <drm/drm_crtc.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+
+#include "tidss_dispc.h"
+
+#define to_tidss_crtc(c) container_of((c), struct tidss_crtc, crtc)
+
+struct tidss_crtc {
+ struct drm_crtc crtc;
+
+ u32 hw_videoport;
+
+ struct drm_pending_vblank_event *event;
+
+ /* has crtc_atomic_enable been called? */
+ bool enabled;
+
+ struct completion framedone_completion;
+};
+
+#define to_tidss_crtc_state(x) container_of(x, struct tidss_crtc_state, base)
+
+struct tidss_crtc_state {
+ /* Must be first. */
+ struct drm_crtc_state base;
+
+ u32 bus_format;
+ u32 bus_flags;
+};
+
+struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, u32 hw_videoport,
+ struct drm_plane *primary);
+
+
+void tidss_crtc_vblank_irq(struct drm_crtc *crtc);
+void tidss_crtc_framedone_irq(struct drm_crtc *crtc);
+void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus);
+
+#endif
new file mode 100644
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC_H__
+#define __TIDSS_DISPC_H__
+
+struct dispc_device;
+struct tidss_device;
+
+struct drm_crtc_state;
+
+#define DSS_MAX_CHANNELS 8
+#define DSS_MAX_PLANES 8
+
+/*
+ * Based on the above 2 defines the bellow defines describe following
+ * u64 IRQ bits:
+ *
+ * bit group |dev |mrg0|mrg1|mrg2|mrg3|mrg4|mrg5|mrg6|mrg7|plane 0-7|<unused> |
+ * bit use |Dfou|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|UUUU|UUUU| | | | | |
+ * bit number|0-3 |4-7 |8-11| 12-35 | 36-43 | 44-63 |
+ *
+ * device bits: D = OCP error
+ * WB bits: f = frame done wb, o = wb buffer overflow,
+ * u = wb buffer uncomplete
+ * vp bits: F = frame done, E = vsync even, O = vsync odd, L = sync lost
+ * plane bits: U = fifo underflow
+ */
+
+#define DSS_IRQ_DEVICE_OCP_ERR BIT_ULL(0)
+
+#define DSS_IRQ_DEVICE_FRAMEDONEWB BIT_ULL(1)
+#define DSS_IRQ_DEVICE_WBBUFFEROVERFLOW BIT_ULL(2)
+#define DSS_IRQ_DEVICE_WBUNCOMPLETEERROR BIT_ULL(3)
+#define DSS_IRQ_DEVICE_WB_MASK GENMASK_ULL(3, 1)
+
+#define DSS_IRQ_VP_BIT_N(ch, bit) (4 + 4 * (ch) + (bit))
+#define DSS_IRQ_PLANE_BIT_N(plane, bit) \
+ (DSS_IRQ_VP_BIT_N(DSS_MAX_CHANNELS, 0) + 1 * (plane) + (bit))
+
+#define DSS_IRQ_VP_BIT(ch, bit) BIT_ULL(DSS_IRQ_VP_BIT_N((ch), (bit)))
+#define DSS_IRQ_PLANE_BIT(plane, bit) \
+ BIT_ULL(DSS_IRQ_PLANE_BIT_N((plane), (bit)))
+
+#define DSS_IRQ_VP_MASK(ch) \
+ GENMASK_ULL(DSS_IRQ_VP_BIT_N((ch), 3), DSS_IRQ_VP_BIT_N((ch), 0))
+#define DSS_IRQ_PLANE_MASK(plane) \
+ GENMASK_ULL(DSS_IRQ_PLANE_BIT_N((plane), 0), \
+ DSS_IRQ_PLANE_BIT_N((plane), 0))
+
+#define DSS_IRQ_VP_FRAME_DONE(ch) DSS_IRQ_VP_BIT((ch), 0)
+#define DSS_IRQ_VP_VSYNC_EVEN(ch) DSS_IRQ_VP_BIT((ch), 1)
+#define DSS_IRQ_VP_VSYNC_ODD(ch) DSS_IRQ_VP_BIT((ch), 2)
+#define DSS_IRQ_VP_SYNC_LOST(ch) DSS_IRQ_VP_BIT((ch), 3)
+
+#define DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane) DSS_IRQ_PLANE_BIT((plane), 0)
+
+struct tidss_vp_feat {
+ struct tidss_vp_color_feat {
+ u32 gamma_size;
+ bool has_ctm;
+ } color;
+};
+
+struct tidss_plane_feat {
+ struct tidss_plane_color_feat {
+ u32 encodings;
+ u32 ranges;
+ enum drm_color_encoding default_encoding;
+ enum drm_color_range default_range;
+ } color;
+ struct tidss_plane_blend_feat {
+ bool global_alpha;
+ } blend;
+};
+
+struct tidss_dispc_ops {
+ u64 (*read_and_clear_irqstatus)(struct dispc_device *dispc);
+ void (*write_irqenable)(struct dispc_device *dispc, u64 enable);
+
+ u32 (*get_memory_bandwidth_limit)(struct dispc_device *dispc);
+
+ int (*get_num_vps)(struct dispc_device *dispc);
+ const char *(*vp_name)(struct dispc_device *dispc,
+ u32 hw_videoport);
+ const struct tidss_vp_feat *(*vp_feat)(struct dispc_device *dispc,
+ u32 hw_videoport);
+ void (*vp_prepare)(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state);
+ void (*vp_enable)(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state);
+ void (*vp_disable)(struct dispc_device *dispc, u32 hw_videoport);
+ void (*vp_unprepare)(struct dispc_device *dispc, u32 hw_videoport);
+ bool (*vp_go_busy)(struct dispc_device *dispc,
+ u32 hw_videoport);
+ void (*vp_go)(struct dispc_device *dispc, u32 hw_videoport);
+ enum drm_mode_status (*vp_mode_valid)(struct dispc_device *dispc,
+ u32 hw_videoport,
+ const struct drm_display_mode *mode);
+ int (*vp_check)(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state);
+ void (*vp_setup)(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state);
+
+ int (*vp_set_clk_rate)(struct dispc_device *dispc,
+ u32 hw_videoport, unsigned long rate);
+ int (*vp_enable_clk)(struct dispc_device *dispc, u32 hw_videoport);
+ void (*vp_disable_clk)(struct dispc_device *dispc, u32 hw_videoport);
+
+ int (*get_num_planes)(struct dispc_device *dispc);
+ const char *(*plane_name)(struct dispc_device *dispc,
+ u32 hw_plane);
+ const struct tidss_plane_feat *(*plane_feat)(struct dispc_device *dispc,
+ u32 hw_plane);
+ int (*plane_enable)(struct dispc_device *dispc, u32 hw_plane,
+ bool enable);
+ int (*plane_check)(struct dispc_device *dispc, u32 hw_plane,
+ const struct drm_plane_state *state,
+ u32 hw_videoport);
+ int (*plane_setup)(struct dispc_device *dispc, u32 hw_plane,
+ const struct drm_plane_state *state,
+ u32 hw_videoport);
+
+ int (*runtime_get)(struct dispc_device *dispc);
+ void (*runtime_put)(struct dispc_device *dispc);
+
+ int (*runtime_suspend)(struct dispc_device *dispc);
+ int (*runtime_resume)(struct dispc_device *dispc);
+
+ void (*remove)(struct dispc_device *dispc);
+
+ int (*modeset_init)(struct dispc_device *dispc);
+};
+
+int dispc6_init(struct tidss_device *tidss);
+
+#endif
new file mode 100644
@@ -0,0 +1,1507 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_plane.h"
+
+#include "tidss_dispc6.h"
+
+static const struct {
+ u32 fmt;
+ u32 port_width;
+} dispc6_bus_formats[] = {
+ { MEDIA_BUS_FMT_RGB444_1X12, 12 },
+ { MEDIA_BUS_FMT_RGB565_1X16, 16 },
+ { MEDIA_BUS_FMT_RGB666_1X18, 18 },
+ { MEDIA_BUS_FMT_RGB888_1X24, 24 },
+};
+
+/*
+ * TRM gives bitfields as start:end, where start is the higher bit
+ * number. For example 7:0
+ */
+
+#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end))
+#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
+#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end))
+#define FLD_MOD(orig, val, start, end) \
+ (((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end))
+
+
+#define REG_GET(dispc, idx, start, end) \
+ FLD_GET(dispc6_read(dispc, idx), start, end)
+
+#define REG_FLD_MOD(dispc, idx, val, start, end) \
+ dispc6_write(dispc, idx, FLD_MOD(dispc6_read(dispc, idx), val, start, end))
+
+#define VID_REG_GET(dispc, plane, idx, start, end) \
+ FLD_GET(dispc6_vid_read(dispc, plane, idx), start, end)
+
+#define VID_REG_FLD_MOD(dispc, plane, idx, val, start, end) \
+ dispc6_vid_write(dispc, plane, idx, FLD_MOD(dispc6_vid_read(dispc, plane, idx), val, start, end))
+
+
+#define VP_REG_GET(dispc, vp, idx, start, end) \
+ FLD_GET(dispc6_vp_read(dispc, vp, idx), start, end)
+
+#define VP_REG_FLD_MOD(dispc, vp, idx, val, start, end) \
+ dispc6_vp_write(dispc, vp, idx, FLD_MOD(dispc6_vp_read(dispc, vp, idx), val, start, end))
+
+#define DISPC6_GAMMA_TABLE_SIZE 256
+
+struct dispc_features {
+ /* XXX should these come from the .dts? Min pclk is not feature of DSS IP */
+ unsigned long min_pclk;
+ unsigned long max_pclk;
+};
+
+/* Note: 9MHz is a special allowed case, and is handled separately in the code */
+static const struct dispc_features k2g_dispc_feats = {
+ .min_pclk = 43750000,
+ .max_pclk = 150000000,
+};
+
+static const struct of_device_id dispc6_of_match[] = {
+ { .compatible = "ti,k2g-dss", .data = &k2g_dispc_feats, },
+ {},
+};
+
+struct dispc_device {
+ struct device *dev;
+
+ void __iomem *base_cfg;
+ void __iomem *base_common;
+ void __iomem *base_vid1;
+ void __iomem *base_ovr1;
+ void __iomem *base_vp1;
+
+ const struct dispc_features *feat;
+
+ struct clk *fclk;
+ struct clk *vp_clk;
+
+ bool is_enabled;
+
+ u32 gamma_table[DISPC6_GAMMA_TABLE_SIZE];
+
+ struct tidss_device *tidss;
+};
+
+static void dispc6_write(struct dispc_device *dispc, u16 reg, u32 val)
+{
+ iowrite32(val, dispc->base_common + reg);
+}
+
+static u32 dispc6_read(struct dispc_device *dispc, u16 reg)
+{
+ return ioread32(dispc->base_common + reg);
+}
+
+static void dispc6_vid_write(struct dispc_device *dispc,
+ u32 hw_plane, u16 reg, u32 val)
+{
+ void __iomem *base = dispc->base_vid1;
+
+ iowrite32(val, base + reg);
+}
+
+static u32 dispc6_vid_read(struct dispc_device *dispc,
+ u32 hw_plane, u16 reg)
+{
+ void __iomem *base = dispc->base_vid1;
+
+ return ioread32(base + reg);
+}
+
+static void dispc6_ovr_write(struct dispc_device *dispc,
+ u32 hw_videoport, u16 reg, u32 val)
+{
+ void __iomem *base = dispc->base_ovr1;
+
+ iowrite32(val, base + reg);
+}
+
+__maybe_unused
+static u32 dispc6_ovr_read(struct dispc_device *dispc,
+ u32 hw_videoport, u16 reg)
+{
+ void __iomem *base = dispc->base_ovr1;
+
+ return ioread32(base + reg);
+}
+
+static void dispc6_vp_write(struct dispc_device *dispc,
+ u32 hw_videoport, u16 reg, u32 val)
+{
+ void __iomem *base = dispc->base_vp1;
+
+ iowrite32(val, base + reg);
+}
+
+static u32 dispc6_vp_read(struct dispc_device *dispc,
+ u32 hw_videoport, u16 reg)
+{
+ void __iomem *base = dispc->base_vp1;
+
+ return ioread32(base + reg);
+}
+
+static int dispc6_runtime_get(struct dispc_device *dispc)
+{
+ int r;
+
+ dev_dbg(dispc->dev, "dispc_runtime_get\n");
+
+ r = pm_runtime_get_sync(dispc->dev);
+ WARN_ON(r < 0);
+ return r < 0 ? r : 0;
+}
+
+static void dispc6_runtime_put(struct dispc_device *dispc)
+{
+ int r;
+
+ dev_dbg(dispc->dev, "dispc_runtime_put\n");
+
+ r = pm_runtime_put_sync(dispc->dev);
+ WARN_ON(r < 0);
+}
+
+static u64 dispc6_vp_irq_from_raw(u32 stat)
+{
+ u32 vp = 0;
+ u64 vp_stat = 0;
+
+ if (stat & BIT(0))
+ vp_stat |= DSS_IRQ_VP_FRAME_DONE(vp);
+ if (stat & BIT(1))
+ vp_stat |= DSS_IRQ_VP_VSYNC_EVEN(vp);
+ if (stat & BIT(2))
+ vp_stat |= DSS_IRQ_VP_VSYNC_ODD(vp);
+ if (stat & BIT(4))
+ vp_stat |= DSS_IRQ_VP_SYNC_LOST(vp);
+
+ return vp_stat;
+}
+
+static u32 dispc6_vp_irq_to_raw(u64 vpstat)
+{
+ u32 vp = 0;
+ u32 stat = 0;
+
+ if (vpstat & DSS_IRQ_VP_FRAME_DONE(vp))
+ stat |= BIT(0);
+ if (vpstat & DSS_IRQ_VP_VSYNC_EVEN(vp))
+ stat |= BIT(1);
+ if (vpstat & DSS_IRQ_VP_VSYNC_ODD(vp))
+ stat |= BIT(2);
+ if (vpstat & DSS_IRQ_VP_SYNC_LOST(vp))
+ stat |= BIT(4);
+
+ return stat;
+}
+
+static u64 dispc6_vid_irq_from_raw(u32 stat)
+{
+ u32 plane = 0;
+ u64 vid_stat = 0;
+
+ if (stat & BIT(0))
+ vid_stat |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane);
+
+ return vid_stat;
+}
+
+static u32 dispc6_vid_irq_to_raw(u64 vidstat)
+{
+ u32 plane = 0;
+ u32 stat = 0;
+
+ if (vidstat & DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane))
+ stat |= BIT(0);
+
+ return stat;
+}
+
+
+static u64 dispc6_vp_read_irqstatus(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQSTATUS);
+
+ return dispc6_vp_irq_from_raw(stat);
+}
+
+static void dispc6_vp_write_irqstatus(struct dispc_device *dispc,
+ u32 hw_videoport,
+ u64 vpstat)
+{
+ u32 stat = dispc6_vp_irq_to_raw(vpstat);
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQSTATUS, stat);
+}
+
+static u64 dispc6_vid_read_irqstatus(struct dispc_device *dispc,
+ u32 hw_plane)
+{
+ u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQSTATUS);
+
+ return dispc6_vid_irq_from_raw(stat);
+}
+
+static void dispc6_vid_write_irqstatus(struct dispc_device *dispc,
+ u32 hw_plane,
+ u64 vidstat)
+{
+ u32 stat = dispc6_vid_irq_to_raw(vidstat);
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQSTATUS, stat);
+}
+
+
+static u64 dispc6_vp_read_irqenable(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQENABLE);
+
+ return dispc6_vp_irq_from_raw(stat);
+}
+
+static void dispc6_vp_write_irqenable(struct dispc_device *dispc,
+ u32 hw_videoport,
+ u64 vpstat)
+{
+ u32 stat = dispc6_vp_irq_to_raw(vpstat);
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQENABLE, stat);
+}
+
+static u64 dispc6_vid_read_irqenable(struct dispc_device *dispc,
+ u32 hw_plane)
+{
+ u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQENABLE);
+
+ return dispc6_vid_irq_from_raw(stat);
+}
+
+static void dispc6_vid_write_irqenable(struct dispc_device *dispc,
+ u32 hw_plane,
+ u64 vidstat)
+{
+ u32 stat = dispc6_vid_irq_to_raw(vidstat);
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQENABLE, stat);
+}
+
+
+static void dispc6_clear_irqstatus(struct dispc_device *dispc, u64 mask)
+{
+ dispc6_vp_write_irqstatus(dispc, 0, mask);
+ dispc6_vid_write_irqstatus(dispc, 0, mask);
+}
+
+static u64 dispc6_read_and_clear_irqstatus(struct dispc_device *dispc)
+{
+ u64 stat = 0;
+
+ /* always clear the top level irqstatus */
+ dispc6_write(dispc, DISPC_IRQSTATUS,
+ dispc6_read(dispc, DISPC_IRQSTATUS));
+
+ stat |= dispc6_vp_read_irqstatus(dispc, 0);
+ stat |= dispc6_vid_read_irqstatus(dispc, 0);
+
+ dispc6_clear_irqstatus(dispc, stat);
+
+ return stat;
+}
+
+static u64 dispc6_read_irqenable(struct dispc_device *dispc)
+{
+ u64 stat = 0;
+
+ stat |= dispc6_vp_read_irqenable(dispc, 0);
+ stat |= dispc6_vid_read_irqenable(dispc, 0);
+
+ return stat;
+}
+
+static void dispc6_write_irqenable(struct dispc_device *dispc, u64 mask)
+{
+ u64 old_mask = dispc6_read_irqenable(dispc);
+
+ /* clear the irqstatus for newly enabled irqs */
+ dispc6_clear_irqstatus(dispc, (mask ^ old_mask) & mask);
+
+ dispc6_vp_write_irqenable(dispc, 0, mask);
+ dispc6_vid_write_irqenable(dispc, 0, mask);
+
+ dispc6_write(dispc, DISPC_IRQENABLE_SET, (1 << 0) | (1 << 7));
+
+ /* flush posted write */
+ dispc6_read_irqenable(dispc);
+}
+
+static void dispc6_set_num_datalines(struct dispc_device *dispc,
+ u32 hw_videoport, int num_lines)
+{
+ int v;
+
+ switch (num_lines) {
+ case 12:
+ v = 0; break;
+ case 16:
+ v = 1; break;
+ case 18:
+ v = 2; break;
+ case 24:
+ v = 3; break;
+ case 30:
+ v = 4; break;
+ case 36:
+ v = 5; break;
+ default:
+ WARN_ON(1);
+ v = 3;
+ break;
+ }
+
+ VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, v, 10, 8);
+}
+
+static void dispc6_vp_enable(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state)
+{
+ const struct drm_display_mode *mode = &state->adjusted_mode;
+ const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+ bool align, onoff, rf, ieo, ipc, ihs, ivs;
+ unsigned int i;
+ u32 port_width;
+ u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+ for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) {
+ if (dispc6_bus_formats[i].fmt != tstate->bus_format)
+ continue;
+
+ port_width = dispc6_bus_formats[i].port_width;
+ break;
+ }
+
+ if (WARN_ON(i == ARRAY_SIZE(dispc6_bus_formats)))
+ return;
+
+ dispc6_set_num_datalines(dispc, hw_videoport, port_width);
+
+ hfp = mode->hsync_start - mode->hdisplay;
+ hsw = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
+
+ vfp = mode->vsync_start - mode->vdisplay;
+ vsw = mode->vsync_end - mode->vsync_start;
+ vbp = mode->vtotal - mode->vsync_end;
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_H,
+ FLD_VAL(hsw - 1, 7, 0) |
+ FLD_VAL(hfp - 1, 19, 8) |
+ FLD_VAL(hbp - 1, 31, 20));
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_V,
+ FLD_VAL(vsw - 1, 7, 0) |
+ FLD_VAL(vfp, 19, 8) |
+ FLD_VAL(vbp, 31, 20));
+
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ ivs = true;
+ else
+ ivs = false;
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ ihs = true;
+ else
+ ihs = false;
+
+ if (tstate->bus_flags & DRM_BUS_FLAG_DE_LOW)
+ ieo = true;
+ else
+ ieo = false;
+
+ if (tstate->bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+ ipc = true;
+ else
+ ipc = false;
+
+ /* always use the 'rf' setting */
+ onoff = true;
+
+ if (tstate->bus_flags & DRM_BUS_FLAG_SYNC_NEGEDGE)
+ rf = false;
+ else
+ rf = true;
+
+ /* always use aligned syncs */
+ align = true;
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_POL_FREQ,
+ FLD_VAL(align, 18, 18) |
+ FLD_VAL(onoff, 17, 17) |
+ FLD_VAL(rf, 16, 16) |
+ FLD_VAL(ieo, 15, 15) |
+ FLD_VAL(ipc, 14, 14) |
+ FLD_VAL(ihs, 13, 13) |
+ FLD_VAL(ivs, 12, 12));
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN,
+ FLD_VAL(mode->hdisplay - 1, 11, 0) |
+ FLD_VAL(mode->vdisplay - 1, 27, 16));
+
+ VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 0, 0);
+}
+
+static void dispc6_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
+{
+ VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0, 0, 0);
+}
+
+static bool dispc6_vp_go_busy(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ return VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL, 5, 5);
+}
+
+static void dispc6_vp_go(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 5, 5);
+}
+
+static u16 c8_to_c12(u8 c8)
+{
+ u16 c12;
+
+ c12 = c8 << 4;
+
+ /* Replication logic: Copy c8 4 MSB to 4 LSB for full scale c12 */
+ c12 = c8 >> 4;
+
+ return c12;
+}
+
+static u64 argb8888_to_argb12121212(u32 argb8888)
+{
+ u8 a, r, g, b;
+ u64 v;
+
+ a = (argb8888 >> 24) & 0xff;
+ r = (argb8888 >> 16) & 0xff;
+ g = (argb8888 >> 8) & 0xff;
+ b = (argb8888 >> 0) & 0xff;
+
+ v = ((u64)c8_to_c12(a) << 36) | ((u64)c8_to_c12(r) << 24) |
+ ((u64)c8_to_c12(g) << 12) | (u64)c8_to_c12(b);
+
+ return v;
+}
+
+static void dispc6_vp_set_default_color(struct dispc_device *dispc,
+ u32 hw_videoport, u32 default_color)
+{
+ u64 v;
+
+ v = argb8888_to_argb12121212(default_color);
+
+ dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR, v & 0xffffffff);
+ dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR2,
+ (v >> 32) & 0xffff);
+}
+
+static enum drm_mode_status dispc6_vp_mode_valid(struct dispc_device *dispc,
+ u32 hw_videoport,
+ const struct drm_display_mode *mode)
+{
+ u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+ /* special case for 9MHz */
+ if (mode->clock * 1000 < dispc->feat->min_pclk && mode->clock != 9000)
+ return MODE_CLOCK_LOW;
+
+ if (mode->clock * 1000 > dispc->feat->max_pclk)
+ return MODE_CLOCK_HIGH;
+
+ if (mode->hdisplay > 4096)
+ return MODE_BAD;
+
+ if (mode->vdisplay > 4096)
+ return MODE_BAD;
+
+ /* TODO: add interlace support */
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_NO_INTERLACE;
+
+ hfp = mode->hsync_start - mode->hdisplay;
+ hsw = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
+
+ vfp = mode->vsync_start - mode->vdisplay;
+ vsw = mode->vsync_end - mode->vsync_start;
+ vbp = mode->vtotal - mode->vsync_end;
+
+ if (hsw < 1 || hsw > 256 ||
+ hfp < 1 || hfp > 4096 ||
+ hbp < 1 || hbp > 4096)
+ return MODE_BAD_HVALUE;
+
+ if (vsw < 1 || vsw > 256 ||
+ vfp < 0 || vfp > 4095 ||
+ vbp < 0 || vbp > 4095)
+ return MODE_BAD_VVALUE;
+
+ return MODE_OK;
+}
+
+static int dispc6_vp_check(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state)
+{
+ const struct drm_display_mode *mode = &state->adjusted_mode;
+ const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+ enum drm_mode_status ok;
+ unsigned int i;
+
+ ok = dispc6_vp_mode_valid(dispc, hw_videoport, mode);
+ if (ok != MODE_OK) {
+ dev_dbg(dispc->dev, "%s: bad mode: %ux%u pclk %u kHz\n",
+ __func__, mode->hdisplay, mode->vdisplay, mode->clock);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) {
+ if (dispc6_bus_formats[i].fmt == tstate->bus_format)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(dispc6_bus_formats)) {
+ dev_dbg(dispc->dev, "%s: Unsupported bus format: %u\n",
+ __func__, tstate->bus_format);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dispc6_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport)
+{
+ int ret = clk_prepare_enable(dispc->vp_clk);
+
+ if (ret)
+ dev_err(dispc->dev, "%s: enabling clk failed: %d\n", __func__,
+ ret);
+
+ return ret;
+}
+
+static void dispc6_vp_disable_clk(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ clk_disable_unprepare(dispc->vp_clk);
+}
+
+static int dispc6_vp_set_clk_rate(struct dispc_device *dispc,
+ u32 hw_videoport, unsigned long rate)
+{
+ int r;
+ unsigned long new_rate;
+
+ r = clk_set_rate(dispc->vp_clk, rate);
+ if (r) {
+ dev_err(dispc->dev, "Failed to set vp clk rate to %lu\n",
+ rate);
+ return r;
+ }
+
+ new_rate = clk_get_rate(dispc->vp_clk);
+
+ if (rate != new_rate)
+ dev_warn(dispc->dev,
+ "Failed to get exact pix clock %lu != %lu\n",
+ rate, new_rate);
+
+ dev_dbg(dispc->dev, "New VP rate %lu Hz (requested %lu Hz)\n",
+ clk_get_rate(dispc->vp_clk), rate);
+
+ return 0;
+}
+
+/* CSC */
+
+struct color_conv_coef {
+ int ry, rcb, rcr;
+ int gy, gcb, gcr;
+ int by, bcb, bcr;
+ int roffset, goffset, boffset;
+ bool full_range;
+};
+
+static void dispc6_vid_write_color_conv_coefs(struct dispc_device *dispc,
+ u32 hw_plane,
+ const struct color_conv_coef *ct)
+{
+#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0))
+
+ dispc6_vid_write(dispc, hw_plane,
+ DISPC_VID_CONV_COEF(0), CVAL(ct->rcr, ct->ry));
+ dispc6_vid_write(dispc, hw_plane,
+ DISPC_VID_CONV_COEF(1), CVAL(ct->gy, ct->rcb));
+ dispc6_vid_write(dispc, hw_plane,
+ DISPC_VID_CONV_COEF(2), CVAL(ct->gcb, ct->gcr));
+ dispc6_vid_write(dispc, hw_plane,
+ DISPC_VID_CONV_COEF(3), CVAL(ct->bcr, ct->by));
+ dispc6_vid_write(dispc, hw_plane,
+ DISPC_VID_CONV_COEF(4), CVAL(0, ct->bcb));
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(5),
+ FLD_VAL(ct->roffset, 15, 3) |
+ FLD_VAL(ct->goffset, 31, 19));
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(6),
+ FLD_VAL(ct->boffset, 15, 3));
+
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+ ct->full_range, 11, 11);
+
+#undef CVAL
+}
+
+static void dispc6_vid_csc_setup(struct dispc_device *dispc)
+{
+ /* YUV -> RGB, ITU-R BT.601, full range */
+ const struct color_conv_coef coefs_yuv2rgb_bt601_full = {
+ 256, 0, 358,
+ 256, -88, -182,
+ 256, 452, 0,
+ 0, -2048, -2048,
+ true,
+ };
+
+ dispc6_vid_write_color_conv_coefs(dispc, 0, &coefs_yuv2rgb_bt601_full);
+}
+
+static void dispc6_vid_csc_enable(struct dispc_device *dispc,
+ u32 hw_plane, bool enable)
+{
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 9, 9);
+}
+
+/* SCALER */
+
+static u32 dispc6_calc_fir_inc(u32 in, u32 out)
+{
+ return (u32)div_u64(0x200000ull * in, out);
+}
+
+struct dispc6_vid_fir_coefs {
+ s16 c2[16];
+ s16 c1[16];
+ u16 c0[9];
+};
+
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_null = {
+ .c2 = { 0 },
+ .c1 = { 0 },
+ .c0 = { 512, 512, 512, 512, 512, 512, 512, 512, 256, },
+};
+
+/* M=8, Upscale x >= 1 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m8 = {
+ .c2 = { 0, -4, -8, -16, -24, -32, -40, -48, 0, 2, 4, 6, 8, 6, 4, 2, },
+ .c1 = { 0, 28, 56, 94, 132, 176, 220, 266, -56, -60, -64, -62, -60, -50, -40, -20, },
+ .c0 = { 512, 506, 500, 478, 456, 424, 392, 352, 312, },
+};
+
+/* 5-tap, M=22, Downscale Ratio 2.5 < x < 3 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_5tap = {
+ .c2 = { 16, 20, 24, 30, 36, 42, 48, 56, 0, 0, 0, 2, 4, 8, 12, 14, },
+ .c1 = { 132, 140, 148, 156, 164, 172, 180, 186, 64, 72, 80, 88, 96, 104, 112, 122, },
+ .c0 = { 216, 216, 216, 214, 212, 208, 204, 198, 192, },
+};
+
+/* 3-tap, M=22, Downscale Ratio 2.5 < x < 3 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_3tap = {
+ .c1 = { 100, 118, 136, 156, 176, 196, 216, 236, 0, 10, 20, 30, 40, 54, 68, 84, },
+ .c0 = { 312, 310, 308, 302, 296, 286, 276, 266, 256, },
+};
+
+enum dispc6_vid_fir_coef_set {
+ DISPC6_VID_FIR_COEF_HORIZ,
+ DISPC6_VID_FIR_COEF_HORIZ_UV,
+ DISPC6_VID_FIR_COEF_VERT,
+ DISPC6_VID_FIR_COEF_VERT_UV,
+};
+
+static void dispc6_vid_write_fir_coefs(struct dispc_device *dispc,
+ u32 hw_plane,
+ enum dispc6_vid_fir_coef_set coef_set,
+ const struct dispc6_vid_fir_coefs *coefs)
+{
+ static const u16 c0_regs[] = {
+ [DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H0,
+ [DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H0_C,
+ [DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V0,
+ [DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V0_C,
+ };
+
+ static const u16 c12_regs[] = {
+ [DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H12,
+ [DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H12_C,
+ [DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V12,
+ [DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V12_C,
+ };
+
+ const u16 c0_base = c0_regs[coef_set];
+ const u16 c12_base = c12_regs[coef_set];
+ int phase;
+
+ for (phase = 0; phase <= 8; ++phase) {
+ u16 reg = c0_base + phase * 4;
+ u16 c0 = coefs->c0[phase];
+
+ dispc6_vid_write(dispc, hw_plane, reg, c0);
+ }
+
+ for (phase = 0; phase <= 15; ++phase) {
+ u16 reg = c12_base + phase * 4;
+ s16 c1, c2;
+ u32 c12;
+
+ c1 = coefs->c1[phase];
+ c2 = coefs->c2[phase];
+ c12 = FLD_VAL(c1, 19, 10) | FLD_VAL(c2, 29, 20);
+
+ dispc6_vid_write(dispc, hw_plane, reg, c12);
+ }
+}
+
+static void dispc6_vid_write_scale_coefs(struct dispc_device *dispc,
+ u32 hw_plane)
+{
+ dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ,
+ &dispc6_fir_coefs_null);
+ dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ_UV,
+ &dispc6_fir_coefs_null);
+ dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT,
+ &dispc6_fir_coefs_null);
+ dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT_UV,
+ &dispc6_fir_coefs_null);
+}
+
+static void dispc6_vid_set_scaling(struct dispc_device *dispc,
+ u32 hw_plane,
+ u32 orig_width, u32 orig_height,
+ u32 out_width, u32 out_height,
+ u32 fourcc)
+{
+ u32 in_w, in_h, in_w_uv, in_h_uv;
+ u32 fir_hinc, fir_vinc, fir_hinc_uv, fir_vinc_uv;
+ bool scale_x, scale_y;
+ bool five_taps = false; /* XXX always 3-tap for now */
+
+ in_w = in_w_uv = orig_width;
+ in_h = in_h_uv = orig_height;
+
+ switch (fourcc) {
+ case DRM_FORMAT_NV12:
+ /* UV is subsampled by 2 horizontally and vertically */
+ in_h_uv >>= 1;
+ in_w_uv >>= 1;
+ break;
+
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ /* UV is subsampled by 2 horizontally */
+ in_w_uv >>= 1;
+ break;
+
+ default:
+ break;
+ }
+
+ scale_x = in_w != out_width || in_w_uv != out_width;
+ scale_y = in_h != out_height || in_h_uv != out_height;
+
+ /* HORIZONTAL RESIZE ENABLE */
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_x, 7, 7);
+
+ /* VERTICAL RESIZE ENABLE */
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_y, 8, 8);
+
+ /* Skip the rest if no scaling is used */
+ if (!scale_x && !scale_y)
+ return;
+
+ /* VERTICAL 5-TAPS */
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, five_taps, 21, 21);
+
+ /* FIR INC */
+
+ fir_hinc = dispc6_calc_fir_inc(in_w, out_width);
+ fir_vinc = dispc6_calc_fir_inc(in_h, out_height);
+ fir_hinc_uv = dispc6_calc_fir_inc(in_w_uv, out_width);
+ fir_vinc_uv = dispc6_calc_fir_inc(in_h_uv, out_height);
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH, fir_hinc);
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV, fir_vinc);
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH2, fir_hinc_uv);
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV2, fir_vinc_uv);
+
+ dispc6_vid_write_scale_coefs(dispc, hw_plane);
+}
+
+/* OTHER */
+
+static const struct {
+ u32 fourcc;
+ u8 dss_code;
+} dispc6_color_formats[] = {
+ { DRM_FORMAT_ARGB4444, 0x0, },
+ { DRM_FORMAT_ABGR4444, 0x1, },
+ { DRM_FORMAT_RGBA4444, 0x2, },
+
+ { DRM_FORMAT_RGB565, 0x3, },
+ { DRM_FORMAT_BGR565, 0x4, },
+
+ { DRM_FORMAT_ARGB1555, 0x5, },
+ { DRM_FORMAT_ABGR1555, 0x6, },
+
+ { DRM_FORMAT_ARGB8888, 0x7, },
+ { DRM_FORMAT_ABGR8888, 0x8, },
+ { DRM_FORMAT_RGBA8888, 0x9, },
+ { DRM_FORMAT_BGRA8888, 0xa, },
+
+ { DRM_FORMAT_XRGB8888, 0x27, },
+ { DRM_FORMAT_XBGR8888, 0x28, },
+ { DRM_FORMAT_RGBX8888, 0x29, },
+ { DRM_FORMAT_BGRX8888, 0x2a, },
+
+ { DRM_FORMAT_YUYV, 0x3e, },
+ { DRM_FORMAT_UYVY, 0x3f, },
+
+ { DRM_FORMAT_NV12, 0x3d, },
+};
+
+static bool dispc6_fourcc_is_yuv(u32 fourcc)
+{
+ switch (fourcc) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_NV12:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void dispc6_plane_set_pixel_format(struct dispc_device *dispc,
+ u32 hw_plane, u32 fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) {
+ if (dispc6_color_formats[i].fourcc == fourcc) {
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+ dispc6_color_formats[i].dss_code,
+ 6, 1);
+ return;
+ }
+ }
+
+ WARN_ON(1);
+}
+
+static s32 pixinc(int pixels, u8 ps)
+{
+ if (pixels == 1)
+ return 1;
+ else if (pixels > 1)
+ return 1 + (pixels - 1) * ps;
+ else if (pixels < 0)
+ return 1 - (-pixels + 1) * ps;
+
+ WARN_ON(1);
+ return 0;
+}
+
+static
+const struct tidss_plane_feat *dispc6_plane_feat(struct dispc_device *dispc,
+ u32 hw_plane)
+{
+ static const struct tidss_plane_feat pfeat = {
+ .color = {
+ .encodings = BIT(DRM_COLOR_YCBCR_BT601),
+ .ranges = BIT(DRM_COLOR_YCBCR_FULL_RANGE),
+ .default_encoding = DRM_COLOR_YCBCR_BT601,
+ .default_range = DRM_COLOR_YCBCR_FULL_RANGE,
+ },
+ .blend = {
+ .global_alpha = false,
+ },
+ };
+
+ return &pfeat;
+}
+
+static int dispc6_plane_check(struct dispc_device *dispc, u32 hw_plane,
+ const struct drm_plane_state *state,
+ u32 hw_videoport)
+{
+ return 0; /* XXX: Dummy check function to be implemented later */
+}
+
+static int dispc6_plane_setup(struct dispc_device *dispc, u32 hw_plane,
+ const struct drm_plane_state *state,
+ u32 hw_videoport)
+{
+ u32 fourcc = state->fb->format->format;
+ dma_addr_t paddr = dispc7_plane_state_paddr(state);
+ u16 cpp = state->fb->format->cpp[0];
+ u32 fb_width = state->fb->pitches[0] / cpp;
+ u32 src_w = state->src_w >> 16;
+ u32 src_h = state->src_h >> 16;
+
+ dispc6_plane_set_pixel_format(dispc, hw_plane, fourcc);
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_0, paddr);
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_1, paddr);
+
+ if (state->fb->format->num_planes == 2) {
+ dma_addr_t p_uv_addr = dispc7_plane_state_p_uv_addr(state);
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_0, p_uv_addr);
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_1, p_uv_addr);
+ }
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_PICTURE_SIZE,
+ (src_w - 1) | ((src_h - 1) << 16));
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_PIXEL_INC,
+ pixinc(1, cpp));
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_ROW_INC,
+ pixinc(1 + fb_width - src_w, cpp));
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_POSITION,
+ state->crtc_x | (state->crtc_y << 16));
+
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_SIZE,
+ (state->crtc_w - 1) | ((state->crtc_h - 1) << 16));
+
+ dispc6_vid_set_scaling(dispc, hw_plane, src_w, src_h,
+ state->crtc_w, state->crtc_h,
+ fourcc);
+
+ /* enable YUV->RGB color conversion */
+ if (dispc6_fourcc_is_yuv(fourcc))
+ dispc6_vid_csc_enable(dispc, hw_plane, true);
+ else
+ dispc6_vid_csc_enable(dispc, hw_plane, false);
+
+ /* hw_videoport */
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, 0, 16, 14);
+
+ return 0;
+}
+
+static int dispc6_plane_enable(struct dispc_device *dispc,
+ u32 hw_plane, bool enable)
+{
+ VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 0, 0);
+ return 0;
+}
+
+static u32 dispc6_vid_get_fifo_size(struct dispc_device *dispc,
+ u32 hw_plane)
+{
+ const u32 unit_size = 16; /* 128-bits */
+
+ return VID_REG_GET(dispc, hw_plane, DISPC_VID_BUF_SIZE_STATUS, 15, 0) *
+ unit_size;
+}
+
+static void dispc6_vid_set_mflag_threshold(struct dispc_device *dispc,
+ u32 hw_plane,
+ u32 low, u32 high)
+{
+ dispc6_vid_write(dispc, hw_plane, DISPC_VID_MFLAG_THRESHOLD,
+ FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0));
+}
+
+static void dispc6_mflag_setup(struct dispc_device *dispc)
+{
+ u32 hw_plane = 0;
+ const u32 unit_size = 16; /* 128-bits */
+ u32 size = dispc6_vid_get_fifo_size(dispc, hw_plane);
+ u32 low, high;
+
+ /* MFLAG_CTRL */
+ REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 1, 1, 0);
+ /* MFLAG_START */
+ REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 0, 2, 2);
+
+ /*
+ * Simulation team suggests below thesholds:
+ * HT = fifosize * 5 / 8;
+ * LT = fifosize * 4 / 8;
+ */
+
+ low = size * 4 / 8 / unit_size;
+ high = size * 5 / 8 / unit_size;
+
+ dispc6_vid_set_mflag_threshold(dispc, hw_plane, low, high);
+}
+
+static void dispc6_initial_config(struct dispc_device *dispc)
+{
+ dispc6_vid_csc_setup(dispc);
+ dispc6_mflag_setup(dispc);
+
+ /* Enable the gamma Shadow bit-field */
+ VP_REG_FLD_MOD(dispc, 0, DISPC_VP_CONFIG, 1, 2, 2);
+}
+
+static int dispc6_init_features(struct dispc_device *dispc)
+{
+ const struct of_device_id *match;
+
+ match = of_match_node(dispc6_of_match, dispc->dev->of_node);
+ if (!match) {
+ dev_err(dispc->dev, "Unsupported DISPC version\n");
+ return -ENODEV;
+ }
+
+ dispc->feat = match->data;
+
+ return 0;
+}
+
+static int dispc6_get_num_planes(struct dispc_device *dispc)
+{
+ return 1;
+}
+
+static int dispc6_get_num_vps(struct dispc_device *dispc)
+{
+ return 1;
+}
+
+static const struct tidss_vp_feat *dispc6_vp_feat(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ static const struct tidss_vp_feat vp_feat = {
+ .color = {
+ .gamma_size = DISPC6_GAMMA_TABLE_SIZE,
+ .has_ctm = false, /* Driver implementation missing */
+ },
+ };
+
+ return &vp_feat;
+}
+
+static void dispc6_vp_write_gamma_table(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ u32 *table = dispc->gamma_table;
+ unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table);
+ unsigned int i;
+
+ dev_dbg(dispc->dev, "%s: hw_videoport %d\n", __func__, hw_videoport);
+
+ for (i = 0; i < hwlen; ++i) {
+ u32 v = table[i];
+
+ v |= i << 24;
+
+ dispc6_vp_write(dispc, hw_videoport, DISPC_VP_GAMMA_TABLE, v);
+ }
+}
+
+static void dispc6_restore_gamma_tables(struct dispc_device *dispc)
+{
+ dev_dbg(dispc->dev, "%s()\n", __func__);
+
+ dispc6_vp_write_gamma_table(dispc, 0);
+}
+
+static const struct drm_color_lut dispc6_vp_gamma_default_lut[] = {
+ { .red = 0, .green = 0, .blue = 0, },
+ { .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, },
+};
+
+static void dispc6_vp_set_gamma(struct dispc_device *dispc,
+ u32 hw_videoport,
+ const struct drm_color_lut *lut,
+ unsigned int length)
+{
+ u32 *table = dispc->gamma_table;
+ unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table);
+ static const unsigned int hwbits = 8;
+ unsigned int i;
+
+ dev_dbg(dispc->dev, "%s: hw_videoport %d, lut len %u, hw len %u\n",
+ __func__, hw_videoport, length, hwlen);
+
+ if (lut == NULL || length < 2) {
+ lut = dispc6_vp_gamma_default_lut;
+ length = ARRAY_SIZE(dispc6_vp_gamma_default_lut);
+ }
+
+ for (i = 0; i < length - 1; ++i) {
+ unsigned int first = i * (hwlen - 1) / (length - 1);
+ unsigned int last = (i + 1) * (hwlen - 1) / (length - 1);
+ unsigned int w = last - first;
+ u16 r, g, b;
+ unsigned int j;
+
+ if (w == 0)
+ continue;
+
+ for (j = 0; j <= w; j++) {
+ r = (lut[i].red * (w - j) + lut[i + 1].red * j) / w;
+ g = (lut[i].green * (w - j) + lut[i + 1].green * j) / w;
+ b = (lut[i].blue * (w - j) + lut[i + 1].blue * j) / w;
+
+ r >>= 16 - hwbits;
+ g >>= 16 - hwbits;
+ b >>= 16 - hwbits;
+
+ table[first + j] = (r << (hwbits * 2)) |
+ (g << hwbits) | b;
+ }
+ }
+
+ if (dispc->is_enabled)
+ dispc6_vp_write_gamma_table(dispc, hw_videoport);
+}
+
+static void dispc6_vp_set_color_mgmt(struct dispc_device *dispc,
+ u32 hw_videoport,
+ const struct drm_crtc_state *state)
+{
+ struct drm_color_lut *lut = NULL;
+ unsigned int length = 0;
+
+ if (!state->color_mgmt_changed)
+ return;
+
+ if (state->gamma_lut) {
+ lut = (struct drm_color_lut *) state->gamma_lut->data;
+ length = state->gamma_lut->length / sizeof(*lut);
+ }
+
+ dispc6_vp_set_gamma(dispc, hw_videoport, lut, length);
+}
+
+static void dispc6_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
+ const struct drm_crtc_state *state)
+{
+ dispc6_vp_set_default_color(dispc, hw_videoport, 0);
+ dispc6_vp_set_color_mgmt(dispc, hw_videoport, state);
+}
+
+static int dispc6_init_gamma_tables(struct dispc_device *dispc)
+{
+ dispc6_vp_set_gamma(dispc, 0, NULL, 0);
+
+ return 0;
+}
+
+static u32 dispc6_get_memory_bandwidth_limit(struct dispc_device *dispc)
+{
+ u32 limit = 0;
+
+ /* Optional maximum memory bandwidth */
+ of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth",
+ &limit);
+
+ return limit;
+}
+
+static const char *dispc6_plane_name(struct dispc_device *dispc,
+ u32 hw_plane)
+{
+ return "vid1";
+}
+
+static const char *dispc6_vp_name(struct dispc_device *dispc,
+ u32 hw_videoport)
+{
+ return "vp1";
+}
+
+static int dispc6_runtime_suspend(struct dispc_device *dispc)
+{
+ struct device *dev = dispc->dev;
+
+ dev_dbg(dev, "suspend\n");
+
+ dispc->is_enabled = false;
+
+ clk_disable_unprepare(dispc->fclk);
+
+ return 0;
+}
+
+static int dispc6_runtime_resume(struct dispc_device *dispc)
+{
+ struct device *dev = dispc->dev;
+
+ dev_dbg(dev, "resume\n");
+
+ clk_prepare_enable(dispc->fclk);
+
+ if (REG_GET(dispc, DISPC_SYSSTATUS, 0, 0) == 0)
+ dev_warn(dev, "DISPC FUNC RESET not done!\n");
+ if (REG_GET(dispc, DISPC_SYSSTATUS, 1, 1) == 0)
+ dev_warn(dev, "DISPC VP RESET not done!\n");
+
+ dispc6_initial_config(dispc);
+
+ dispc6_restore_gamma_tables(dispc);
+
+ dispc->is_enabled = true;
+
+ return 0;
+}
+
+static int dispc6_modeset_init(struct dispc_device *dispc)
+{
+ struct tidss_device *tidss = dispc->tidss;
+ struct device *dev = tidss->dev;
+ const u32 hw_videoport = 0;
+ const u32 crtc_mask = 1;
+ const u32 hw_plane_id = 0;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ u32 enc_type;
+ int ret;
+ struct tidss_plane *tplane;
+ struct tidss_crtc *tcrtc;
+ struct drm_encoder *enc;
+ u32 fourccs[ARRAY_SIZE(dispc6_color_formats)];
+ unsigned int i;
+
+ /* first find if there is a connected panel/bridge */
+
+ ret = drm_of_find_panel_or_bridge(dev->of_node, hw_videoport, 0, &panel, &bridge);
+ if (ret) {
+ dev_dbg(dev, "no panel or bridge found\n");
+ return ret;
+ }
+
+ if (panel) {
+ dev_dbg(dev, "Setting up panel\n");
+
+ enc_type = DRM_MODE_ENCODER_DPI;
+
+ bridge = devm_drm_panel_bridge_add(dev, panel, DRM_MODE_CONNECTOR_DPI);
+ if (IS_ERR(bridge)) {
+ dev_err(dev, "failed to set up panel bridge\n");
+ return PTR_ERR(bridge);
+ }
+ } else {
+ enc_type = DRM_MODE_ENCODER_NONE;
+ }
+
+ /* then create a plane, a crtc and an encoder for the panel/bridge */
+
+ for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i)
+ fourccs[i] = dispc6_color_formats[i].fourcc;
+
+ tplane = tidss_plane_create(tidss, hw_plane_id, DRM_PLANE_TYPE_PRIMARY,
+ crtc_mask, fourccs, ARRAY_SIZE(fourccs));
+ if (IS_ERR(tplane)) {
+ dev_err(tidss->dev, "plane create failed\n");
+ return PTR_ERR(tplane);
+ }
+
+ tidss->planes[tidss->num_planes++] = &tplane->plane;
+
+ tcrtc = tidss_crtc_create(tidss, hw_videoport, &tplane->plane);
+ if (IS_ERR(tcrtc)) {
+ dev_err(tidss->dev, "crtc create failed\n");
+ return PTR_ERR(tcrtc);
+ }
+
+ tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc;
+
+ enc = tidss_encoder_create(tidss, enc_type, 1 << tcrtc->crtc.index);
+ if (IS_ERR(enc)) {
+ dev_err(tidss->dev, "encoder create failed\n");
+ return PTR_ERR(enc);
+ }
+
+ ret = drm_bridge_attach(enc, bridge, NULL);
+ if (ret) {
+ dev_err(tidss->dev, "bridge attach failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dispc6_remove(struct dispc_device *dispc);
+
+static const struct tidss_dispc_ops dispc6_ops = {
+ .read_and_clear_irqstatus = dispc6_read_and_clear_irqstatus,
+ .write_irqenable = dispc6_write_irqenable,
+
+ .get_memory_bandwidth_limit = dispc6_get_memory_bandwidth_limit,
+
+ .get_num_vps = dispc6_get_num_vps,
+ .vp_name = dispc6_vp_name,
+ .vp_feat = dispc6_vp_feat,
+ .vp_enable = dispc6_vp_enable,
+ .vp_disable = dispc6_vp_disable,
+ .vp_go_busy = dispc6_vp_go_busy,
+ .vp_go = dispc6_vp_go,
+ .vp_mode_valid = dispc6_vp_mode_valid,
+ .vp_check = dispc6_vp_check,
+ .vp_setup = dispc6_vp_setup,
+
+ .vp_set_clk_rate = dispc6_vp_set_clk_rate,
+ .vp_enable_clk = dispc6_vp_enable_clk,
+ .vp_disable_clk = dispc6_vp_disable_clk,
+
+ .get_num_planes = dispc6_get_num_planes,
+ .plane_name = dispc6_plane_name,
+ .plane_feat = dispc6_plane_feat,
+ .plane_enable = dispc6_plane_enable,
+ .plane_check = dispc6_plane_check,
+ .plane_setup = dispc6_plane_setup,
+
+ .runtime_get = dispc6_runtime_get,
+ .runtime_put = dispc6_runtime_put,
+
+ .runtime_suspend = dispc6_runtime_suspend,
+ .runtime_resume = dispc6_runtime_resume,
+
+ .remove = dispc6_remove,
+
+ .modeset_init = dispc6_modeset_init,
+};
+
+static int dispc6_iomap_resource(struct platform_device *pdev, const char *name,
+ void __iomem **base)
+{
+ struct resource *res;
+ void __iomem *b;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+ b = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(b)) {
+ dev_err(&pdev->dev, "cannot ioremap resource '%s'\n", name);
+ return PTR_ERR(b);
+ }
+
+ *base = b;
+
+ return 0;
+}
+
+int dispc6_init(struct tidss_device *tidss)
+{
+ struct device *dev = tidss->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dispc_device *dispc;
+ int r;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ dispc = devm_kzalloc(dev, sizeof(*dispc), GFP_KERNEL);
+ if (!dispc)
+ return -ENOMEM;
+
+ dispc->tidss = tidss;
+ dispc->dev = dev;
+
+ r = dispc6_init_features(dispc);
+ if (r)
+ goto err_free;
+
+ r = dispc6_iomap_resource(pdev, "cfg", &dispc->base_cfg);
+ if (r)
+ goto err_free;
+
+ r = dispc6_iomap_resource(pdev, "common", &dispc->base_common);
+ if (r)
+ goto err_free;
+
+ r = dispc6_iomap_resource(pdev, "vid1", &dispc->base_vid1);
+ if (r)
+ goto err_free;
+
+ r = dispc6_iomap_resource(pdev, "ovr1", &dispc->base_ovr1);
+ if (r)
+ goto err_free;
+
+ r = dispc6_iomap_resource(pdev, "vp1", &dispc->base_vp1);
+ if (r)
+ goto err_free;
+
+ dev_dbg(dev, "dispc6_bind: iores ok\n");
+
+ dispc->fclk = devm_clk_get(dev, "fck");
+ if (IS_ERR(dispc->fclk)) {
+ dev_err(dev, "Failed to get fclk\n");
+ r = PTR_ERR(dispc->fclk);
+ goto err_free;
+ }
+
+ dispc->vp_clk = devm_clk_get(dev, "vp1");
+ if (IS_ERR(dispc->vp_clk)) {
+ dev_err(dev, "Failed to get vp1 clk\n");
+ r = PTR_ERR(dispc->vp_clk);
+ goto err_free;
+ }
+
+ r = dispc6_init_gamma_tables(dispc);
+ if (r)
+ goto err_free;
+
+ tidss->dispc_ops = &dispc6_ops;
+ tidss->dispc = dispc;
+
+ dev_dbg(dev, "%s done\n", __func__);
+
+ return 0;
+err_free:
+ dev_err(dev, "%s failed: %d\n", __func__, r);
+ return r;
+}
+
+static void dispc6_remove(struct dispc_device *dispc)
+{
+ struct device *dev = dispc->dev;
+
+ dev_dbg(dev, "dispc6_unbind\n");
+
+ dispc->tidss->dispc_ops = NULL;
+ dispc->tidss->dispc = NULL;
+
+ dev_dbg(dev, "dispc6_unbind done\n");
+}
new file mode 100644
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC6_H__
+#define __TIDSS_DISPC6_H__
+
+/* COMMON */
+
+#define DISPC_REVISION 0x000
+#define DISPC_SYSCONFIG 0x004
+#define DISPC_SYSSTATUS 0x008
+
+#define DISPC_IRQ_EOI 0x020
+#define DISPC_IRQSTATUS_RAW 0x024
+#define DISPC_IRQSTATUS 0x028
+#define DISPC_IRQENABLE_SET 0x02c
+#define DISPC_IRQENABLE_CLR 0x030
+#define DISPC_IRQWAKEEN 0x034
+
+#define DISPC_GLOBAL_MFLAG_ATTRIBUTE 0x040
+#define DISPC_GLOBAL_BUFFER 0x044
+#define DISPC_BA0_FLIPIMMEDIATE_EN 0x048
+
+#define DISPC_DBG_CONTROL 0x04c
+#define DISPC_DBG_STATUS 0x050
+
+#define DISPC_CLKGATING_DISABLE 0x054
+
+/* VID */
+
+#define DISPC_VID_ACCUH_0 0x0
+#define DISPC_VID_ACCUH_1 0x4
+#define DISPC_VID_ACCUH2_0 0x8
+#define DISPC_VID_ACCUH2_1 0xc
+
+#define DISPC_VID_ACCUV_0 0x10
+#define DISPC_VID_ACCUV_1 0x14
+#define DISPC_VID_ACCUV2_0 0x18
+#define DISPC_VID_ACCUV2_1 0x1c
+
+#define DISPC_VID_ATTRIBUTES 0x20
+#define DISPC_VID_ATTRIBUTES2 0x24
+
+#define DISPC_VID_BA_0 0x28
+#define DISPC_VID_BA_1 0x2c
+#define DISPC_VID_BA_UV_0 0x30
+#define DISPC_VID_BA_UV_1 0x34
+#define DISPC_VID_BUF_SIZE_STATUS 0x38
+#define DISPC_VID_BUF_THRESHOLD 0x3c
+
+#define DISPC_VID_CONV_COEF(n) (0x40 + (n) * 4)
+
+#define DISPC_VID_FIRH 0x5c
+#define DISPC_VID_FIRH2 0x60
+#define DISPC_VID_FIRV 0x64
+#define DISPC_VID_FIRV2 0x68
+
+#define DISPC_VID_FIR_COEFS_H0 0x6c
+#define DISPC_VID_FIR_COEF_H0(phase) (0x6c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H0_C 0x90
+#define DISPC_VID_FIR_COEF_H0_C(phase) (0x90 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_H12 0xb4
+#define DISPC_VID_FIR_COEF_H12(phase) (0xb4 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H12_C 0xf4
+#define DISPC_VID_FIR_COEF_H12_C(phase) (0xf4 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V0 0x134
+#define DISPC_VID_FIR_COEF_V0(phase) (0x134 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V0_C 0x158
+#define DISPC_VID_FIR_COEF_V0_C(phase) (0x158 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V12 0x17c
+#define DISPC_VID_FIR_COEF_V12(phase) (0x17c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V12_C 0x1bc
+#define DISPC_VID_FIR_COEF_V12_C(phase) (0x1bc + (phase) * 4)
+
+#define DISPC_VID_IRQENABLE 0x200
+#define DISPC_VID_IRQSTATUS 0x204
+
+#define DISPC_VID_MFLAG_THRESHOLD 0x208
+#define DISPC_VID_PICTURE_SIZE 0x20c
+#define DISPC_VID_PIXEL_INC 0x210
+#define DISPC_VID_POSITION 0x214
+#define DISPC_VID_PRELOAD 0x218
+#define DISPC_VID_ROW_INC 0x21c
+#define DISPC_VID_SIZE 0x220
+
+/* OVR */
+
+#define DISPC_OVR_DEFAULT_COLOR 0x08
+#define DISPC_OVR_DEFAULT_COLOR2 0x0c
+
+/* VP */
+
+#define DISPC_VP_CONFIG 0x00
+#define DISPC_VP_CONTROL 0x04
+#define DISPC_VP_GAMMA_TABLE 0x20
+#define DISPC_VP_IRQENABLE 0x3c
+#define DISPC_VP_IRQSTATUS 0x40
+#define DISPC_VP_POL_FREQ 0x4c
+#define DISPC_VP_SIZE_SCREEN 0x50
+#define DISPC_VP_TIMING_H 0x54
+#define DISPC_VP_TIMING_V 0x58
+
+#endif
new file mode 100644
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/console.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "tidss_dispc.h"
+#include "tidss_drv.h"
+#include "tidss_irq.h"
+#include "tidss_kms.h"
+
+/* -----------------------------------------------------------------------------
+ * Device Information
+ */
+
+DEFINE_DRM_GEM_CMA_FOPS(tidss_fops);
+
+static struct drm_driver tidss_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
+ | DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
+ .gem_free_object_unlocked = drm_gem_cma_free_object,
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_export = drm_gem_prime_export,
+ .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+ .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+ .gem_prime_vmap = drm_gem_cma_prime_vmap,
+ .gem_prime_vunmap = drm_gem_cma_prime_vunmap,
+ .gem_prime_mmap = drm_gem_cma_prime_mmap,
+ .dumb_create = drm_gem_cma_dumb_create,
+ .fops = &tidss_fops,
+ .name = "tidss",
+ .desc = "TI Keystone DSS",
+ .date = "20180215",
+ .major = 1,
+ .minor = 0,
+
+ .irq_preinstall = tidss_irq_preinstall,
+ .irq_postinstall = tidss_irq_postinstall,
+ .irq_handler = tidss_irq_handler,
+ .irq_uninstall = tidss_irq_uninstall,
+};
+
+#ifdef CONFIG_PM
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int tidss_pm_runtime_suspend(struct device *dev)
+{
+ struct tidss_device *tidss = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ return tidss->dispc_ops->runtime_suspend(tidss->dispc);
+}
+
+static int tidss_pm_runtime_resume(struct device *dev)
+{
+ struct tidss_device *tidss = dev_get_drvdata(dev);
+ int r;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ r = tidss->dispc_ops->runtime_resume(tidss->dispc);
+ if (r)
+ return r;
+
+ tidss_irq_resume(tidss->ddev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tidss_suspend(struct device *dev)
+{
+ struct tidss_device *tidss = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ return drm_mode_config_helper_suspend(tidss->ddev);
+}
+
+static int tidss_resume(struct device *dev)
+{
+ struct tidss_device *tidss = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ return drm_mode_config_helper_resume(tidss->ddev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops tidss_pm_ops = {
+ .runtime_suspend = tidss_pm_runtime_suspend,
+ .runtime_resume = tidss_pm_runtime_resume,
+ SET_SYSTEM_SLEEP_PM_OPS(tidss_suspend, tidss_resume)
+};
+
+#endif /* CONFIG_PM */
+
+/* -----------------------------------------------------------------------------
+ * Platform driver
+ */
+
+static int tidss_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tidss_device *tidss;
+ struct drm_device *ddev;
+ int ret;
+ int irq;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ tidss = devm_kzalloc(dev, sizeof(*tidss), GFP_KERNEL);
+ if (tidss == NULL)
+ return -ENOMEM;
+
+ tidss->dev = dev;
+ tidss->features = of_device_get_match_data(dev);
+
+ platform_set_drvdata(pdev, tidss);
+
+ ddev = drm_dev_alloc(&tidss_driver, dev);
+ if (IS_ERR(ddev))
+ return PTR_ERR(ddev);
+
+ tidss->ddev = ddev;
+ ddev->dev_private = tidss;
+
+ pm_runtime_enable(dev);
+
+ ret = tidss->features->dispc_init(tidss);
+ if (ret) {
+ dev_err(dev, "failed to initialize dispc: %d\n", ret);
+ goto err_disable_pm;
+ }
+
+#ifndef CONFIG_PM_SLEEP
+ /* no PM, so force enable DISPC */
+ tidss->dispc_ops->runtime_resume(tidss->dispc);
+#endif
+
+ ret = tidss_modeset_init(tidss);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to init DRM/KMS (%d)\n", ret);
+ goto err_runtime_suspend;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ dev_err(dev, "platform_get_irq failed: %d\n", ret);
+ goto err_modeset_cleanup;
+ }
+
+ ret = drm_irq_install(ddev, irq);
+ if (ret) {
+ dev_err(dev, "drm_irq_install failed: %d\n", ret);
+ goto err_modeset_cleanup;
+ }
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+ if (ddev->mode_config.num_connector) {
+ struct drm_fbdev_cma *fbdev;
+
+ fbdev = drm_fbdev_cma_init(ddev, 32,
+ ddev->mode_config.num_connector);
+ if (IS_ERR(fbdev)) {
+ dev_err(tidss->dev, "fbdev init failed\n");
+ ret = PTR_ERR(fbdev);
+ goto err_irq_uninstall;
+ }
+
+ tidss->fbdev = fbdev;
+ }
+#endif
+
+ drm_kms_helper_poll_init(ddev);
+
+ ret = drm_dev_register(ddev, 0);
+ if (ret) {
+ dev_err(dev, "failed to register DRM device\n");
+ goto err_poll_fini;
+ }
+
+ dev_dbg(dev, "%s done\n", __func__);
+
+ return 0;
+
+err_poll_fini:
+ drm_kms_helper_poll_fini(ddev);
+
+ if (tidss->fbdev)
+ drm_fbdev_cma_fini(tidss->fbdev);
+
+ drm_atomic_helper_shutdown(ddev);
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+err_irq_uninstall:
+#endif
+ drm_irq_uninstall(ddev);
+
+err_modeset_cleanup:
+ drm_mode_config_cleanup(ddev);
+
+err_runtime_suspend:
+#ifndef CONFIG_PM_SLEEP
+ /* no PM, so force disable DISPC */
+ tidss->dispc_ops->runtime_suspend(tidss->dispc);
+#endif
+
+ tidss->dispc_ops->remove(tidss->dispc);
+
+err_disable_pm:
+ pm_runtime_disable(dev);
+
+ drm_dev_put(ddev);
+
+ return ret;
+}
+
+static int tidss_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tidss_device *tidss = platform_get_drvdata(pdev);
+ struct drm_device *ddev = tidss->ddev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ drm_dev_unregister(ddev);
+
+ drm_kms_helper_poll_fini(ddev);
+
+ if (tidss->fbdev)
+ drm_fbdev_cma_fini(tidss->fbdev);
+
+ drm_atomic_helper_shutdown(ddev);
+
+ drm_irq_uninstall(ddev);
+
+ drm_mode_config_cleanup(ddev);
+
+#ifndef CONFIG_PM_SLEEP
+ /* no PM, so force disable DISPC */
+ tidss->dispc_ops->runtime_suspend(tidss->dispc);
+#endif
+
+ tidss->dispc_ops->remove(tidss->dispc);
+
+ pm_runtime_disable(dev);
+
+ drm_dev_put(ddev);
+
+ dev_dbg(dev, "%s done\n", __func__);
+
+ return 0;
+}
+
+static const struct tidss_features tidss_k2g_features = {
+ .dispc_init = dispc6_init,
+};
+
+static const struct of_device_id tidss_of_table[] = {
+ { .compatible = "ti,k2g-dss", .data = &tidss_k2g_features },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, tidss_of_table);
+
+static struct platform_driver tidss_platform_driver = {
+ .probe = tidss_probe,
+ .remove = tidss_remove,
+ .driver = {
+ .name = "tidss",
+#ifdef CONFIG_PM
+ .pm = &tidss_pm_ops,
+#endif
+ .of_match_table = tidss_of_table,
+ .suppress_bind_attrs = true,
+ },
+};
+
+module_platform_driver(tidss_platform_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("TI Keystone DSS Driver");
+MODULE_LICENSE("GPL v2");
new file mode 100644
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DRV_H__
+#define __TIDSS_DRV_H__
+
+#include <linux/spinlock.h>
+
+struct tidss_device {
+ struct device *dev; /* Underlying DSS device */
+ struct drm_device *ddev; /* DRM device for DSS */
+
+ struct drm_fbdev_cma *fbdev;
+
+ struct dispc_device *dispc;
+ const struct tidss_dispc_ops *dispc_ops;
+
+ const struct tidss_features *features;
+
+ unsigned int num_crtcs;
+ struct drm_crtc *crtcs[8];
+
+ unsigned int num_planes;
+ struct drm_plane *planes[8];
+
+ spinlock_t wait_lock; /* protects the irq masks */
+ u64 irq_mask; /* enabled irqs in addition to wait_list */
+ u64 irq_uf_mask; /* underflow irq bits for all planes */
+
+ struct drm_atomic_state *saved_state;
+};
+
+struct tidss_features {
+ int (*dispc_init)(struct tidss_device *tidss);
+};
+
+#endif
new file mode 100644
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/export.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_of.h>
+
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_crtc.h"
+
+/* -----------------------------------------------------------------------------
+ * Encoder
+ */
+
+static int tidss_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct tidss_crtc_state *tcrtc_state = to_tidss_crtc_state(crtc_state);
+ struct drm_display_info *di = &conn_state->connector->display_info;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ // XXX any cleaner way to set bus format and flags?
+ tcrtc_state->bus_format = di->bus_formats[0];
+ tcrtc_state->bus_flags = di->bus_flags;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+ .atomic_check = tidss_encoder_atomic_check,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss,
+ u32 encoder_type, u32 possible_crtcs)
+{
+ struct drm_encoder *enc;
+ int ret;
+
+ enc = devm_kzalloc(tidss->dev, sizeof(*enc), GFP_KERNEL);
+ if (!enc)
+ return ERR_PTR(-ENOMEM);
+
+ enc->possible_crtcs = possible_crtcs;
+
+ ret = drm_encoder_init(tidss->ddev, enc, &encoder_funcs,
+ encoder_type, NULL);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ drm_encoder_helper_add(enc, &encoder_helper_funcs);
+
+ dev_dbg(tidss->dev, "Encoder create done\n");
+
+ return enc;
+}
new file mode 100644
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_ENCODER_H__
+#define __TIDSS_ENCODER_H__
+
+#include <drm/drm_encoder.h>
+
+struct tidss_device;
+
+struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss,
+ u32 encoder_type, u32 possible_crtcs);
+
+#endif
new file mode 100644
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+
+#include "tidss_irq.h"
+#include "tidss_drv.h"
+#include "tidss_dispc.h"
+#include "tidss_crtc.h"
+#include "tidss_plane.h"
+
+/* call with wait_lock and dispc runtime held */
+static void tidss_irq_update(struct drm_device *ddev)
+{
+ struct tidss_device *tidss = ddev->dev_private;
+
+ assert_spin_locked(&tidss->wait_lock);
+
+ tidss->dispc_ops->write_irqenable(tidss->dispc, tidss->irq_mask);
+}
+
+void tidss_irq_enable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ u32 hw_videoport = tcrtc->hw_videoport;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tidss->wait_lock, flags);
+ tidss->irq_mask |= DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+ DSS_IRQ_VP_VSYNC_ODD(hw_videoport);
+ tidss_irq_update(ddev);
+ spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
+
+void tidss_irq_disable_vblank(struct drm_crtc *crtc)
+{
+ struct drm_device *ddev = crtc->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ u32 hw_videoport = tcrtc->hw_videoport;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tidss->wait_lock, flags);
+ tidss->irq_mask &= ~(DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+ DSS_IRQ_VP_VSYNC_ODD(hw_videoport));
+ tidss_irq_update(ddev);
+ spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
+
+static void tidss_irq_fifo_underflow(struct tidss_device *tidss,
+ u64 irqstatus)
+{
+ static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+ unsigned int i;
+ u64 masked;
+
+ spin_lock(&tidss->wait_lock);
+ masked = irqstatus & tidss->irq_uf_mask & tidss->irq_mask;
+ spin_unlock(&tidss->wait_lock);
+
+ if (!masked)
+ return;
+
+ if (!__ratelimit(&_rs))
+ return;
+
+ DRM_ERROR("FIFO underflow on ");
+
+ for (i = 0; i < DSS_MAX_PLANES; ++i) {
+ if (masked & DSS_IRQ_PLANE_FIFO_UNDERFLOW(i))
+ pr_cont("%u:%s ", i,
+ tidss->dispc_ops->plane_name(tidss->dispc, i));
+ }
+
+ pr_cont("(%016llx)\n", irqstatus);
+}
+
+static void tidss_irq_ocp_error_handler(struct drm_device *ddev,
+ u64 irqstatus)
+{
+ if (irqstatus & DSS_IRQ_DEVICE_OCP_ERR)
+ dev_err_ratelimited(ddev->dev, "OCP error\n");
+}
+
+irqreturn_t tidss_irq_handler(int irq, void *arg)
+{
+ struct drm_device *ddev = (struct drm_device *) arg;
+ struct tidss_device *tidss = ddev->dev_private;
+ unsigned int id;
+ u64 irqstatus;
+
+ if (WARN_ON(!ddev->irq_enabled))
+ return IRQ_NONE;
+
+ irqstatus = tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc);
+
+ for (id = 0; id < tidss->num_crtcs; id++) {
+ struct drm_crtc *crtc = tidss->crtcs[id];
+ struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+ u32 hw_videoport = tcrtc->hw_videoport;
+
+ if (irqstatus & (DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+ DSS_IRQ_VP_VSYNC_ODD(hw_videoport)))
+ tidss_crtc_vblank_irq(crtc);
+
+ if (irqstatus & (DSS_IRQ_VP_FRAME_DONE(hw_videoport)))
+ tidss_crtc_framedone_irq(crtc);
+
+ if (irqstatus & DSS_IRQ_VP_SYNC_LOST(hw_videoport))
+ tidss_crtc_error_irq(crtc, irqstatus);
+ }
+
+ tidss_irq_ocp_error_handler(ddev, irqstatus);
+ tidss_irq_fifo_underflow(tidss, irqstatus);
+
+ return IRQ_HANDLED;
+}
+
+void tidss_irq_preinstall(struct drm_device *ddev)
+{
+ struct tidss_device *tidss = ddev->dev_private;
+
+ spin_lock_init(&tidss->wait_lock);
+
+ tidss->dispc_ops->runtime_get(tidss->dispc);
+
+ tidss->dispc_ops->write_irqenable(tidss->dispc, 0);
+ tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc);
+
+ tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+int tidss_irq_postinstall(struct drm_device *ddev)
+{
+ struct tidss_device *tidss = ddev->dev_private;
+ unsigned int i;
+ unsigned long flags;
+
+ tidss->dispc_ops->runtime_get(tidss->dispc);
+
+ spin_lock_irqsave(&tidss->wait_lock, flags);
+
+ tidss->irq_mask = DSS_IRQ_DEVICE_OCP_ERR;
+
+ tidss->irq_uf_mask = 0;
+ for (i = 0; i < tidss->num_planes; ++i) {
+ struct tidss_plane *tplane = to_tidss_plane(tidss->planes[i]);
+
+ tidss->irq_uf_mask |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(tplane->hw_plane_id);
+ }
+ tidss->irq_mask |= tidss->irq_uf_mask;
+
+ for (i = 0; i < tidss->num_crtcs; ++i) {
+ struct tidss_crtc *tcrtc = to_tidss_crtc(tidss->crtcs[i]);
+
+ tidss->irq_mask |= DSS_IRQ_VP_SYNC_LOST(tcrtc->hw_videoport);
+
+ tidss->irq_mask |= DSS_IRQ_VP_FRAME_DONE(tcrtc->hw_videoport);
+ }
+
+ tidss_irq_update(ddev);
+
+ spin_unlock_irqrestore(&tidss->wait_lock, flags);
+
+ tidss->dispc_ops->runtime_put(tidss->dispc);
+
+ return 0;
+}
+
+void tidss_irq_uninstall(struct drm_device *ddev)
+{
+ struct tidss_device *tidss = ddev->dev_private;
+
+ tidss->dispc_ops->runtime_get(tidss->dispc);
+ tidss->dispc_ops->write_irqenable(tidss->dispc, 0);
+ tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+void tidss_irq_resume(struct drm_device *ddev)
+{
+ struct tidss_device *tidss = ddev->dev_private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tidss->wait_lock, flags);
+ tidss_irq_update(ddev);
+ spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
new file mode 100644
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_IRQ_H__
+#define __TIDSS_IRQ_H__
+
+#include <linux/types.h>
+
+struct drm_crtc;
+struct drm_device;
+
+void tidss_irq_enable_vblank(struct drm_crtc *crtc);
+void tidss_irq_disable_vblank(struct drm_crtc *crtc);
+
+void tidss_irq_preinstall(struct drm_device *ddev);
+int tidss_irq_postinstall(struct drm_device *ddev);
+void tidss_irq_uninstall(struct drm_device *ddev);
+irqreturn_t tidss_irq_handler(int irq, void *arg);
+
+void tidss_irq_resume(struct drm_device *ddev);
+
+#endif
new file mode 100644
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_kms.h"
+#include "tidss_plane.h"
+
+static void tidss_atomic_commit_tail(struct drm_atomic_state *old_state)
+{
+ struct drm_device *ddev = old_state->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ tidss->dispc_ops->runtime_get(tidss->dispc);
+
+ drm_atomic_helper_commit_modeset_disables(ddev, old_state);
+ drm_atomic_helper_commit_planes(ddev, old_state, 0);
+ drm_atomic_helper_commit_modeset_enables(ddev, old_state);
+
+ drm_atomic_helper_commit_hw_done(old_state);
+ drm_atomic_helper_wait_for_flip_done(ddev, old_state);
+
+ drm_atomic_helper_cleanup_planes(ddev, old_state);
+
+ tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
+ .atomic_commit_tail = tidss_atomic_commit_tail,
+};
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static int tidss_modeset_init_properties(struct tidss_device *tidss)
+{
+ return 0;
+}
+
+int tidss_modeset_init(struct tidss_device *tidss)
+{
+ struct drm_device *ddev = tidss->ddev;
+ unsigned int i;
+ int ret;
+
+ dev_dbg(tidss->dev, "%s\n", __func__);
+
+ drm_mode_config_init(ddev);
+
+ ddev->mode_config.min_width = 8;
+ ddev->mode_config.min_height = 8;
+ ddev->mode_config.max_width = 8096;
+ ddev->mode_config.max_height = 8096;
+ ddev->mode_config.normalize_zpos = true;
+ ddev->mode_config.funcs = &mode_config_funcs;
+ ddev->mode_config.helper_private = &mode_config_helper_funcs;
+
+ ret = tidss_modeset_init_properties(tidss);
+ if (ret < 0)
+ return ret;
+
+ ret = tidss->dispc_ops->modeset_init(tidss->dispc);
+ if (ret)
+ return ret;
+
+ ret = drm_vblank_init(ddev, tidss->num_crtcs);
+ if (ret)
+ return ret;
+
+ /* Start with vertical blanking interrupt reporting disabled. */
+ for (i = 0; i < tidss->num_crtcs; ++i)
+ drm_crtc_vblank_reset(tidss->crtcs[i]);
+
+ drm_mode_config_reset(ddev);
+
+ dev_dbg(tidss->dev, "%s done\n", __func__);
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_KMS_H__
+#define __TIDSS_KMS_H__
+
+struct tidss_device;
+
+int tidss_modeset_init(struct tidss_device *tidss);
+
+#endif
new file mode 100644
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_plane.h"
+
+dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *gem;
+ u32 x = state->src_x >> 16;
+ u32 y = state->src_y >> 16;
+
+ gem = drm_fb_cma_get_gem_obj(state->fb, 0);
+
+ return gem->paddr + fb->offsets[0] + x * fb->format->cpp[0] +
+ y * fb->pitches[0];
+}
+
+dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *gem;
+ u32 x = state->src_x >> 16;
+ u32 y = state->src_y >> 16;
+
+ if (WARN_ON(state->fb->format->num_planes != 2))
+ return 0;
+
+ gem = drm_fb_cma_get_gem_obj(fb, 1);
+
+ return gem->paddr + fb->offsets[1] +
+ (x * fb->format->cpp[1] / fb->format->hsub) +
+ (y * fb->pitches[1] / fb->format->vsub);
+}
+
+static int tidss_plane_atomic_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ struct drm_crtc_state *crtc_state;
+ struct tidss_plane *tplane = to_tidss_plane(plane);
+ const struct drm_format_info *finfo;
+ u32 hw_videoport;
+ int ret;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ if (!state->crtc) {
+ /*
+ * The visible field is not reset by the DRM core but only
+ * updated by drm_plane_helper_check_state(), set it manually.
+ */
+ state->visible = false;
+ return 0;
+ }
+
+ crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ ret = drm_atomic_helper_check_plane_state(state, crtc_state,
+ 0,
+ INT_MAX,
+ true, true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The HW is only able to start drawing at subpixel boundary
+ * (the two first checks bellow). At the end of a row the HW
+ * can only jump integer number of subpixels forward to
+ * beginning of next row. So we can only show picture with
+ * integer subpixel width (the third check). However, after
+ * reaching the end of the drawn picture the drawing starts
+ * again at the absolute memory address where top left corner
+ * position of the drawn picture is (so there is no need to
+ * check for odd height).
+ */
+
+ finfo = drm_format_info(state->fb->format->format);
+
+ if ((state->src_x >> 16) % finfo->hsub != 0) {
+ dev_dbg(ddev->dev,
+ "%s: x-position %u not divisible subpixel size %u\n",
+ __func__, (state->src_x >> 16), finfo->hsub);
+ return -EINVAL;
+ }
+
+ if ((state->src_y >> 16) % finfo->vsub != 0) {
+ dev_dbg(ddev->dev,
+ "%s: y-position %u not divisible subpixel size %u\n",
+ __func__, (state->src_y >> 16), finfo->vsub);
+ return -EINVAL;
+ }
+
+ if ((state->src_w >> 16) % finfo->hsub != 0) {
+ dev_dbg(ddev->dev,
+ "%s: src width %u not divisible by subpixel size %u\n",
+ __func__, (state->src_w >> 16), finfo->hsub);
+ return -EINVAL;
+ }
+
+ if (!state->visible)
+ return 0;
+
+ hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport;
+
+ return tidss->dispc_ops->plane_check(tidss->dispc,
+ tplane->hw_plane_id,
+ state, hw_videoport);
+}
+
+static void tidss_plane_atomic_update(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ struct tidss_plane *tplane = to_tidss_plane(plane);
+ struct drm_plane_state *state = plane->state;
+ u32 hw_videoport;
+ int ret;
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ if (!state->visible) {
+ tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id,
+ false);
+ return;
+ }
+
+ hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport;
+
+ ret = tidss->dispc_ops->plane_setup(tidss->dispc, tplane->hw_plane_id,
+ state, hw_videoport);
+
+ if (ret) {
+ dev_err(plane->dev->dev, "Failed to setup plane %d\n",
+ tplane->hw_plane_id);
+ tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id,
+ false);
+ return;
+ }
+
+ tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, true);
+}
+
+static void tidss_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct tidss_device *tidss = ddev->dev_private;
+ struct tidss_plane *tplane = to_tidss_plane(plane);
+
+ dev_dbg(ddev->dev, "%s\n", __func__);
+
+ tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, false);
+}
+
+static const struct drm_plane_helper_funcs tidss_plane_helper_funcs = {
+ .atomic_check = tidss_plane_atomic_check,
+ .atomic_update = tidss_plane_atomic_update,
+ .atomic_disable = tidss_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs tidss_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .destroy = drm_plane_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
+ u32 hw_plane_id, u32 plane_type,
+ u32 crtc_mask, const u32 *formats,
+ u32 num_formats)
+{
+ const struct tidss_plane_feat *pfeat;
+ struct tidss_plane *tplane;
+ enum drm_plane_type type;
+ u32 possible_crtcs;
+ u32 num_planes = tidss->dispc_ops->get_num_planes(tidss->dispc);
+ int ret;
+
+ pfeat = tidss->dispc_ops->plane_feat(tidss->dispc, hw_plane_id);
+
+ tplane = devm_kzalloc(tidss->dev, sizeof(*tplane), GFP_KERNEL);
+ if (!tplane)
+ return ERR_PTR(-ENOMEM);
+
+ tplane->hw_plane_id = hw_plane_id;
+
+ possible_crtcs = crtc_mask;
+ type = plane_type;
+
+ ret = drm_universal_plane_init(tidss->ddev, &tplane->plane,
+ possible_crtcs,
+ &tidss_plane_funcs,
+ formats, num_formats,
+ NULL, type, NULL);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ drm_plane_helper_add(&tplane->plane, &tidss_plane_helper_funcs);
+ if (num_planes > 1)
+ drm_plane_create_zpos_property(&tplane->plane, hw_plane_id, 0,
+ num_planes - 1);
+
+ ret = drm_plane_create_color_properties(&tplane->plane,
+ pfeat->color.encodings,
+ pfeat->color.ranges,
+ pfeat->color.default_encoding,
+ pfeat->color.default_range);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (pfeat->blend.global_alpha) {
+ ret = drm_plane_create_alpha_property(&tplane->plane);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ return tplane;
+}
new file mode 100644
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_PLANE_H__
+#define __TIDSS_PLANE_H__
+
+#include "tidss_dispc.h"
+
+#define to_tidss_plane(p) container_of((p), struct tidss_plane, plane)
+
+struct tidss_plane {
+ struct drm_plane plane;
+
+ u32 hw_plane_id;
+
+};
+
+struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
+ u32 hw_plane_id, u32 plane_type,
+ u32 crtc_mask, const u32 *formats,
+ u32 num_formats);
+
+dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state);
+dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state);
+
+#endif