From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/omapdrm/Kconfig | 137 + drivers/gpu/drm/omapdrm/Makefile | 35 + drivers/gpu/drm/omapdrm/TODO | 23 + drivers/gpu/drm/omapdrm/dss/base.c | 281 ++ drivers/gpu/drm/omapdrm/dss/dispc.c | 4776 +++++++++++++++++++++++++++ drivers/gpu/drm/omapdrm/dss/dispc.h | 909 +++++ drivers/gpu/drm/omapdrm/dss/dispc_coefs.c | 312 ++ drivers/gpu/drm/omapdrm/dss/dpi.c | 752 +++++ drivers/gpu/drm/omapdrm/dss/dsi.c | 5104 +++++++++++++++++++++++++++++ drivers/gpu/drm/omapdrm/dss/dsi.h | 454 +++ drivers/gpu/drm/omapdrm/dss/dss.c | 1641 ++++++++++ drivers/gpu/drm/omapdrm/dss/dss.h | 548 ++++ drivers/gpu/drm/omapdrm/dss/hdmi.h | 385 +++ drivers/gpu/drm/omapdrm/dss/hdmi4.c | 856 +++++ drivers/gpu/drm/omapdrm/dss/hdmi4_cec.c | 353 ++ drivers/gpu/drm/omapdrm/dss/hdmi4_cec.h | 43 + drivers/gpu/drm/omapdrm/dss/hdmi4_core.c | 888 +++++ drivers/gpu/drm/omapdrm/dss/hdmi4_core.h | 268 ++ drivers/gpu/drm/omapdrm/dss/hdmi5.c | 822 +++++ drivers/gpu/drm/omapdrm/dss/hdmi5_core.c | 863 +++++ drivers/gpu/drm/omapdrm/dss/hdmi5_core.h | 295 ++ drivers/gpu/drm/omapdrm/dss/hdmi_common.c | 149 + drivers/gpu/drm/omapdrm/dss/hdmi_phy.c | 195 ++ drivers/gpu/drm/omapdrm/dss/hdmi_pll.c | 187 ++ drivers/gpu/drm/omapdrm/dss/hdmi_wp.c | 297 ++ drivers/gpu/drm/omapdrm/dss/omapdss.h | 316 ++ drivers/gpu/drm/omapdrm/dss/output.c | 140 + drivers/gpu/drm/omapdrm/dss/pll.c | 572 ++++ drivers/gpu/drm/omapdrm/dss/sdi.c | 396 +++ drivers/gpu/drm/omapdrm/dss/venc.c | 922 ++++++ drivers/gpu/drm/omapdrm/dss/video-pll.c | 193 ++ drivers/gpu/drm/omapdrm/omap_crtc.c | 849 +++++ drivers/gpu/drm/omapdrm/omap_crtc.h | 34 + drivers/gpu/drm/omapdrm/omap_debugfs.c | 97 + drivers/gpu/drm/omapdrm/omap_dmm_priv.h | 193 ++ drivers/gpu/drm/omapdrm/omap_dmm_tiler.c | 1224 +++++++ drivers/gpu/drm/omapdrm/omap_dmm_tiler.h | 133 + drivers/gpu/drm/omapdrm/omap_drv.c | 909 +++++ drivers/gpu/drm/omapdrm/omap_drv.h | 112 + drivers/gpu/drm/omapdrm/omap_encoder.c | 144 + drivers/gpu/drm/omapdrm/omap_encoder.h | 19 + drivers/gpu/drm/omapdrm/omap_fb.c | 457 +++ drivers/gpu/drm/omapdrm/omap_fb.h | 38 + drivers/gpu/drm/omapdrm/omap_fbdev.c | 294 ++ drivers/gpu/drm/omapdrm/omap_fbdev.h | 30 + drivers/gpu/drm/omapdrm/omap_gem.c | 1524 +++++++++ drivers/gpu/drm/omapdrm/omap_gem.h | 86 + drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c | 146 + drivers/gpu/drm/omapdrm/omap_irq.c | 303 ++ drivers/gpu/drm/omapdrm/omap_irq.h | 29 + drivers/gpu/drm/omapdrm/omap_overlay.c | 212 ++ drivers/gpu/drm/omapdrm/omap_overlay.h | 35 + drivers/gpu/drm/omapdrm/omap_plane.c | 591 ++++ drivers/gpu/drm/omapdrm/omap_plane.h | 27 + drivers/gpu/drm/omapdrm/tcm-sita.c | 250 ++ drivers/gpu/drm/omapdrm/tcm.h | 330 ++ 56 files changed, 31178 insertions(+) create mode 100644 drivers/gpu/drm/omapdrm/Kconfig create mode 100644 drivers/gpu/drm/omapdrm/Makefile create mode 100644 drivers/gpu/drm/omapdrm/TODO create mode 100644 drivers/gpu/drm/omapdrm/dss/base.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dispc.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dispc.h create mode 100644 drivers/gpu/drm/omapdrm/dss/dispc_coefs.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dpi.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dsi.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dsi.h create mode 100644 drivers/gpu/drm/omapdrm/dss/dss.c create mode 100644 drivers/gpu/drm/omapdrm/dss/dss.h create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi.h create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi4.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi4_cec.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi4_cec.h create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi4_core.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi4_core.h create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5_core.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi5_core.h create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi_common.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi_phy.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi_pll.c create mode 100644 drivers/gpu/drm/omapdrm/dss/hdmi_wp.c create mode 100644 drivers/gpu/drm/omapdrm/dss/omapdss.h create mode 100644 drivers/gpu/drm/omapdrm/dss/output.c create mode 100644 drivers/gpu/drm/omapdrm/dss/pll.c create mode 100644 drivers/gpu/drm/omapdrm/dss/sdi.c create mode 100644 drivers/gpu/drm/omapdrm/dss/venc.c create mode 100644 drivers/gpu/drm/omapdrm/dss/video-pll.c create mode 100644 drivers/gpu/drm/omapdrm/omap_crtc.c create mode 100644 drivers/gpu/drm/omapdrm/omap_crtc.h create mode 100644 drivers/gpu/drm/omapdrm/omap_debugfs.c create mode 100644 drivers/gpu/drm/omapdrm/omap_dmm_priv.h create mode 100644 drivers/gpu/drm/omapdrm/omap_dmm_tiler.c create mode 100644 drivers/gpu/drm/omapdrm/omap_dmm_tiler.h create mode 100644 drivers/gpu/drm/omapdrm/omap_drv.c create mode 100644 drivers/gpu/drm/omapdrm/omap_drv.h create mode 100644 drivers/gpu/drm/omapdrm/omap_encoder.c create mode 100644 drivers/gpu/drm/omapdrm/omap_encoder.h create mode 100644 drivers/gpu/drm/omapdrm/omap_fb.c create mode 100644 drivers/gpu/drm/omapdrm/omap_fb.h create mode 100644 drivers/gpu/drm/omapdrm/omap_fbdev.c create mode 100644 drivers/gpu/drm/omapdrm/omap_fbdev.h create mode 100644 drivers/gpu/drm/omapdrm/omap_gem.c create mode 100644 drivers/gpu/drm/omapdrm/omap_gem.h create mode 100644 drivers/gpu/drm/omapdrm/omap_gem_dmabuf.c create mode 100644 drivers/gpu/drm/omapdrm/omap_irq.c create mode 100644 drivers/gpu/drm/omapdrm/omap_irq.h create mode 100644 drivers/gpu/drm/omapdrm/omap_overlay.c create mode 100644 drivers/gpu/drm/omapdrm/omap_overlay.h create mode 100644 drivers/gpu/drm/omapdrm/omap_plane.c create mode 100644 drivers/gpu/drm/omapdrm/omap_plane.h create mode 100644 drivers/gpu/drm/omapdrm/tcm-sita.c create mode 100644 drivers/gpu/drm/omapdrm/tcm.h (limited to 'drivers/gpu/drm/omapdrm') diff --git a/drivers/gpu/drm/omapdrm/Kconfig b/drivers/gpu/drm/omapdrm/Kconfig new file mode 100644 index 000000000..9d4016bd0 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/Kconfig @@ -0,0 +1,137 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_OMAP + tristate "OMAP DRM" + depends on MMU + depends on DRM && OF + depends on ARCH_OMAP2PLUS || (COMPILE_TEST && PAGE_SIZE_LESS_THAN_64KB) + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select FB_DMAMEM_HELPERS_DEFERRED if DRM_FBDEV_EMULATION + select VIDEOMODE_HELPERS + select HDMI + default n + help + DRM display driver for OMAP2/3/4 based boards. + +if DRM_OMAP + +config OMAP2_DSS_DEBUG + bool "Debug support" + default n + help + This enables printing of debug messages. Alternatively, debug messages + can also be enabled by setting CONFIG_DYNAMIC_DEBUG and then setting + appropriate flags in /dynamic_debug/control. + +config OMAP2_DSS_DEBUGFS + bool "Debugfs filesystem support" + depends on DEBUG_FS + default n + help + This enables debugfs for OMAPDSS at /omapdss. This enables + querying about clock configuration and register configuration of dss, + dispc, dsi, hdmi and rfbi. + +config OMAP2_DSS_COLLECT_IRQ_STATS + bool "Collect DSS IRQ statistics" + depends on OMAP2_DSS_DEBUGFS + default n + help + Collect DSS IRQ statistics, printable via debugfs. + + The statistics can be found from + /omapdss/dispc_irq for DISPC interrupts, and + /omapdss/dsi_irq for DSI interrupts. + +config OMAP2_DSS_DPI + bool "DPI support" + default y + help + DPI Interface. This is the Parallel Display Interface. + +config OMAP2_DSS_VENC + bool "VENC support" + default y + help + OMAP Video Encoder support for S-Video and composite TV-out. + +config OMAP2_DSS_HDMI_COMMON + bool + +config OMAP4_DSS_HDMI + bool "HDMI support for OMAP4" + default y + select OMAP2_DSS_HDMI_COMMON + help + HDMI support for OMAP4 based SoCs. + +config OMAP4_DSS_HDMI_CEC + bool "Enable HDMI CEC support for OMAP4" + depends on OMAP4_DSS_HDMI + select CEC_CORE + default y + help + When selected the HDMI transmitter will support the CEC feature. + +config OMAP5_DSS_HDMI + bool "HDMI support for OMAP5" + default n + select OMAP2_DSS_HDMI_COMMON + help + HDMI Interface for OMAP5 and similar cores. This adds the High + Definition Multimedia Interface. See http://www.hdmi.org/ for HDMI + specification. + +config OMAP2_DSS_SDI + bool "SDI support" + default n + help + SDI (Serial Display Interface) support. + + SDI is a high speed one-way display serial bus between the host + processor and a display. + +config OMAP2_DSS_DSI + bool "DSI support" + default n + select DRM_MIPI_DSI + help + MIPI DSI (Display Serial Interface) support. + + DSI is a high speed half-duplex serial interface between the host + processor and a peripheral, such as a display or a framebuffer chip. + + See http://www.mipi.org/ for DSI specifications. + +config OMAP2_DSS_MIN_FCK_PER_PCK + int "Minimum FCK/PCK ratio (for scaling)" + range 0 32 + default 0 + help + This can be used to adjust the minimum FCK/PCK ratio. + + With this you can make sure that DISPC FCK is at least + n x PCK. Video plane scaling requires higher FCK than + normally. + + If this is set to 0, there's no extra constraint on the + DISPC FCK. However, the FCK will at minimum be + 2xPCK (if active matrix) or 3xPCK (if passive matrix). + + Max FCK is 173MHz, so this doesn't work if your PCK + is very high. + +config OMAP2_DSS_SLEEP_AFTER_VENC_RESET + bool "Sleep 20ms after VENC reset" + default y + help + There is a 20ms sleep after VENC reset which seemed to fix the + reset. The reason for the bug is unclear, and it's also unclear + on what platforms this happens. + + This option enables the sleep, and is enabled by default. You can + disable the sleep if it doesn't cause problems on your platform. + +endif diff --git a/drivers/gpu/drm/omapdrm/Makefile b/drivers/gpu/drm/omapdrm/Makefile new file mode 100644 index 000000000..710b4e0ab --- /dev/null +++ b/drivers/gpu/drm/omapdrm/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) +# + +omapdrm-y := omap_drv.o \ + omap_irq.o \ + omap_debugfs.o \ + omap_crtc.o \ + omap_plane.o \ + omap_overlay.o \ + omap_encoder.o \ + omap_fb.o \ + omap_gem.o \ + omap_gem_dmabuf.o \ + omap_dmm_tiler.o \ + tcm-sita.o + +omapdrm-$(CONFIG_DRM_FBDEV_EMULATION) += omap_fbdev.o + +omapdrm-y += dss/base.o dss/output.o dss/dss.o dss/dispc.o \ + dss/dispc_coefs.o dss/pll.o dss/video-pll.o +omapdrm-$(CONFIG_OMAP2_DSS_DPI) += dss/dpi.o +omapdrm-$(CONFIG_OMAP2_DSS_VENC) += dss/venc.o +omapdrm-$(CONFIG_OMAP2_DSS_SDI) += dss/sdi.o +omapdrm-$(CONFIG_OMAP2_DSS_DSI) += dss/dsi.o +omapdrm-$(CONFIG_OMAP2_DSS_HDMI_COMMON) += dss/hdmi_common.o dss/hdmi_wp.o \ + dss/hdmi_pll.o dss/hdmi_phy.o +omapdrm-$(CONFIG_OMAP4_DSS_HDMI) += dss/hdmi4.o dss/hdmi4_core.o +omapdrm-$(CONFIG_OMAP4_DSS_HDMI_CEC) += dss/hdmi4_cec.o +omapdrm-$(CONFIG_OMAP5_DSS_HDMI) += dss/hdmi5.o dss/hdmi5_core.o +ccflags-$(CONFIG_OMAP2_DSS_DEBUG) += -DDEBUG + +obj-$(CONFIG_DRM_OMAP) += omapdrm.o diff --git a/drivers/gpu/drm/omapdrm/TODO b/drivers/gpu/drm/omapdrm/TODO new file mode 100644 index 000000000..4d8c18aa5 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/TODO @@ -0,0 +1,23 @@ +TODO +. Where should we do eviction (detatch_pages())? We aren't necessarily + accessing the pages via a GART, so maybe we need some other threshold + to put a cap on the # of pages that can be pin'd. + . Use mm_shrinker to trigger unpinning pages. + . This is mainly theoretical since most of these devices don't actually + have swap or harddrive. +. GEM/shmem backed pages can have existing mappings (kernel linear map, + etc..), which isn't really ideal. +. Revisit GEM sync object infrastructure.. TTM has some framework for this + already. Possibly this could be refactored out and made more common? + There should be some way to do this with less wheel-reinvention. + . This can be handled by the dma-buf fence/reservation stuff when it + lands + +Userspace: +. git://anongit.freedesktop.org/xorg/driver/xf86-video-omap + +Currently tested on +. OMAP3530 beagleboard +. OMAP4430 pandaboard +. OMAP4460 pandaboard +. OMAP5432 uEVM diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c new file mode 100644 index 000000000..a4ac113e1 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/base.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * OMAP Display Subsystem Base + * + * Copyright (C) 2015-2017 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dss.h" +#include "omapdss.h" + +struct dispc_device *dispc_get_dispc(struct dss_device *dss) +{ + return dss->dispc; +} + +/* ----------------------------------------------------------------------------- + * OMAP DSS Devices Handling + */ + +static LIST_HEAD(omapdss_devices_list); +static DEFINE_MUTEX(omapdss_devices_lock); + +void omapdss_device_register(struct omap_dss_device *dssdev) +{ + mutex_lock(&omapdss_devices_lock); + list_add_tail(&dssdev->list, &omapdss_devices_list); + mutex_unlock(&omapdss_devices_lock); +} + +void omapdss_device_unregister(struct omap_dss_device *dssdev) +{ + mutex_lock(&omapdss_devices_lock); + list_del(&dssdev->list); + mutex_unlock(&omapdss_devices_lock); +} + +static bool omapdss_device_is_registered(struct device_node *node) +{ + struct omap_dss_device *dssdev; + bool found = false; + + mutex_lock(&omapdss_devices_lock); + + list_for_each_entry(dssdev, &omapdss_devices_list, list) { + if (dssdev->dev->of_node == node) { + found = true; + break; + } + } + + mutex_unlock(&omapdss_devices_lock); + return found; +} + +struct omap_dss_device *omapdss_device_get(struct omap_dss_device *dssdev) +{ + if (get_device(dssdev->dev) == NULL) + return NULL; + + return dssdev; +} + +void omapdss_device_put(struct omap_dss_device *dssdev) +{ + put_device(dssdev->dev); +} + +struct omap_dss_device *omapdss_find_device_by_node(struct device_node *node) +{ + struct omap_dss_device *dssdev; + + list_for_each_entry(dssdev, &omapdss_devices_list, list) { + if (dssdev->dev->of_node == node) + return omapdss_device_get(dssdev); + } + + return NULL; +} + +/* + * Search for the next output device starting at @from. Release the reference to + * the @from device, and acquire a reference to the returned device if found. + */ +struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from) +{ + struct omap_dss_device *dssdev; + struct list_head *list; + + mutex_lock(&omapdss_devices_lock); + + if (list_empty(&omapdss_devices_list)) { + dssdev = NULL; + goto done; + } + + /* + * Start from the from entry if given or from omapdss_devices_list + * otherwise. + */ + list = from ? &from->list : &omapdss_devices_list; + + list_for_each_entry(dssdev, list, list) { + /* + * Stop if we reach the omapdss_devices_list, that's the end of + * the list. + */ + if (&dssdev->list == &omapdss_devices_list) { + dssdev = NULL; + goto done; + } + + if (dssdev->id && dssdev->bridge) + goto done; + } + + dssdev = NULL; + +done: + if (from) + omapdss_device_put(from); + if (dssdev) + omapdss_device_get(dssdev); + + mutex_unlock(&omapdss_devices_lock); + return dssdev; +} + +static bool omapdss_device_is_connected(struct omap_dss_device *dssdev) +{ + return dssdev->dss; +} + +int omapdss_device_connect(struct dss_device *dss, + struct omap_dss_device *dst) +{ + dev_dbg(&dss->pdev->dev, "connect(%s)\n", + dst ? dev_name(dst->dev) : "NULL"); + + if (!dst) + return -EINVAL; + + if (omapdss_device_is_connected(dst)) + return -EBUSY; + + dst->dss = dss; + + return 0; +} + +void omapdss_device_disconnect(struct dss_device *dss, + struct omap_dss_device *dst) +{ + dev_dbg(&dss->pdev->dev, "disconnect(%s)\n", + dst ? dev_name(dst->dev) : "NULL"); + + if (WARN_ON(!dst)) + return; + + if (!dst->id && !omapdss_device_is_connected(dst)) { + WARN_ON(1); + return; + } + + dst->dss = NULL; +} + +/* ----------------------------------------------------------------------------- + * Components Handling + */ + +static struct list_head omapdss_comp_list; + +struct omapdss_comp_node { + struct list_head list; + struct device_node *node; + bool dss_core_component; + const char *compat; +}; + +static bool omapdss_list_contains(const struct device_node *node) +{ + struct omapdss_comp_node *comp; + + list_for_each_entry(comp, &omapdss_comp_list, list) { + if (comp->node == node) + return true; + } + + return false; +} + +static void omapdss_walk_device(struct device *dev, struct device_node *node, + bool dss_core) +{ + struct omapdss_comp_node *comp; + struct device_node *n; + const char *compat; + int ret; + + ret = of_property_read_string(node, "compatible", &compat); + if (ret < 0) + return; + + comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); + if (comp) { + comp->node = node; + comp->dss_core_component = dss_core; + comp->compat = compat; + list_add(&comp->list, &omapdss_comp_list); + } + + /* + * of_graph_get_remote_port_parent() prints an error if there is no + * port/ports node. To avoid that, check first that there's the node. + */ + n = of_get_child_by_name(node, "ports"); + if (!n) + n = of_get_child_by_name(node, "port"); + if (!n) + return; + + of_node_put(n); + + for_each_endpoint_of_node(node, n) { + struct device_node *pn = of_graph_get_remote_port_parent(n); + + if (!pn) + continue; + + if (!of_device_is_available(pn) || omapdss_list_contains(pn)) { + of_node_put(pn); + continue; + } + + omapdss_walk_device(dev, pn, false); + } +} + +void omapdss_gather_components(struct device *dev) +{ + struct device_node *child; + + INIT_LIST_HEAD(&omapdss_comp_list); + + omapdss_walk_device(dev, dev->of_node, true); + + for_each_available_child_of_node(dev->of_node, child) + omapdss_walk_device(dev, child, true); +} + +static bool omapdss_component_is_loaded(struct omapdss_comp_node *comp) +{ + if (comp->dss_core_component) + return true; + if (!strstarts(comp->compat, "omapdss,")) + return true; + if (omapdss_device_is_registered(comp->node)) + return true; + + return false; +} + +bool omapdss_stack_is_ready(void) +{ + struct omapdss_comp_node *comp; + + list_for_each_entry(comp, &omapdss_comp_list, list) { + if (!omapdss_component_is_loaded(comp)) + return false; + } + + return true; +} diff --git a/drivers/gpu/drm/omapdrm/dss/dispc.c b/drivers/gpu/drm/omapdrm/dss/dispc.c new file mode 100644 index 000000000..78b74217f --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dispc.c @@ -0,0 +1,4776 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + */ + +#define DSS_SUBSYS_NAME "DISPC" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "omapdss.h" +#include "dss.h" +#include "dispc.h" + +struct dispc_device; + +/* DISPC */ +#define DISPC_SZ_REGS SZ_4K + +enum omap_burst_size { + BURST_SIZE_X2 = 0, + BURST_SIZE_X4 = 1, + BURST_SIZE_X8 = 2, +}; + +#define REG_GET(dispc, idx, start, end) \ + FLD_GET(dispc_read_reg(dispc, idx), start, end) + +#define REG_FLD_MOD(dispc, idx, val, start, end) \ + dispc_write_reg(dispc, idx, \ + FLD_MOD(dispc_read_reg(dispc, idx), val, start, end)) + +/* DISPC has feature id */ +enum dispc_feature_id { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_MGR_LCD2, + FEAT_MGR_LCD3, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + /* Independent core clk divider */ + FEAT_CORE_CLK_DIV, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + /* An unknown HW bug causing the normal FIFO thresholds not to work */ + FEAT_OMAP3_DSI_FIFO_BUG, + FEAT_BURST_2D, + FEAT_MFLAG, +}; + +struct dispc_features { + u8 sw_start; + u8 fp_start; + u8 bp_start; + u16 sw_max; + u16 vp_max; + u16 hp_max; + u8 mgr_width_start; + u8 mgr_height_start; + u16 mgr_width_max; + u16 mgr_height_max; + u16 ovl_width_max; + u16 ovl_height_max; + unsigned long max_lcd_pclk; + unsigned long max_tv_pclk; + unsigned int max_downscale; + unsigned int max_line_width; + unsigned int min_pcd; + int (*calc_scaling)(struct dispc_device *dispc, + unsigned long pclk, unsigned long lclk, + const struct videomode *vm, + u16 width, u16 height, u16 out_width, u16 out_height, + u32 fourcc, bool *five_taps, + int *x_predecim, int *y_predecim, int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, bool mem_to_mem); + unsigned long (*calc_core_clk) (unsigned long pclk, + u16 width, u16 height, u16 out_width, u16 out_height, + bool mem_to_mem); + u8 num_fifos; + const enum dispc_feature_id *features; + unsigned int num_features; + const struct dss_reg_field *reg_fields; + const unsigned int num_reg_fields; + const enum omap_overlay_caps *overlay_caps; + const u32 **supported_color_modes; + const u32 *supported_scaler_color_modes; + unsigned int num_mgrs; + unsigned int num_ovls; + unsigned int buffer_size_unit; + unsigned int burst_size_unit; + + /* swap GFX & WB fifos */ + bool gfx_fifo_workaround:1; + + /* no DISPC_IRQ_FRAMEDONETV on this SoC */ + bool no_framedone_tv:1; + + /* revert to the OMAP4 mechanism of DISPC Smart Standby operation */ + bool mstandby_workaround:1; + + bool set_max_preload:1; + + /* PIXEL_INC is not added to the last pixel of a line */ + bool last_pixel_inc_missing:1; + + /* POL_FREQ has ALIGN bit */ + bool supports_sync_align:1; + + bool has_writeback:1; + + bool supports_double_pixel:1; + + /* + * Field order for VENC is different than HDMI. We should handle this in + * some intelligent manner, but as the SoCs have either HDMI or VENC, + * never both, we can just use this flag for now. + */ + bool reverse_ilace_field_order:1; + + bool has_gamma_table:1; + + bool has_gamma_i734_bug:1; +}; + +#define DISPC_MAX_NR_FIFOS 5 +#define DISPC_MAX_CHANNEL_GAMMA 4 + +struct dispc_device { + struct platform_device *pdev; + void __iomem *base; + struct dss_device *dss; + + struct dss_debugfs_entry *debugfs; + + int irq; + irq_handler_t user_handler; + void *user_data; + + unsigned long core_clk_rate; + unsigned long tv_pclk_rate; + + u32 fifo_size[DISPC_MAX_NR_FIFOS]; + /* maps which plane is using a fifo. fifo-id -> plane-id */ + int fifo_assignment[DISPC_MAX_NR_FIFOS]; + + bool ctx_valid; + u32 ctx[DISPC_SZ_REGS / sizeof(u32)]; + + u32 *gamma_table[DISPC_MAX_CHANNEL_GAMMA]; + + const struct dispc_features *feat; + + bool is_enabled; + + struct regmap *syscon_pol; + u32 syscon_pol_offset; +}; + +enum omap_color_component { + /* used for all color formats for OMAP3 and earlier + * and for RGB and Y color component on OMAP4 + */ + DISPC_COLOR_COMPONENT_RGB_Y = 1 << 0, + /* used for UV component for + * DRM_FORMAT_YUYV, DRM_FORMAT_UYVY, DRM_FORMAT_NV12 + * color formats on OMAP4 + */ + DISPC_COLOR_COMPONENT_UV = 1 << 1, +}; + +enum mgr_reg_fields { + DISPC_MGR_FLD_ENABLE, + DISPC_MGR_FLD_STNTFT, + DISPC_MGR_FLD_GO, + DISPC_MGR_FLD_TFTDATALINES, + DISPC_MGR_FLD_STALLMODE, + DISPC_MGR_FLD_TCKENABLE, + DISPC_MGR_FLD_TCKSELECTION, + DISPC_MGR_FLD_CPR, + DISPC_MGR_FLD_FIFOHANDCHECK, + /* used to maintain a count of the above fields */ + DISPC_MGR_FLD_NUM, +}; + +/* DISPC register field id */ +enum dispc_feat_reg_field { + FEAT_REG_FIRHINC, + FEAT_REG_FIRVINC, + FEAT_REG_FIFOHIGHTHRESHOLD, + FEAT_REG_FIFOLOWTHRESHOLD, + FEAT_REG_FIFOSIZE, + FEAT_REG_HORIZONTALACCU, + FEAT_REG_VERTICALACCU, +}; + +struct dispc_reg_field { + u16 reg; + u8 high; + u8 low; +}; + +struct dispc_gamma_desc { + u32 len; + u32 bits; + u16 reg; + bool has_index; +}; + +static const struct { + const char *name; + u32 vsync_irq; + u32 framedone_irq; + u32 sync_lost_irq; + struct dispc_gamma_desc gamma; + struct dispc_reg_field reg_desc[DISPC_MGR_FLD_NUM]; +} mgr_desc[] = { + [OMAP_DSS_CHANNEL_LCD] = { + .name = "LCD", + .vsync_irq = DISPC_IRQ_VSYNC, + .framedone_irq = DISPC_IRQ_FRAMEDONE, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST, + .gamma = { + .len = 256, + .bits = 8, + .reg = DISPC_GAMMA_TABLE0, + .has_index = true, + }, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL, 0, 0 }, + [DISPC_MGR_FLD_STNTFT] = { DISPC_CONTROL, 3, 3 }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL, 5, 5 }, + [DISPC_MGR_FLD_TFTDATALINES] = { DISPC_CONTROL, 9, 8 }, + [DISPC_MGR_FLD_STALLMODE] = { DISPC_CONTROL, 11, 11 }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG, 10, 10 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG, 11, 11 }, + [DISPC_MGR_FLD_CPR] = { DISPC_CONFIG, 15, 15 }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG, 16, 16 }, + }, + }, + [OMAP_DSS_CHANNEL_DIGIT] = { + .name = "DIGIT", + .vsync_irq = DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_EVSYNC_EVEN, + .framedone_irq = DISPC_IRQ_FRAMEDONETV, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST_DIGIT, + .gamma = { + .len = 1024, + .bits = 10, + .reg = DISPC_GAMMA_TABLE2, + .has_index = false, + }, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL, 1, 1 }, + [DISPC_MGR_FLD_STNTFT] = { }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL, 6, 6 }, + [DISPC_MGR_FLD_TFTDATALINES] = { }, + [DISPC_MGR_FLD_STALLMODE] = { }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG, 12, 12 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG, 13, 13 }, + [DISPC_MGR_FLD_CPR] = { }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG, 16, 16 }, + }, + }, + [OMAP_DSS_CHANNEL_LCD2] = { + .name = "LCD2", + .vsync_irq = DISPC_IRQ_VSYNC2, + .framedone_irq = DISPC_IRQ_FRAMEDONE2, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST2, + .gamma = { + .len = 256, + .bits = 8, + .reg = DISPC_GAMMA_TABLE1, + .has_index = true, + }, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL2, 0, 0 }, + [DISPC_MGR_FLD_STNTFT] = { DISPC_CONTROL2, 3, 3 }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL2, 5, 5 }, + [DISPC_MGR_FLD_TFTDATALINES] = { DISPC_CONTROL2, 9, 8 }, + [DISPC_MGR_FLD_STALLMODE] = { DISPC_CONTROL2, 11, 11 }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG2, 10, 10 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG2, 11, 11 }, + [DISPC_MGR_FLD_CPR] = { DISPC_CONFIG2, 15, 15 }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG2, 16, 16 }, + }, + }, + [OMAP_DSS_CHANNEL_LCD3] = { + .name = "LCD3", + .vsync_irq = DISPC_IRQ_VSYNC3, + .framedone_irq = DISPC_IRQ_FRAMEDONE3, + .sync_lost_irq = DISPC_IRQ_SYNC_LOST3, + .gamma = { + .len = 256, + .bits = 8, + .reg = DISPC_GAMMA_TABLE3, + .has_index = true, + }, + .reg_desc = { + [DISPC_MGR_FLD_ENABLE] = { DISPC_CONTROL3, 0, 0 }, + [DISPC_MGR_FLD_STNTFT] = { DISPC_CONTROL3, 3, 3 }, + [DISPC_MGR_FLD_GO] = { DISPC_CONTROL3, 5, 5 }, + [DISPC_MGR_FLD_TFTDATALINES] = { DISPC_CONTROL3, 9, 8 }, + [DISPC_MGR_FLD_STALLMODE] = { DISPC_CONTROL3, 11, 11 }, + [DISPC_MGR_FLD_TCKENABLE] = { DISPC_CONFIG3, 10, 10 }, + [DISPC_MGR_FLD_TCKSELECTION] = { DISPC_CONFIG3, 11, 11 }, + [DISPC_MGR_FLD_CPR] = { DISPC_CONFIG3, 15, 15 }, + [DISPC_MGR_FLD_FIFOHANDCHECK] = { DISPC_CONFIG3, 16, 16 }, + }, + }, +}; + +static unsigned long dispc_fclk_rate(struct dispc_device *dispc); +static unsigned long dispc_core_clk_rate(struct dispc_device *dispc); +static unsigned long dispc_mgr_lclk_rate(struct dispc_device *dispc, + enum omap_channel channel); +static unsigned long dispc_mgr_pclk_rate(struct dispc_device *dispc, + enum omap_channel channel); + +static unsigned long dispc_plane_pclk_rate(struct dispc_device *dispc, + enum omap_plane_id plane); +static unsigned long dispc_plane_lclk_rate(struct dispc_device *dispc, + enum omap_plane_id plane); + +static inline void dispc_write_reg(struct dispc_device *dispc, u16 idx, u32 val) +{ + __raw_writel(val, dispc->base + idx); +} + +static inline u32 dispc_read_reg(struct dispc_device *dispc, u16 idx) +{ + return __raw_readl(dispc->base + idx); +} + +static u32 mgr_fld_read(struct dispc_device *dispc, enum omap_channel channel, + enum mgr_reg_fields regfld) +{ + const struct dispc_reg_field *rfld = &mgr_desc[channel].reg_desc[regfld]; + + return REG_GET(dispc, rfld->reg, rfld->high, rfld->low); +} + +static void mgr_fld_write(struct dispc_device *dispc, enum omap_channel channel, + enum mgr_reg_fields regfld, int val) +{ + const struct dispc_reg_field *rfld = &mgr_desc[channel].reg_desc[regfld]; + + REG_FLD_MOD(dispc, rfld->reg, val, rfld->high, rfld->low); +} + +int dispc_get_num_ovls(struct dispc_device *dispc) +{ + return dispc->feat->num_ovls; +} + +int dispc_get_num_mgrs(struct dispc_device *dispc) +{ + return dispc->feat->num_mgrs; +} + +static void dispc_get_reg_field(struct dispc_device *dispc, + enum dispc_feat_reg_field id, + u8 *start, u8 *end) +{ + BUG_ON(id >= dispc->feat->num_reg_fields); + + *start = dispc->feat->reg_fields[id].start; + *end = dispc->feat->reg_fields[id].end; +} + +static bool dispc_has_feature(struct dispc_device *dispc, + enum dispc_feature_id id) +{ + unsigned int i; + + for (i = 0; i < dispc->feat->num_features; i++) { + if (dispc->feat->features[i] == id) + return true; + } + + return false; +} + +#define SR(dispc, reg) \ + dispc->ctx[DISPC_##reg / sizeof(u32)] = dispc_read_reg(dispc, DISPC_##reg) +#define RR(dispc, reg) \ + dispc_write_reg(dispc, DISPC_##reg, dispc->ctx[DISPC_##reg / sizeof(u32)]) + +static void dispc_save_context(struct dispc_device *dispc) +{ + int i, j; + + DSSDBG("dispc_save_context\n"); + + SR(dispc, IRQENABLE); + SR(dispc, CONTROL); + SR(dispc, CONFIG); + SR(dispc, LINE_NUMBER); + if (dispc_has_feature(dispc, FEAT_ALPHA_FIXED_ZORDER) || + dispc_has_feature(dispc, FEAT_ALPHA_FREE_ZORDER)) + SR(dispc, GLOBAL_ALPHA); + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) { + SR(dispc, CONTROL2); + SR(dispc, CONFIG2); + } + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) { + SR(dispc, CONTROL3); + SR(dispc, CONFIG3); + } + + for (i = 0; i < dispc_get_num_mgrs(dispc); i++) { + SR(dispc, DEFAULT_COLOR(i)); + SR(dispc, TRANS_COLOR(i)); + SR(dispc, SIZE_MGR(i)); + if (i == OMAP_DSS_CHANNEL_DIGIT) + continue; + SR(dispc, TIMING_H(i)); + SR(dispc, TIMING_V(i)); + SR(dispc, POL_FREQ(i)); + SR(dispc, DIVISORo(i)); + + SR(dispc, DATA_CYCLE1(i)); + SR(dispc, DATA_CYCLE2(i)); + SR(dispc, DATA_CYCLE3(i)); + + if (dispc_has_feature(dispc, FEAT_CPR)) { + SR(dispc, CPR_COEF_R(i)); + SR(dispc, CPR_COEF_G(i)); + SR(dispc, CPR_COEF_B(i)); + } + } + + for (i = 0; i < dispc_get_num_ovls(dispc); i++) { + SR(dispc, OVL_BA0(i)); + SR(dispc, OVL_BA1(i)); + SR(dispc, OVL_POSITION(i)); + SR(dispc, OVL_SIZE(i)); + SR(dispc, OVL_ATTRIBUTES(i)); + SR(dispc, OVL_FIFO_THRESHOLD(i)); + SR(dispc, OVL_ROW_INC(i)); + SR(dispc, OVL_PIXEL_INC(i)); + if (dispc_has_feature(dispc, FEAT_PRELOAD)) + SR(dispc, OVL_PRELOAD(i)); + if (i == OMAP_DSS_GFX) { + SR(dispc, OVL_WINDOW_SKIP(i)); + SR(dispc, OVL_TABLE_BA(i)); + continue; + } + SR(dispc, OVL_FIR(i)); + SR(dispc, OVL_PICTURE_SIZE(i)); + SR(dispc, OVL_ACCU0(i)); + SR(dispc, OVL_ACCU1(i)); + + for (j = 0; j < 8; j++) + SR(dispc, OVL_FIR_COEF_H(i, j)); + + for (j = 0; j < 8; j++) + SR(dispc, OVL_FIR_COEF_HV(i, j)); + + for (j = 0; j < 5; j++) + SR(dispc, OVL_CONV_COEF(i, j)); + + if (dispc_has_feature(dispc, FEAT_FIR_COEF_V)) { + for (j = 0; j < 8; j++) + SR(dispc, OVL_FIR_COEF_V(i, j)); + } + + if (dispc_has_feature(dispc, FEAT_HANDLE_UV_SEPARATE)) { + SR(dispc, OVL_BA0_UV(i)); + SR(dispc, OVL_BA1_UV(i)); + SR(dispc, OVL_FIR2(i)); + SR(dispc, OVL_ACCU2_0(i)); + SR(dispc, OVL_ACCU2_1(i)); + + for (j = 0; j < 8; j++) + SR(dispc, OVL_FIR_COEF_H2(i, j)); + + for (j = 0; j < 8; j++) + SR(dispc, OVL_FIR_COEF_HV2(i, j)); + + for (j = 0; j < 8; j++) + SR(dispc, OVL_FIR_COEF_V2(i, j)); + } + if (dispc_has_feature(dispc, FEAT_ATTR2)) + SR(dispc, OVL_ATTRIBUTES2(i)); + } + + if (dispc_has_feature(dispc, FEAT_CORE_CLK_DIV)) + SR(dispc, DIVISOR); + + dispc->ctx_valid = true; + + DSSDBG("context saved\n"); +} + +static noinline_for_stack void dispc_restore_context(struct dispc_device *dispc) +{ + int i, j; + + DSSDBG("dispc_restore_context\n"); + + if (!dispc->ctx_valid) + return; + + /*RR(dispc, IRQENABLE);*/ + /*RR(dispc, CONTROL);*/ + RR(dispc, CONFIG); + RR(dispc, LINE_NUMBER); + if (dispc_has_feature(dispc, FEAT_ALPHA_FIXED_ZORDER) || + dispc_has_feature(dispc, FEAT_ALPHA_FREE_ZORDER)) + RR(dispc, GLOBAL_ALPHA); + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) + RR(dispc, CONFIG2); + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) + RR(dispc, CONFIG3); + + for (i = 0; i < dispc_get_num_mgrs(dispc); i++) { + RR(dispc, DEFAULT_COLOR(i)); + RR(dispc, TRANS_COLOR(i)); + RR(dispc, SIZE_MGR(i)); + if (i == OMAP_DSS_CHANNEL_DIGIT) + continue; + RR(dispc, TIMING_H(i)); + RR(dispc, TIMING_V(i)); + RR(dispc, POL_FREQ(i)); + RR(dispc, DIVISORo(i)); + + RR(dispc, DATA_CYCLE1(i)); + RR(dispc, DATA_CYCLE2(i)); + RR(dispc, DATA_CYCLE3(i)); + + if (dispc_has_feature(dispc, FEAT_CPR)) { + RR(dispc, CPR_COEF_R(i)); + RR(dispc, CPR_COEF_G(i)); + RR(dispc, CPR_COEF_B(i)); + } + } + + for (i = 0; i < dispc_get_num_ovls(dispc); i++) { + RR(dispc, OVL_BA0(i)); + RR(dispc, OVL_BA1(i)); + RR(dispc, OVL_POSITION(i)); + RR(dispc, OVL_SIZE(i)); + RR(dispc, OVL_ATTRIBUTES(i)); + RR(dispc, OVL_FIFO_THRESHOLD(i)); + RR(dispc, OVL_ROW_INC(i)); + RR(dispc, OVL_PIXEL_INC(i)); + if (dispc_has_feature(dispc, FEAT_PRELOAD)) + RR(dispc, OVL_PRELOAD(i)); + if (i == OMAP_DSS_GFX) { + RR(dispc, OVL_WINDOW_SKIP(i)); + RR(dispc, OVL_TABLE_BA(i)); + continue; + } + RR(dispc, OVL_FIR(i)); + RR(dispc, OVL_PICTURE_SIZE(i)); + RR(dispc, OVL_ACCU0(i)); + RR(dispc, OVL_ACCU1(i)); + + for (j = 0; j < 8; j++) + RR(dispc, OVL_FIR_COEF_H(i, j)); + + for (j = 0; j < 8; j++) + RR(dispc, OVL_FIR_COEF_HV(i, j)); + + for (j = 0; j < 5; j++) + RR(dispc, OVL_CONV_COEF(i, j)); + + if (dispc_has_feature(dispc, FEAT_FIR_COEF_V)) { + for (j = 0; j < 8; j++) + RR(dispc, OVL_FIR_COEF_V(i, j)); + } + + if (dispc_has_feature(dispc, FEAT_HANDLE_UV_SEPARATE)) { + RR(dispc, OVL_BA0_UV(i)); + RR(dispc, OVL_BA1_UV(i)); + RR(dispc, OVL_FIR2(i)); + RR(dispc, OVL_ACCU2_0(i)); + RR(dispc, OVL_ACCU2_1(i)); + + for (j = 0; j < 8; j++) + RR(dispc, OVL_FIR_COEF_H2(i, j)); + + for (j = 0; j < 8; j++) + RR(dispc, OVL_FIR_COEF_HV2(i, j)); + + for (j = 0; j < 8; j++) + RR(dispc, OVL_FIR_COEF_V2(i, j)); + } + if (dispc_has_feature(dispc, FEAT_ATTR2)) + RR(dispc, OVL_ATTRIBUTES2(i)); + } + + if (dispc_has_feature(dispc, FEAT_CORE_CLK_DIV)) + RR(dispc, DIVISOR); + + /* enable last, because LCD & DIGIT enable are here */ + RR(dispc, CONTROL); + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) + RR(dispc, CONTROL2); + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) + RR(dispc, CONTROL3); + /* clear spurious SYNC_LOST_DIGIT interrupts */ + dispc_clear_irqstatus(dispc, DISPC_IRQ_SYNC_LOST_DIGIT); + + /* + * enable last so IRQs won't trigger before + * the context is fully restored + */ + RR(dispc, IRQENABLE); + + DSSDBG("context restored\n"); +} + +#undef SR +#undef RR + +int dispc_runtime_get(struct dispc_device *dispc) +{ + int r; + + DSSDBG("dispc_runtime_get\n"); + + r = pm_runtime_get_sync(&dispc->pdev->dev); + if (WARN_ON(r < 0)) { + pm_runtime_put_noidle(&dispc->pdev->dev); + return r; + } + return 0; +} + +void dispc_runtime_put(struct dispc_device *dispc) +{ + int r; + + DSSDBG("dispc_runtime_put\n"); + + r = pm_runtime_put_sync(&dispc->pdev->dev); + WARN_ON(r < 0 && r != -ENOSYS); +} + +u32 dispc_mgr_get_vsync_irq(struct dispc_device *dispc, + enum omap_channel channel) +{ + return mgr_desc[channel].vsync_irq; +} + +u32 dispc_mgr_get_framedone_irq(struct dispc_device *dispc, + enum omap_channel channel) +{ + if (channel == OMAP_DSS_CHANNEL_DIGIT && dispc->feat->no_framedone_tv) + return 0; + + return mgr_desc[channel].framedone_irq; +} + +u32 dispc_mgr_get_sync_lost_irq(struct dispc_device *dispc, + enum omap_channel channel) +{ + return mgr_desc[channel].sync_lost_irq; +} + +void dispc_mgr_enable(struct dispc_device *dispc, + enum omap_channel channel, bool enable) +{ + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_ENABLE, enable); + /* flush posted write */ + mgr_fld_read(dispc, channel, DISPC_MGR_FLD_ENABLE); +} + +static bool dispc_mgr_is_enabled(struct dispc_device *dispc, + enum omap_channel channel) +{ + return !!mgr_fld_read(dispc, channel, DISPC_MGR_FLD_ENABLE); +} + +bool dispc_mgr_go_busy(struct dispc_device *dispc, + enum omap_channel channel) +{ + return mgr_fld_read(dispc, channel, DISPC_MGR_FLD_GO) == 1; +} + +void dispc_mgr_go(struct dispc_device *dispc, enum omap_channel channel) +{ + WARN_ON(!dispc_mgr_is_enabled(dispc, channel)); + WARN_ON(dispc_mgr_go_busy(dispc, channel)); + + DSSDBG("GO %s\n", mgr_desc[channel].name); + + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_GO, 1); +} + +static void dispc_ovl_write_firh_reg(struct dispc_device *dispc, + enum omap_plane_id plane, int reg, + u32 value) +{ + dispc_write_reg(dispc, DISPC_OVL_FIR_COEF_H(plane, reg), value); +} + +static void dispc_ovl_write_firhv_reg(struct dispc_device *dispc, + enum omap_plane_id plane, int reg, + u32 value) +{ + dispc_write_reg(dispc, DISPC_OVL_FIR_COEF_HV(plane, reg), value); +} + +static void dispc_ovl_write_firv_reg(struct dispc_device *dispc, + enum omap_plane_id plane, int reg, + u32 value) +{ + dispc_write_reg(dispc, DISPC_OVL_FIR_COEF_V(plane, reg), value); +} + +static void dispc_ovl_write_firh2_reg(struct dispc_device *dispc, + enum omap_plane_id plane, int reg, + u32 value) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_write_reg(dispc, DISPC_OVL_FIR_COEF_H2(plane, reg), value); +} + +static void dispc_ovl_write_firhv2_reg(struct dispc_device *dispc, + enum omap_plane_id plane, int reg, + u32 value) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_write_reg(dispc, DISPC_OVL_FIR_COEF_HV2(plane, reg), value); +} + +static void dispc_ovl_write_firv2_reg(struct dispc_device *dispc, + enum omap_plane_id plane, int reg, + u32 value) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_write_reg(dispc, DISPC_OVL_FIR_COEF_V2(plane, reg), value); +} + +static void dispc_ovl_set_scale_coef(struct dispc_device *dispc, + enum omap_plane_id plane, int fir_hinc, + int fir_vinc, int five_taps, + enum omap_color_component color_comp) +{ + const struct dispc_coef *h_coef, *v_coef; + int i; + + h_coef = dispc_ovl_get_scale_coef(fir_hinc, true); + v_coef = dispc_ovl_get_scale_coef(fir_vinc, five_taps); + + if (!h_coef || !v_coef) { + dev_err(&dispc->pdev->dev, "%s: failed to find scale coefs\n", + __func__); + return; + } + + for (i = 0; i < 8; i++) { + u32 h, hv; + + h = FLD_VAL(h_coef[i].hc0_vc00, 7, 0) + | FLD_VAL(h_coef[i].hc1_vc0, 15, 8) + | FLD_VAL(h_coef[i].hc2_vc1, 23, 16) + | FLD_VAL(h_coef[i].hc3_vc2, 31, 24); + hv = FLD_VAL(h_coef[i].hc4_vc22, 7, 0) + | FLD_VAL(v_coef[i].hc1_vc0, 15, 8) + | FLD_VAL(v_coef[i].hc2_vc1, 23, 16) + | FLD_VAL(v_coef[i].hc3_vc2, 31, 24); + + if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) { + dispc_ovl_write_firh_reg(dispc, plane, i, h); + dispc_ovl_write_firhv_reg(dispc, plane, i, hv); + } else { + dispc_ovl_write_firh2_reg(dispc, plane, i, h); + dispc_ovl_write_firhv2_reg(dispc, plane, i, hv); + } + + } + + if (five_taps) { + for (i = 0; i < 8; i++) { + u32 v; + v = FLD_VAL(v_coef[i].hc0_vc00, 7, 0) + | FLD_VAL(v_coef[i].hc4_vc22, 15, 8); + if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) + dispc_ovl_write_firv_reg(dispc, plane, i, v); + else + dispc_ovl_write_firv2_reg(dispc, plane, i, v); + } + } +} + +struct csc_coef_yuv2rgb { + int ry, rcb, rcr, gy, gcb, gcr, by, bcb, bcr; + bool full_range; +}; + +static void dispc_ovl_write_color_conv_coef(struct dispc_device *dispc, + enum omap_plane_id plane, + const struct csc_coef_yuv2rgb *ct) +{ +#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0)) + + dispc_write_reg(dispc, DISPC_OVL_CONV_COEF(plane, 0), CVAL(ct->rcr, ct->ry)); + dispc_write_reg(dispc, DISPC_OVL_CONV_COEF(plane, 1), CVAL(ct->gy, ct->rcb)); + dispc_write_reg(dispc, DISPC_OVL_CONV_COEF(plane, 2), CVAL(ct->gcb, ct->gcr)); + dispc_write_reg(dispc, DISPC_OVL_CONV_COEF(plane, 3), CVAL(ct->bcr, ct->by)); + dispc_write_reg(dispc, DISPC_OVL_CONV_COEF(plane, 4), CVAL(0, ct->bcb)); + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), ct->full_range, 11, 11); + +#undef CVAL +} + +/* YUV -> RGB, ITU-R BT.601, full range */ +static const struct csc_coef_yuv2rgb coefs_yuv2rgb_bt601_full = { + 256, 0, 358, /* ry, rcb, rcr |1.000 0.000 1.402|*/ + 256, -88, -182, /* gy, gcb, gcr |1.000 -0.344 -0.714|*/ + 256, 452, 0, /* by, bcb, bcr |1.000 1.772 0.000|*/ + true, /* full range */ +}; + +/* YUV -> RGB, ITU-R BT.601, limited range */ +static const struct csc_coef_yuv2rgb coefs_yuv2rgb_bt601_lim = { + 298, 0, 409, /* ry, rcb, rcr |1.164 0.000 1.596|*/ + 298, -100, -208, /* gy, gcb, gcr |1.164 -0.392 -0.813|*/ + 298, 516, 0, /* by, bcb, bcr |1.164 2.017 0.000|*/ + false, /* limited range */ +}; + +/* YUV -> RGB, ITU-R BT.709, full range */ +static const struct csc_coef_yuv2rgb coefs_yuv2rgb_bt709_full = { + 256, 0, 402, /* ry, rcb, rcr |1.000 0.000 1.570|*/ + 256, -48, -120, /* gy, gcb, gcr |1.000 -0.187 -0.467|*/ + 256, 475, 0, /* by, bcb, bcr |1.000 1.856 0.000|*/ + true, /* full range */ +}; + +/* YUV -> RGB, ITU-R BT.709, limited range */ +static const struct csc_coef_yuv2rgb coefs_yuv2rgb_bt709_lim = { + 298, 0, 459, /* ry, rcb, rcr |1.164 0.000 1.793|*/ + 298, -55, -136, /* gy, gcb, gcr |1.164 -0.213 -0.533|*/ + 298, 541, 0, /* by, bcb, bcr |1.164 2.112 0.000|*/ + false, /* limited range */ +}; + +static void dispc_ovl_set_csc(struct dispc_device *dispc, + enum omap_plane_id plane, + enum drm_color_encoding color_encoding, + enum drm_color_range color_range) +{ + const struct csc_coef_yuv2rgb *csc; + + switch (color_encoding) { + default: + case DRM_COLOR_YCBCR_BT601: + if (color_range == DRM_COLOR_YCBCR_FULL_RANGE) + csc = &coefs_yuv2rgb_bt601_full; + else + csc = &coefs_yuv2rgb_bt601_lim; + break; + case DRM_COLOR_YCBCR_BT709: + if (color_range == DRM_COLOR_YCBCR_FULL_RANGE) + csc = &coefs_yuv2rgb_bt709_full; + else + csc = &coefs_yuv2rgb_bt709_lim; + break; + } + + dispc_ovl_write_color_conv_coef(dispc, plane, csc); +} + +static void dispc_ovl_set_ba0(struct dispc_device *dispc, + enum omap_plane_id plane, u32 paddr) +{ + dispc_write_reg(dispc, DISPC_OVL_BA0(plane), paddr); +} + +static void dispc_ovl_set_ba1(struct dispc_device *dispc, + enum omap_plane_id plane, u32 paddr) +{ + dispc_write_reg(dispc, DISPC_OVL_BA1(plane), paddr); +} + +static void dispc_ovl_set_ba0_uv(struct dispc_device *dispc, + enum omap_plane_id plane, u32 paddr) +{ + dispc_write_reg(dispc, DISPC_OVL_BA0_UV(plane), paddr); +} + +static void dispc_ovl_set_ba1_uv(struct dispc_device *dispc, + enum omap_plane_id plane, u32 paddr) +{ + dispc_write_reg(dispc, DISPC_OVL_BA1_UV(plane), paddr); +} + +static void dispc_ovl_set_pos(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_overlay_caps caps, int x, int y) +{ + u32 val; + + if ((caps & OMAP_DSS_OVL_CAP_POS) == 0) + return; + + val = FLD_VAL(y, 26, 16) | FLD_VAL(x, 10, 0); + + dispc_write_reg(dispc, DISPC_OVL_POSITION(plane), val); +} + +static void dispc_ovl_set_input_size(struct dispc_device *dispc, + enum omap_plane_id plane, int width, + int height) +{ + u32 val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); + + if (plane == OMAP_DSS_GFX || plane == OMAP_DSS_WB) + dispc_write_reg(dispc, DISPC_OVL_SIZE(plane), val); + else + dispc_write_reg(dispc, DISPC_OVL_PICTURE_SIZE(plane), val); +} + +static void dispc_ovl_set_output_size(struct dispc_device *dispc, + enum omap_plane_id plane, int width, + int height) +{ + u32 val; + + BUG_ON(plane == OMAP_DSS_GFX); + + val = FLD_VAL(height - 1, 26, 16) | FLD_VAL(width - 1, 10, 0); + + if (plane == OMAP_DSS_WB) + dispc_write_reg(dispc, DISPC_OVL_PICTURE_SIZE(plane), val); + else + dispc_write_reg(dispc, DISPC_OVL_SIZE(plane), val); +} + +static void dispc_ovl_set_zorder(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_overlay_caps caps, u8 zorder) +{ + if ((caps & OMAP_DSS_OVL_CAP_ZORDER) == 0) + return; + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), zorder, 27, 26); +} + +static void dispc_ovl_enable_zorder_planes(struct dispc_device *dispc) +{ + int i; + + if (!dispc_has_feature(dispc, FEAT_ALPHA_FREE_ZORDER)) + return; + + for (i = 0; i < dispc_get_num_ovls(dispc); i++) + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(i), 1, 25, 25); +} + +static void dispc_ovl_set_pre_mult_alpha(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_overlay_caps caps, + bool enable) +{ + if ((caps & OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA) == 0) + return; + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), enable ? 1 : 0, 28, 28); +} + +static void dispc_ovl_setup_global_alpha(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_overlay_caps caps, + u8 global_alpha) +{ + static const unsigned int shifts[] = { 0, 8, 16, 24, }; + int shift; + + if ((caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0) + return; + + shift = shifts[plane]; + REG_FLD_MOD(dispc, DISPC_GLOBAL_ALPHA, global_alpha, shift + 7, shift); +} + +static void dispc_ovl_set_pix_inc(struct dispc_device *dispc, + enum omap_plane_id plane, s32 inc) +{ + dispc_write_reg(dispc, DISPC_OVL_PIXEL_INC(plane), inc); +} + +static void dispc_ovl_set_row_inc(struct dispc_device *dispc, + enum omap_plane_id plane, s32 inc) +{ + dispc_write_reg(dispc, DISPC_OVL_ROW_INC(plane), inc); +} + +static void dispc_ovl_set_color_mode(struct dispc_device *dispc, + enum omap_plane_id plane, u32 fourcc) +{ + u32 m = 0; + if (plane != OMAP_DSS_GFX) { + switch (fourcc) { + case DRM_FORMAT_NV12: + m = 0x0; break; + case DRM_FORMAT_XRGB4444: + m = 0x1; break; + case DRM_FORMAT_RGBA4444: + m = 0x2; break; + case DRM_FORMAT_RGBX4444: + m = 0x4; break; + case DRM_FORMAT_ARGB4444: + m = 0x5; break; + case DRM_FORMAT_RGB565: + m = 0x6; break; + case DRM_FORMAT_ARGB1555: + m = 0x7; break; + case DRM_FORMAT_XRGB8888: + m = 0x8; break; + case DRM_FORMAT_RGB888: + m = 0x9; break; + case DRM_FORMAT_YUYV: + m = 0xa; break; + case DRM_FORMAT_UYVY: + m = 0xb; break; + case DRM_FORMAT_ARGB8888: + m = 0xc; break; + case DRM_FORMAT_RGBA8888: + m = 0xd; break; + case DRM_FORMAT_RGBX8888: + m = 0xe; break; + case DRM_FORMAT_XRGB1555: + m = 0xf; break; + default: + BUG(); return; + } + } else { + switch (fourcc) { + case DRM_FORMAT_RGBX4444: + m = 0x4; break; + case DRM_FORMAT_ARGB4444: + m = 0x5; break; + case DRM_FORMAT_RGB565: + m = 0x6; break; + case DRM_FORMAT_ARGB1555: + m = 0x7; break; + case DRM_FORMAT_XRGB8888: + m = 0x8; break; + case DRM_FORMAT_RGB888: + m = 0x9; break; + case DRM_FORMAT_XRGB4444: + m = 0xa; break; + case DRM_FORMAT_RGBA4444: + m = 0xb; break; + case DRM_FORMAT_ARGB8888: + m = 0xc; break; + case DRM_FORMAT_RGBA8888: + m = 0xd; break; + case DRM_FORMAT_RGBX8888: + m = 0xe; break; + case DRM_FORMAT_XRGB1555: + m = 0xf; break; + default: + BUG(); return; + } + } + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), m, 4, 1); +} + +static void dispc_ovl_configure_burst_type(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_dss_rotation_type rotation) +{ + if (dispc_has_feature(dispc, FEAT_BURST_2D) == 0) + return; + + if (rotation == OMAP_DSS_ROT_TILER) + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), 1, 29, 29); + else + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), 0, 29, 29); +} + +static void dispc_ovl_set_channel_out(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_channel channel) +{ + int shift; + u32 val; + int chan = 0, chan2 = 0; + + switch (plane) { + case OMAP_DSS_GFX: + shift = 8; + break; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + shift = 16; + break; + default: + BUG(); + return; + } + + val = dispc_read_reg(dispc, DISPC_OVL_ATTRIBUTES(plane)); + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) { + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + chan = 0; + chan2 = 0; + break; + case OMAP_DSS_CHANNEL_DIGIT: + chan = 1; + chan2 = 0; + break; + case OMAP_DSS_CHANNEL_LCD2: + chan = 0; + chan2 = 1; + break; + case OMAP_DSS_CHANNEL_LCD3: + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) { + chan = 0; + chan2 = 2; + } else { + BUG(); + return; + } + break; + case OMAP_DSS_CHANNEL_WB: + chan = 0; + chan2 = 3; + break; + default: + BUG(); + return; + } + + val = FLD_MOD(val, chan, shift, shift); + val = FLD_MOD(val, chan2, 31, 30); + } else { + val = FLD_MOD(val, channel, shift, shift); + } + dispc_write_reg(dispc, DISPC_OVL_ATTRIBUTES(plane), val); +} + +static enum omap_channel dispc_ovl_get_channel_out(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + int shift; + u32 val; + + switch (plane) { + case OMAP_DSS_GFX: + shift = 8; + break; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + shift = 16; + break; + default: + BUG(); + return 0; + } + + val = dispc_read_reg(dispc, DISPC_OVL_ATTRIBUTES(plane)); + + if (FLD_GET(val, shift, shift) == 1) + return OMAP_DSS_CHANNEL_DIGIT; + + if (!dispc_has_feature(dispc, FEAT_MGR_LCD2)) + return OMAP_DSS_CHANNEL_LCD; + + switch (FLD_GET(val, 31, 30)) { + case 0: + default: + return OMAP_DSS_CHANNEL_LCD; + case 1: + return OMAP_DSS_CHANNEL_LCD2; + case 2: + return OMAP_DSS_CHANNEL_LCD3; + case 3: + return OMAP_DSS_CHANNEL_WB; + } +} + +static void dispc_ovl_set_burst_size(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_burst_size burst_size) +{ + static const unsigned int shifts[] = { 6, 14, 14, 14, 14, }; + int shift; + + shift = shifts[plane]; + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), burst_size, + shift + 1, shift); +} + +static void dispc_configure_burst_sizes(struct dispc_device *dispc) +{ + int i; + const int burst_size = BURST_SIZE_X8; + + /* Configure burst size always to maximum size */ + for (i = 0; i < dispc_get_num_ovls(dispc); ++i) + dispc_ovl_set_burst_size(dispc, i, burst_size); + if (dispc->feat->has_writeback) + dispc_ovl_set_burst_size(dispc, OMAP_DSS_WB, burst_size); +} + +static u32 dispc_ovl_get_burst_size(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + /* burst multiplier is always x8 (see dispc_configure_burst_sizes()) */ + return dispc->feat->burst_size_unit * 8; +} + +bool dispc_ovl_color_mode_supported(struct dispc_device *dispc, + enum omap_plane_id plane, u32 fourcc) +{ + const u32 *modes; + unsigned int i; + + modes = dispc->feat->supported_color_modes[plane]; + + for (i = 0; modes[i]; ++i) { + if (modes[i] == fourcc) + return true; + } + + return false; +} + +const u32 *dispc_ovl_get_color_modes(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + return dispc->feat->supported_color_modes[plane]; +} + +static void dispc_mgr_enable_cpr(struct dispc_device *dispc, + enum omap_channel channel, bool enable) +{ + if (channel == OMAP_DSS_CHANNEL_DIGIT) + return; + + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_CPR, enable); +} + +static void dispc_mgr_set_cpr_coef(struct dispc_device *dispc, + enum omap_channel channel, + const struct omap_dss_cpr_coefs *coefs) +{ + u32 coef_r, coef_g, coef_b; + + if (!dss_mgr_is_lcd(channel)) + return; + + coef_r = FLD_VAL(coefs->rr, 31, 22) | FLD_VAL(coefs->rg, 20, 11) | + FLD_VAL(coefs->rb, 9, 0); + coef_g = FLD_VAL(coefs->gr, 31, 22) | FLD_VAL(coefs->gg, 20, 11) | + FLD_VAL(coefs->gb, 9, 0); + coef_b = FLD_VAL(coefs->br, 31, 22) | FLD_VAL(coefs->bg, 20, 11) | + FLD_VAL(coefs->bb, 9, 0); + + dispc_write_reg(dispc, DISPC_CPR_COEF_R(channel), coef_r); + dispc_write_reg(dispc, DISPC_CPR_COEF_G(channel), coef_g); + dispc_write_reg(dispc, DISPC_CPR_COEF_B(channel), coef_b); +} + +static void dispc_ovl_set_vid_color_conv(struct dispc_device *dispc, + enum omap_plane_id plane, bool enable) +{ + u32 val; + + BUG_ON(plane == OMAP_DSS_GFX); + + val = dispc_read_reg(dispc, DISPC_OVL_ATTRIBUTES(plane)); + val = FLD_MOD(val, enable, 9, 9); + dispc_write_reg(dispc, DISPC_OVL_ATTRIBUTES(plane), val); +} + +static void dispc_ovl_enable_replication(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_overlay_caps caps, + bool enable) +{ + static const unsigned int shifts[] = { 5, 10, 10, 10 }; + int shift; + + if ((caps & OMAP_DSS_OVL_CAP_REPLICATION) == 0) + return; + + shift = shifts[plane]; + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), enable, shift, shift); +} + +static void dispc_mgr_set_size(struct dispc_device *dispc, + enum omap_channel channel, u16 width, u16 height) +{ + u32 val; + + val = FLD_VAL(height - 1, dispc->feat->mgr_height_start, 16) | + FLD_VAL(width - 1, dispc->feat->mgr_width_start, 0); + + dispc_write_reg(dispc, DISPC_SIZE_MGR(channel), val); +} + +static void dispc_init_fifos(struct dispc_device *dispc) +{ + u32 size; + int fifo; + u8 start, end; + u32 unit; + int i; + + unit = dispc->feat->buffer_size_unit; + + dispc_get_reg_field(dispc, FEAT_REG_FIFOSIZE, &start, &end); + + for (fifo = 0; fifo < dispc->feat->num_fifos; ++fifo) { + size = REG_GET(dispc, DISPC_OVL_FIFO_SIZE_STATUS(fifo), + start, end); + size *= unit; + dispc->fifo_size[fifo] = size; + + /* + * By default fifos are mapped directly to overlays, fifo 0 to + * ovl 0, fifo 1 to ovl 1, etc. + */ + dispc->fifo_assignment[fifo] = fifo; + } + + /* + * The GFX fifo on OMAP4 is smaller than the other fifos. The small fifo + * causes problems with certain use cases, like using the tiler in 2D + * mode. The below hack swaps the fifos of GFX and WB planes, thus + * giving GFX plane a larger fifo. WB but should work fine with a + * smaller fifo. + */ + if (dispc->feat->gfx_fifo_workaround) { + u32 v; + + v = dispc_read_reg(dispc, DISPC_GLOBAL_BUFFER); + + v = FLD_MOD(v, 4, 2, 0); /* GFX BUF top to WB */ + v = FLD_MOD(v, 4, 5, 3); /* GFX BUF bottom to WB */ + v = FLD_MOD(v, 0, 26, 24); /* WB BUF top to GFX */ + v = FLD_MOD(v, 0, 29, 27); /* WB BUF bottom to GFX */ + + dispc_write_reg(dispc, DISPC_GLOBAL_BUFFER, v); + + dispc->fifo_assignment[OMAP_DSS_GFX] = OMAP_DSS_WB; + dispc->fifo_assignment[OMAP_DSS_WB] = OMAP_DSS_GFX; + } + + /* + * Setup default fifo thresholds. + */ + for (i = 0; i < dispc_get_num_ovls(dispc); ++i) { + u32 low, high; + const bool use_fifomerge = false; + const bool manual_update = false; + + dispc_ovl_compute_fifo_thresholds(dispc, i, &low, &high, + use_fifomerge, manual_update); + + dispc_ovl_set_fifo_threshold(dispc, i, low, high); + } + + if (dispc->feat->has_writeback) { + u32 low, high; + const bool use_fifomerge = false; + const bool manual_update = false; + + dispc_ovl_compute_fifo_thresholds(dispc, OMAP_DSS_WB, + &low, &high, use_fifomerge, + manual_update); + + dispc_ovl_set_fifo_threshold(dispc, OMAP_DSS_WB, low, high); + } +} + +static u32 dispc_ovl_get_fifo_size(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + int fifo; + u32 size = 0; + + for (fifo = 0; fifo < dispc->feat->num_fifos; ++fifo) { + if (dispc->fifo_assignment[fifo] == plane) + size += dispc->fifo_size[fifo]; + } + + return size; +} + +void dispc_ovl_set_fifo_threshold(struct dispc_device *dispc, + enum omap_plane_id plane, + u32 low, u32 high) +{ + u8 hi_start, hi_end, lo_start, lo_end; + u32 unit; + + unit = dispc->feat->buffer_size_unit; + + WARN_ON(low % unit != 0); + WARN_ON(high % unit != 0); + + low /= unit; + high /= unit; + + dispc_get_reg_field(dispc, FEAT_REG_FIFOHIGHTHRESHOLD, + &hi_start, &hi_end); + dispc_get_reg_field(dispc, FEAT_REG_FIFOLOWTHRESHOLD, + &lo_start, &lo_end); + + DSSDBG("fifo(%d) threshold (bytes), old %u/%u, new %u/%u\n", + plane, + REG_GET(dispc, DISPC_OVL_FIFO_THRESHOLD(plane), + lo_start, lo_end) * unit, + REG_GET(dispc, DISPC_OVL_FIFO_THRESHOLD(plane), + hi_start, hi_end) * unit, + low * unit, high * unit); + + dispc_write_reg(dispc, DISPC_OVL_FIFO_THRESHOLD(plane), + FLD_VAL(high, hi_start, hi_end) | + FLD_VAL(low, lo_start, lo_end)); + + /* + * configure the preload to the pipeline's high threhold, if HT it's too + * large for the preload field, set the threshold to the maximum value + * that can be held by the preload register + */ + if (dispc_has_feature(dispc, FEAT_PRELOAD) && + dispc->feat->set_max_preload && plane != OMAP_DSS_WB) + dispc_write_reg(dispc, DISPC_OVL_PRELOAD(plane), + min(high, 0xfffu)); +} + +void dispc_ovl_compute_fifo_thresholds(struct dispc_device *dispc, + enum omap_plane_id plane, + u32 *fifo_low, u32 *fifo_high, + bool use_fifomerge, bool manual_update) +{ + /* + * All sizes are in bytes. Both the buffer and burst are made of + * buffer_units, and the fifo thresholds must be buffer_unit aligned. + */ + unsigned int buf_unit = dispc->feat->buffer_size_unit; + unsigned int ovl_fifo_size, total_fifo_size, burst_size; + int i; + + burst_size = dispc_ovl_get_burst_size(dispc, plane); + ovl_fifo_size = dispc_ovl_get_fifo_size(dispc, plane); + + if (use_fifomerge) { + total_fifo_size = 0; + for (i = 0; i < dispc_get_num_ovls(dispc); ++i) + total_fifo_size += dispc_ovl_get_fifo_size(dispc, i); + } else { + total_fifo_size = ovl_fifo_size; + } + + /* + * We use the same low threshold for both fifomerge and non-fifomerge + * cases, but for fifomerge we calculate the high threshold using the + * combined fifo size + */ + + if (manual_update && dispc_has_feature(dispc, FEAT_OMAP3_DSI_FIFO_BUG)) { + *fifo_low = ovl_fifo_size - burst_size * 2; + *fifo_high = total_fifo_size - burst_size; + } else if (plane == OMAP_DSS_WB) { + /* + * Most optimal configuration for writeback is to push out data + * to the interconnect the moment writeback pushes enough pixels + * in the FIFO to form a burst + */ + *fifo_low = 0; + *fifo_high = burst_size; + } else { + *fifo_low = ovl_fifo_size - burst_size; + *fifo_high = total_fifo_size - buf_unit; + } +} + +static void dispc_ovl_set_mflag(struct dispc_device *dispc, + enum omap_plane_id plane, bool enable) +{ + int bit; + + if (plane == OMAP_DSS_GFX) + bit = 14; + else + bit = 23; + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), enable, bit, bit); +} + +static void dispc_ovl_set_mflag_threshold(struct dispc_device *dispc, + enum omap_plane_id plane, + int low, int high) +{ + dispc_write_reg(dispc, DISPC_OVL_MFLAG_THRESHOLD(plane), + FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0)); +} + +static void dispc_init_mflag(struct dispc_device *dispc) +{ + int i; + + /* + * HACK: NV12 color format and MFLAG seem to have problems working + * together: using two displays, and having an NV12 overlay on one of + * the displays will cause underflows/synclosts when MFLAG_CTRL=2. + * Changing MFLAG thresholds and PRELOAD to certain values seem to + * remove the errors, but there doesn't seem to be a clear logic on + * which values work and which not. + * + * As a work-around, set force MFLAG to always on. + */ + dispc_write_reg(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, + (1 << 0) | /* MFLAG_CTRL = force always on */ + (0 << 2)); /* MFLAG_START = disable */ + + for (i = 0; i < dispc_get_num_ovls(dispc); ++i) { + u32 size = dispc_ovl_get_fifo_size(dispc, i); + u32 unit = dispc->feat->buffer_size_unit; + u32 low, high; + + dispc_ovl_set_mflag(dispc, i, true); + + /* + * Simulation team suggests below thesholds: + * HT = fifosize * 5 / 8; + * LT = fifosize * 4 / 8; + */ + + low = size * 4 / 8 / unit; + high = size * 5 / 8 / unit; + + dispc_ovl_set_mflag_threshold(dispc, i, low, high); + } + + if (dispc->feat->has_writeback) { + u32 size = dispc_ovl_get_fifo_size(dispc, OMAP_DSS_WB); + u32 unit = dispc->feat->buffer_size_unit; + u32 low, high; + + dispc_ovl_set_mflag(dispc, OMAP_DSS_WB, true); + + /* + * Simulation team suggests below thesholds: + * HT = fifosize * 5 / 8; + * LT = fifosize * 4 / 8; + */ + + low = size * 4 / 8 / unit; + high = size * 5 / 8 / unit; + + dispc_ovl_set_mflag_threshold(dispc, OMAP_DSS_WB, low, high); + } +} + +static void dispc_ovl_set_fir(struct dispc_device *dispc, + enum omap_plane_id plane, + int hinc, int vinc, + enum omap_color_component color_comp) +{ + u32 val; + + if (color_comp == DISPC_COLOR_COMPONENT_RGB_Y) { + u8 hinc_start, hinc_end, vinc_start, vinc_end; + + dispc_get_reg_field(dispc, FEAT_REG_FIRHINC, + &hinc_start, &hinc_end); + dispc_get_reg_field(dispc, FEAT_REG_FIRVINC, + &vinc_start, &vinc_end); + val = FLD_VAL(vinc, vinc_start, vinc_end) | + FLD_VAL(hinc, hinc_start, hinc_end); + + dispc_write_reg(dispc, DISPC_OVL_FIR(plane), val); + } else { + val = FLD_VAL(vinc, 28, 16) | FLD_VAL(hinc, 12, 0); + dispc_write_reg(dispc, DISPC_OVL_FIR2(plane), val); + } +} + +static void dispc_ovl_set_vid_accu0(struct dispc_device *dispc, + enum omap_plane_id plane, int haccu, + int vaccu) +{ + u32 val; + u8 hor_start, hor_end, vert_start, vert_end; + + dispc_get_reg_field(dispc, FEAT_REG_HORIZONTALACCU, + &hor_start, &hor_end); + dispc_get_reg_field(dispc, FEAT_REG_VERTICALACCU, + &vert_start, &vert_end); + + val = FLD_VAL(vaccu, vert_start, vert_end) | + FLD_VAL(haccu, hor_start, hor_end); + + dispc_write_reg(dispc, DISPC_OVL_ACCU0(plane), val); +} + +static void dispc_ovl_set_vid_accu1(struct dispc_device *dispc, + enum omap_plane_id plane, int haccu, + int vaccu) +{ + u32 val; + u8 hor_start, hor_end, vert_start, vert_end; + + dispc_get_reg_field(dispc, FEAT_REG_HORIZONTALACCU, + &hor_start, &hor_end); + dispc_get_reg_field(dispc, FEAT_REG_VERTICALACCU, + &vert_start, &vert_end); + + val = FLD_VAL(vaccu, vert_start, vert_end) | + FLD_VAL(haccu, hor_start, hor_end); + + dispc_write_reg(dispc, DISPC_OVL_ACCU1(plane), val); +} + +static void dispc_ovl_set_vid_accu2_0(struct dispc_device *dispc, + enum omap_plane_id plane, int haccu, + int vaccu) +{ + u32 val; + + val = FLD_VAL(vaccu, 26, 16) | FLD_VAL(haccu, 10, 0); + dispc_write_reg(dispc, DISPC_OVL_ACCU2_0(plane), val); +} + +static void dispc_ovl_set_vid_accu2_1(struct dispc_device *dispc, + enum omap_plane_id plane, int haccu, + int vaccu) +{ + u32 val; + + val = FLD_VAL(vaccu, 26, 16) | FLD_VAL(haccu, 10, 0); + dispc_write_reg(dispc, DISPC_OVL_ACCU2_1(plane), val); +} + +static void dispc_ovl_set_scale_param(struct dispc_device *dispc, + enum omap_plane_id plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool five_taps, u8 rotation, + enum omap_color_component color_comp) +{ + int fir_hinc, fir_vinc; + + fir_hinc = 1024 * orig_width / out_width; + fir_vinc = 1024 * orig_height / out_height; + + dispc_ovl_set_scale_coef(dispc, plane, fir_hinc, fir_vinc, five_taps, + color_comp); + dispc_ovl_set_fir(dispc, plane, fir_hinc, fir_vinc, color_comp); +} + +static void dispc_ovl_set_accu_uv(struct dispc_device *dispc, + enum omap_plane_id plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, u32 fourcc, u8 rotation) +{ + int h_accu2_0, h_accu2_1; + int v_accu2_0, v_accu2_1; + int chroma_hinc, chroma_vinc; + int idx; + + struct accu { + s8 h0_m, h0_n; + s8 h1_m, h1_n; + s8 v0_m, v0_n; + s8 v1_m, v1_n; + }; + + const struct accu *accu_table; + const struct accu *accu_val; + + static const struct accu accu_nv12[4] = { + { 0, 1, 0, 1 , -1, 2, 0, 1 }, + { 1, 2, -3, 4 , 0, 1, 0, 1 }, + { -1, 1, 0, 1 , -1, 2, 0, 1 }, + { -1, 2, -1, 2 , -1, 1, 0, 1 }, + }; + + static const struct accu accu_nv12_ilace[4] = { + { 0, 1, 0, 1 , -3, 4, -1, 4 }, + { -1, 4, -3, 4 , 0, 1, 0, 1 }, + { -1, 1, 0, 1 , -1, 4, -3, 4 }, + { -3, 4, -3, 4 , -1, 1, 0, 1 }, + }; + + static const struct accu accu_yuv[4] = { + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { 0, 1, 0, 1, 0, 1, 0, 1 }, + { -1, 1, 0, 1, 0, 1, 0, 1 }, + { 0, 1, 0, 1, -1, 1, 0, 1 }, + }; + + /* Note: DSS HW rotates clockwise, DRM_MODE_ROTATE_* counter-clockwise */ + switch (rotation & DRM_MODE_ROTATE_MASK) { + default: + case DRM_MODE_ROTATE_0: + idx = 0; + break; + case DRM_MODE_ROTATE_90: + idx = 3; + break; + case DRM_MODE_ROTATE_180: + idx = 2; + break; + case DRM_MODE_ROTATE_270: + idx = 1; + break; + } + + switch (fourcc) { + case DRM_FORMAT_NV12: + if (ilace) + accu_table = accu_nv12_ilace; + else + accu_table = accu_nv12; + break; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + accu_table = accu_yuv; + break; + default: + BUG(); + return; + } + + accu_val = &accu_table[idx]; + + chroma_hinc = 1024 * orig_width / out_width; + chroma_vinc = 1024 * orig_height / out_height; + + h_accu2_0 = (accu_val->h0_m * chroma_hinc / accu_val->h0_n) % 1024; + h_accu2_1 = (accu_val->h1_m * chroma_hinc / accu_val->h1_n) % 1024; + v_accu2_0 = (accu_val->v0_m * chroma_vinc / accu_val->v0_n) % 1024; + v_accu2_1 = (accu_val->v1_m * chroma_vinc / accu_val->v1_n) % 1024; + + dispc_ovl_set_vid_accu2_0(dispc, plane, h_accu2_0, v_accu2_0); + dispc_ovl_set_vid_accu2_1(dispc, plane, h_accu2_1, v_accu2_1); +} + +static void dispc_ovl_set_scaling_common(struct dispc_device *dispc, + enum omap_plane_id plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, bool five_taps, + bool fieldmode, u32 fourcc, + u8 rotation) +{ + int accu0 = 0; + int accu1 = 0; + u32 l; + + dispc_ovl_set_scale_param(dispc, plane, orig_width, orig_height, + out_width, out_height, five_taps, + rotation, DISPC_COLOR_COMPONENT_RGB_Y); + l = dispc_read_reg(dispc, DISPC_OVL_ATTRIBUTES(plane)); + + /* RESIZEENABLE and VERTICALTAPS */ + l &= ~((0x3 << 5) | (0x1 << 21)); + l |= (orig_width != out_width) ? (1 << 5) : 0; + l |= (orig_height != out_height) ? (1 << 6) : 0; + l |= five_taps ? (1 << 21) : 0; + + /* VRESIZECONF and HRESIZECONF */ + if (dispc_has_feature(dispc, FEAT_RESIZECONF)) { + l &= ~(0x3 << 7); + l |= (orig_width <= out_width) ? 0 : (1 << 7); + l |= (orig_height <= out_height) ? 0 : (1 << 8); + } + + /* LINEBUFFERSPLIT */ + if (dispc_has_feature(dispc, FEAT_LINEBUFFERSPLIT)) { + l &= ~(0x1 << 22); + l |= five_taps ? (1 << 22) : 0; + } + + dispc_write_reg(dispc, DISPC_OVL_ATTRIBUTES(plane), l); + + /* + * field 0 = even field = bottom field + * field 1 = odd field = top field + */ + if (ilace && !fieldmode) { + accu1 = 0; + accu0 = ((1024 * orig_height / out_height) / 2) & 0x3ff; + if (accu0 >= 1024/2) { + accu1 = 1024/2; + accu0 -= accu1; + } + } + + dispc_ovl_set_vid_accu0(dispc, plane, 0, accu0); + dispc_ovl_set_vid_accu1(dispc, plane, 0, accu1); +} + +static void dispc_ovl_set_scaling_uv(struct dispc_device *dispc, + enum omap_plane_id plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, bool five_taps, + bool fieldmode, u32 fourcc, + u8 rotation) +{ + int scale_x = out_width != orig_width; + int scale_y = out_height != orig_height; + bool chroma_upscale = plane != OMAP_DSS_WB; + const struct drm_format_info *info; + + info = drm_format_info(fourcc); + + if (!dispc_has_feature(dispc, FEAT_HANDLE_UV_SEPARATE)) + return; + + if (!info->is_yuv) { + /* reset chroma resampling for RGB formats */ + if (plane != OMAP_DSS_WB) + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES2(plane), + 0, 8, 8); + return; + } + + dispc_ovl_set_accu_uv(dispc, plane, orig_width, orig_height, out_width, + out_height, ilace, fourcc, rotation); + + switch (fourcc) { + case DRM_FORMAT_NV12: + if (chroma_upscale) { + /* UV is subsampled by 2 horizontally and vertically */ + orig_height >>= 1; + orig_width >>= 1; + } else { + /* UV is downsampled by 2 horizontally and vertically */ + orig_height <<= 1; + orig_width <<= 1; + } + + break; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + /* For YUV422 with 90/270 rotation, we don't upsample chroma */ + if (!drm_rotation_90_or_270(rotation)) { + if (chroma_upscale) + /* UV is subsampled by 2 horizontally */ + orig_width >>= 1; + else + /* UV is downsampled by 2 horizontally */ + orig_width <<= 1; + } + + /* must use FIR for YUV422 if rotated */ + if ((rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_0) + scale_x = scale_y = true; + + break; + default: + BUG(); + return; + } + + if (out_width != orig_width) + scale_x = true; + if (out_height != orig_height) + scale_y = true; + + dispc_ovl_set_scale_param(dispc, plane, orig_width, orig_height, + out_width, out_height, five_taps, + rotation, DISPC_COLOR_COMPONENT_UV); + + if (plane != OMAP_DSS_WB) + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES2(plane), + (scale_x || scale_y) ? 1 : 0, 8, 8); + + /* set H scaling */ + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), scale_x ? 1 : 0, 5, 5); + /* set V scaling */ + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), scale_y ? 1 : 0, 6, 6); +} + +static void dispc_ovl_set_scaling(struct dispc_device *dispc, + enum omap_plane_id plane, + u16 orig_width, u16 orig_height, + u16 out_width, u16 out_height, + bool ilace, bool five_taps, + bool fieldmode, u32 fourcc, + u8 rotation) +{ + BUG_ON(plane == OMAP_DSS_GFX); + + dispc_ovl_set_scaling_common(dispc, plane, orig_width, orig_height, + out_width, out_height, ilace, five_taps, + fieldmode, fourcc, rotation); + + dispc_ovl_set_scaling_uv(dispc, plane, orig_width, orig_height, + out_width, out_height, ilace, five_taps, + fieldmode, fourcc, rotation); +} + +static void dispc_ovl_set_rotation_attrs(struct dispc_device *dispc, + enum omap_plane_id plane, u8 rotation, + enum omap_dss_rotation_type rotation_type, + u32 fourcc) +{ + bool row_repeat = false; + int vidrot = 0; + + /* Note: DSS HW rotates clockwise, DRM_MODE_ROTATE_* counter-clockwise */ + if (fourcc == DRM_FORMAT_YUYV || fourcc == DRM_FORMAT_UYVY) { + + if (rotation & DRM_MODE_REFLECT_X) { + switch (rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_0: + vidrot = 2; + break; + case DRM_MODE_ROTATE_90: + vidrot = 1; + break; + case DRM_MODE_ROTATE_180: + vidrot = 0; + break; + case DRM_MODE_ROTATE_270: + vidrot = 3; + break; + } + } else { + switch (rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_0: + vidrot = 0; + break; + case DRM_MODE_ROTATE_90: + vidrot = 3; + break; + case DRM_MODE_ROTATE_180: + vidrot = 2; + break; + case DRM_MODE_ROTATE_270: + vidrot = 1; + break; + } + } + + if (drm_rotation_90_or_270(rotation)) + row_repeat = true; + else + row_repeat = false; + } + + /* + * OMAP4/5 Errata i631: + * NV12 in 1D mode must use ROTATION=1. Otherwise DSS will fetch extra + * rows beyond the framebuffer, which may cause OCP error. + */ + if (fourcc == DRM_FORMAT_NV12 && rotation_type != OMAP_DSS_ROT_TILER) + vidrot = 1; + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), vidrot, 13, 12); + if (dispc_has_feature(dispc, FEAT_ROWREPEATENABLE)) + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), + row_repeat ? 1 : 0, 18, 18); + + if (dispc_ovl_color_mode_supported(dispc, plane, DRM_FORMAT_NV12)) { + bool doublestride = + fourcc == DRM_FORMAT_NV12 && + rotation_type == OMAP_DSS_ROT_TILER && + !drm_rotation_90_or_270(rotation); + + /* DOUBLESTRIDE */ + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), + doublestride, 22, 22); + } +} + +static int color_mode_to_bpp(u32 fourcc) +{ + switch (fourcc) { + case DRM_FORMAT_NV12: + return 8; + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + return 16; + case DRM_FORMAT_RGB888: + return 24; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + return 32; + default: + BUG(); + return 0; + } +} + +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; + + BUG(); +} + +static void calc_offset(u16 screen_width, u16 width, + u32 fourcc, bool fieldmode, unsigned int field_offset, + unsigned int *offset0, unsigned int *offset1, + s32 *row_inc, s32 *pix_inc, int x_predecim, int y_predecim, + enum omap_dss_rotation_type rotation_type, u8 rotation) +{ + u8 ps; + + ps = color_mode_to_bpp(fourcc) / 8; + + DSSDBG("scrw %d, width %d\n", screen_width, width); + + if (rotation_type == OMAP_DSS_ROT_TILER && + (fourcc == DRM_FORMAT_UYVY || fourcc == DRM_FORMAT_YUYV) && + drm_rotation_90_or_270(rotation)) { + /* + * HACK: ROW_INC needs to be calculated with TILER units. + * We get such 'screen_width' that multiplying it with the + * YUV422 pixel size gives the correct TILER container width. + * However, 'width' is in pixels and multiplying it with YUV422 + * pixel size gives incorrect result. We thus multiply it here + * with 2 to match the 32 bit TILER unit size. + */ + width *= 2; + } + + /* + * field 0 = even field = bottom field + * field 1 = odd field = top field + */ + *offset0 = field_offset * screen_width * ps; + *offset1 = 0; + + *row_inc = pixinc(1 + (y_predecim * screen_width - width * x_predecim) + + (fieldmode ? screen_width : 0), ps); + if (fourcc == DRM_FORMAT_YUYV || fourcc == DRM_FORMAT_UYVY) + *pix_inc = pixinc(x_predecim, 2 * ps); + else + *pix_inc = pixinc(x_predecim, ps); +} + +/* + * This function is used to avoid synclosts in OMAP3, because of some + * undocumented horizontal position and timing related limitations. + */ +static int check_horiz_timing_omap3(unsigned long pclk, unsigned long lclk, + const struct videomode *vm, u16 pos_x, + u16 width, u16 height, u16 out_width, u16 out_height, + bool five_taps) +{ + const int ds = DIV_ROUND_UP(height, out_height); + unsigned long nonactive; + static const u8 limits[3] = { 8, 10, 20 }; + u64 val, blank; + int i; + + nonactive = vm->hactive + vm->hfront_porch + vm->hsync_len + + vm->hback_porch - out_width; + + i = 0; + if (out_height < height) + i++; + if (out_width < width) + i++; + blank = div_u64((u64)(vm->hback_porch + vm->hsync_len + vm->hfront_porch) * + lclk, pclk); + DSSDBG("blanking period + ppl = %llu (limit = %u)\n", blank, limits[i]); + if (blank <= limits[i]) + return -EINVAL; + + /* FIXME add checks for 3-tap filter once the limitations are known */ + if (!five_taps) + return 0; + + /* + * Pixel data should be prepared before visible display point starts. + * So, atleast DS-2 lines must have already been fetched by DISPC + * during nonactive - pos_x period. + */ + val = div_u64((u64)(nonactive - pos_x) * lclk, pclk); + DSSDBG("(nonactive - pos_x) * pcd = %llu max(0, DS - 2) * width = %d\n", + val, max(0, ds - 2) * width); + if (val < max(0, ds - 2) * width) + return -EINVAL; + + /* + * All lines need to be refilled during the nonactive period of which + * only one line can be loaded during the active period. So, atleast + * DS - 1 lines should be loaded during nonactive period. + */ + val = div_u64((u64)nonactive * lclk, pclk); + DSSDBG("nonactive * pcd = %llu, max(0, DS - 1) * width = %d\n", + val, max(0, ds - 1) * width); + if (val < max(0, ds - 1) * width) + return -EINVAL; + + return 0; +} + +static unsigned long calc_core_clk_five_taps(unsigned long pclk, + const struct videomode *vm, u16 width, + u16 height, u16 out_width, u16 out_height, + u32 fourcc) +{ + u32 core_clk = 0; + u64 tmp; + + if (height <= out_height && width <= out_width) + return (unsigned long) pclk; + + if (height > out_height) { + unsigned int ppl = vm->hactive; + + tmp = (u64)pclk * height * out_width; + do_div(tmp, 2 * out_height * ppl); + core_clk = tmp; + + if (height > 2 * out_height) { + if (ppl == out_width) + return 0; + + tmp = (u64)pclk * (height - 2 * out_height) * out_width; + do_div(tmp, 2 * out_height * (ppl - out_width)); + core_clk = max_t(u32, core_clk, tmp); + } + } + + if (width > out_width) { + tmp = (u64)pclk * width; + do_div(tmp, out_width); + core_clk = max_t(u32, core_clk, tmp); + + if (fourcc == DRM_FORMAT_XRGB8888) + core_clk <<= 1; + } + + return core_clk; +} + +static unsigned long calc_core_clk_24xx(unsigned long pclk, u16 width, + u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ + if (height > out_height && width > out_width) + return pclk * 4; + else + return pclk * 2; +} + +static unsigned long calc_core_clk_34xx(unsigned long pclk, u16 width, + u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ + unsigned int hf, vf; + + /* + * FIXME how to determine the 'A' factor + * for the no downscaling case ? + */ + + if (width > 3 * out_width) + hf = 4; + else if (width > 2 * out_width) + hf = 3; + else if (width > out_width) + hf = 2; + else + hf = 1; + if (height > out_height) + vf = 2; + else + vf = 1; + + return pclk * vf * hf; +} + +static unsigned long calc_core_clk_44xx(unsigned long pclk, u16 width, + u16 height, u16 out_width, u16 out_height, bool mem_to_mem) +{ + /* + * If the overlay/writeback is in mem to mem mode, there are no + * downscaling limitations with respect to pixel clock, return 1 as + * required core clock to represent that we have sufficient enough + * core clock to do maximum downscaling + */ + if (mem_to_mem) + return 1; + + if (width > out_width) + return DIV_ROUND_UP(pclk, out_width) * width; + else + return pclk; +} + +static int dispc_ovl_calc_scaling_24xx(struct dispc_device *dispc, + unsigned long pclk, unsigned long lclk, + const struct videomode *vm, + u16 width, u16 height, + u16 out_width, u16 out_height, + u32 fourcc, bool *five_taps, + int *x_predecim, int *y_predecim, + int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, + bool mem_to_mem) +{ + int error; + u16 in_width, in_height; + int min_factor = min(*decim_x, *decim_y); + const int maxsinglelinewidth = dispc->feat->max_line_width; + + *five_taps = false; + + do { + in_height = height / *decim_y; + in_width = width / *decim_x; + *core_clk = dispc->feat->calc_core_clk(pclk, in_width, + in_height, out_width, out_height, mem_to_mem); + error = (in_width > maxsinglelinewidth || !*core_clk || + *core_clk > dispc_core_clk_rate(dispc)); + if (error) { + if (*decim_x == *decim_y) { + *decim_x = min_factor; + ++*decim_y; + } else { + swap(*decim_x, *decim_y); + if (*decim_x < *decim_y) + ++*decim_x; + } + } + } while (*decim_x <= *x_predecim && *decim_y <= *y_predecim && error); + + if (error) { + DSSERR("failed to find scaling settings\n"); + return -EINVAL; + } + + if (in_width > maxsinglelinewidth) { + DSSERR("Cannot scale max input width exceeded\n"); + return -EINVAL; + } + return 0; +} + +static int dispc_ovl_calc_scaling_34xx(struct dispc_device *dispc, + unsigned long pclk, unsigned long lclk, + const struct videomode *vm, + u16 width, u16 height, + u16 out_width, u16 out_height, + u32 fourcc, bool *five_taps, + int *x_predecim, int *y_predecim, + int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, + bool mem_to_mem) +{ + int error; + u16 in_width, in_height; + const int maxsinglelinewidth = dispc->feat->max_line_width; + + do { + in_height = height / *decim_y; + in_width = width / *decim_x; + *five_taps = in_height > out_height; + + if (in_width > maxsinglelinewidth) + if (in_height > out_height && + in_height < out_height * 2) + *five_taps = false; +again: + if (*five_taps) + *core_clk = calc_core_clk_five_taps(pclk, vm, + in_width, in_height, out_width, + out_height, fourcc); + else + *core_clk = dispc->feat->calc_core_clk(pclk, in_width, + in_height, out_width, out_height, + mem_to_mem); + + error = check_horiz_timing_omap3(pclk, lclk, vm, + pos_x, in_width, in_height, out_width, + out_height, *five_taps); + if (error && *five_taps) { + *five_taps = false; + goto again; + } + + error = (error || in_width > maxsinglelinewidth * 2 || + (in_width > maxsinglelinewidth && *five_taps) || + !*core_clk || *core_clk > dispc_core_clk_rate(dispc)); + + if (!error) { + /* verify that we're inside the limits of scaler */ + if (in_width / 4 > out_width) + error = 1; + + if (*five_taps) { + if (in_height / 4 > out_height) + error = 1; + } else { + if (in_height / 2 > out_height) + error = 1; + } + } + + if (error) + ++*decim_y; + } while (*decim_x <= *x_predecim && *decim_y <= *y_predecim && error); + + if (error) { + DSSERR("failed to find scaling settings\n"); + return -EINVAL; + } + + if (check_horiz_timing_omap3(pclk, lclk, vm, pos_x, in_width, + in_height, out_width, out_height, *five_taps)) { + DSSERR("horizontal timing too tight\n"); + return -EINVAL; + } + + if (in_width > (maxsinglelinewidth * 2)) { + DSSERR("Cannot setup scaling\n"); + DSSERR("width exceeds maximum width possible\n"); + return -EINVAL; + } + + if (in_width > maxsinglelinewidth && *five_taps) { + DSSERR("cannot setup scaling with five taps\n"); + return -EINVAL; + } + return 0; +} + +static int dispc_ovl_calc_scaling_44xx(struct dispc_device *dispc, + unsigned long pclk, unsigned long lclk, + const struct videomode *vm, + u16 width, u16 height, + u16 out_width, u16 out_height, + u32 fourcc, bool *five_taps, + int *x_predecim, int *y_predecim, + int *decim_x, int *decim_y, + u16 pos_x, unsigned long *core_clk, + bool mem_to_mem) +{ + u16 in_width, in_width_max; + int decim_x_min = *decim_x; + u16 in_height = height / *decim_y; + const int maxsinglelinewidth = dispc->feat->max_line_width; + const int maxdownscale = dispc->feat->max_downscale; + + if (mem_to_mem) { + in_width_max = out_width * maxdownscale; + } else { + in_width_max = dispc_core_clk_rate(dispc) + / DIV_ROUND_UP(pclk, out_width); + } + + *decim_x = DIV_ROUND_UP(width, in_width_max); + + *decim_x = max(*decim_x, decim_x_min); + if (*decim_x > *x_predecim) + return -EINVAL; + + do { + in_width = width / *decim_x; + } while (*decim_x <= *x_predecim && + in_width > maxsinglelinewidth && ++*decim_x); + + if (in_width > maxsinglelinewidth) { + DSSERR("Cannot scale width exceeds max line width\n"); + return -EINVAL; + } + + if (*decim_x > 4 && fourcc != DRM_FORMAT_NV12) { + /* + * Let's disable all scaling that requires horizontal + * decimation with higher factor than 4, until we have + * better estimates of what we can and can not + * do. However, NV12 color format appears to work Ok + * with all decimation factors. + * + * When decimating horizontally by more that 4 the dss + * is not able to fetch the data in burst mode. When + * this happens it is hard to tell if there enough + * bandwidth. Despite what theory says this appears to + * be true also for 16-bit color formats. + */ + DSSERR("Not enough bandwidth, too much downscaling (x-decimation factor %d > 4)\n", *decim_x); + + return -EINVAL; + } + + *core_clk = dispc->feat->calc_core_clk(pclk, in_width, in_height, + out_width, out_height, mem_to_mem); + return 0; +} + +enum omap_overlay_caps dispc_ovl_get_caps(struct dispc_device *dispc, enum omap_plane_id plane) +{ + return dispc->feat->overlay_caps[plane]; +} + +#define DIV_FRAC(dividend, divisor) \ + ((dividend) * 100 / (divisor) - ((dividend) / (divisor) * 100)) + +static int dispc_ovl_calc_scaling(struct dispc_device *dispc, + enum omap_plane_id plane, + unsigned long pclk, unsigned long lclk, + enum omap_overlay_caps caps, + const struct videomode *vm, + u16 width, u16 height, + u16 out_width, u16 out_height, + u32 fourcc, bool *five_taps, + int *x_predecim, int *y_predecim, u16 pos_x, + enum omap_dss_rotation_type rotation_type, + bool mem_to_mem) +{ + int maxhdownscale = dispc->feat->max_downscale; + int maxvdownscale = dispc->feat->max_downscale; + const int max_decim_limit = 16; + unsigned long core_clk = 0; + int decim_x, decim_y, ret; + + if (width == out_width && height == out_height) + return 0; + + if (dispc->feat->supported_scaler_color_modes) { + const u32 *modes = dispc->feat->supported_scaler_color_modes; + unsigned int i; + + for (i = 0; modes[i]; ++i) { + if (modes[i] == fourcc) + break; + } + + if (modes[i] == 0) + return -EINVAL; + } + + if (plane == OMAP_DSS_WB) { + switch (fourcc) { + case DRM_FORMAT_NV12: + maxhdownscale = maxvdownscale = 2; + break; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_UYVY: + maxhdownscale = 2; + maxvdownscale = 4; + break; + default: + break; + } + } + if (!mem_to_mem && (pclk == 0 || vm->pixelclock == 0)) { + DSSERR("cannot calculate scaling settings: pclk is zero\n"); + return -EINVAL; + } + + if ((caps & OMAP_DSS_OVL_CAP_SCALE) == 0) + return -EINVAL; + + if (mem_to_mem) { + *x_predecim = *y_predecim = 1; + } else { + *x_predecim = max_decim_limit; + *y_predecim = (rotation_type == OMAP_DSS_ROT_TILER && + dispc_has_feature(dispc, FEAT_BURST_2D)) ? + 2 : max_decim_limit; + } + + decim_x = DIV_ROUND_UP(DIV_ROUND_UP(width, out_width), maxhdownscale); + decim_y = DIV_ROUND_UP(DIV_ROUND_UP(height, out_height), maxvdownscale); + + if (decim_x > *x_predecim || out_width > width * 8) + return -EINVAL; + + if (decim_y > *y_predecim || out_height > height * 8) + return -EINVAL; + + ret = dispc->feat->calc_scaling(dispc, pclk, lclk, vm, width, height, + out_width, out_height, fourcc, + five_taps, x_predecim, y_predecim, + &decim_x, &decim_y, pos_x, &core_clk, + mem_to_mem); + if (ret) + return ret; + + DSSDBG("%dx%d -> %dx%d (%d.%02d x %d.%02d), decim %dx%d %dx%d (%d.%02d x %d.%02d), taps %d, req clk %lu, cur clk %lu\n", + width, height, + out_width, out_height, + out_width / width, DIV_FRAC(out_width, width), + out_height / height, DIV_FRAC(out_height, height), + + decim_x, decim_y, + width / decim_x, height / decim_y, + out_width / (width / decim_x), DIV_FRAC(out_width, width / decim_x), + out_height / (height / decim_y), DIV_FRAC(out_height, height / decim_y), + + *five_taps ? 5 : 3, + core_clk, dispc_core_clk_rate(dispc)); + + if (!core_clk || core_clk > dispc_core_clk_rate(dispc)) { + DSSERR("failed to set up scaling, " + "required core clk rate = %lu Hz, " + "current core clk rate = %lu Hz\n", + core_clk, dispc_core_clk_rate(dispc)); + return -EINVAL; + } + + *x_predecim = decim_x; + *y_predecim = decim_y; + return 0; +} + +void dispc_ovl_get_max_size(struct dispc_device *dispc, u16 *width, u16 *height) +{ + *width = dispc->feat->ovl_width_max; + *height = dispc->feat->ovl_height_max; +} + +static int dispc_ovl_setup_common(struct dispc_device *dispc, + enum omap_plane_id plane, + enum omap_overlay_caps caps, + u32 paddr, u32 p_uv_addr, + u16 screen_width, int pos_x, int pos_y, + u16 width, u16 height, + u16 out_width, u16 out_height, + u32 fourcc, u8 rotation, u8 zorder, + u8 pre_mult_alpha, u8 global_alpha, + enum omap_dss_rotation_type rotation_type, + bool replication, const struct videomode *vm, + bool mem_to_mem, + enum drm_color_encoding color_encoding, + enum drm_color_range color_range) +{ + bool five_taps = true; + bool fieldmode = false; + int r, cconv = 0; + unsigned int offset0, offset1; + s32 row_inc; + s32 pix_inc; + u16 frame_width; + unsigned int field_offset = 0; + u16 in_height = height; + u16 in_width = width; + int x_predecim = 1, y_predecim = 1; + bool ilace = !!(vm->flags & DISPLAY_FLAGS_INTERLACED); + unsigned long pclk = dispc_plane_pclk_rate(dispc, plane); + unsigned long lclk = dispc_plane_lclk_rate(dispc, plane); + const struct drm_format_info *info; + + info = drm_format_info(fourcc); + + /* when setting up WB, dispc_plane_pclk_rate() returns 0 */ + if (plane == OMAP_DSS_WB) + pclk = vm->pixelclock; + + if (paddr == 0 && rotation_type != OMAP_DSS_ROT_TILER) + return -EINVAL; + + if (info->is_yuv && (in_width & 1)) { + DSSERR("input width %d is not even for YUV format\n", in_width); + return -EINVAL; + } + + out_width = out_width == 0 ? width : out_width; + out_height = out_height == 0 ? height : out_height; + + if (plane != OMAP_DSS_WB) { + if (ilace && height == out_height) + fieldmode = true; + + if (ilace) { + if (fieldmode) + in_height /= 2; + pos_y /= 2; + out_height /= 2; + + DSSDBG("adjusting for ilace: height %d, pos_y %d, out_height %d\n", + in_height, pos_y, out_height); + } + } + + if (!dispc_ovl_color_mode_supported(dispc, plane, fourcc)) + return -EINVAL; + + r = dispc_ovl_calc_scaling(dispc, plane, pclk, lclk, caps, vm, in_width, + in_height, out_width, out_height, fourcc, + &five_taps, &x_predecim, &y_predecim, pos_x, + rotation_type, mem_to_mem); + if (r) + return r; + + in_width = in_width / x_predecim; + in_height = in_height / y_predecim; + + if (x_predecim > 1 || y_predecim > 1) + DSSDBG("predecimation %d x %x, new input size %d x %d\n", + x_predecim, y_predecim, in_width, in_height); + + if (info->is_yuv && (in_width & 1)) { + DSSDBG("predecimated input width is not even for YUV format\n"); + DSSDBG("adjusting input width %d -> %d\n", + in_width, in_width & ~1); + + in_width &= ~1; + } + + if (info->is_yuv) + cconv = 1; + + if (ilace && !fieldmode) { + /* + * when downscaling the bottom field may have to start several + * source lines below the top field. Unfortunately ACCUI + * registers will only hold the fractional part of the offset + * so the integer part must be added to the base address of the + * bottom field. + */ + if (!in_height || in_height == out_height) + field_offset = 0; + else + field_offset = in_height / out_height / 2; + } + + /* Fields are independent but interleaved in memory. */ + if (fieldmode) + field_offset = 1; + + offset0 = 0; + offset1 = 0; + row_inc = 0; + pix_inc = 0; + + if (plane == OMAP_DSS_WB) + frame_width = out_width; + else + frame_width = in_width; + + calc_offset(screen_width, frame_width, + fourcc, fieldmode, field_offset, + &offset0, &offset1, &row_inc, &pix_inc, + x_predecim, y_predecim, + rotation_type, rotation); + + DSSDBG("offset0 %u, offset1 %u, row_inc %d, pix_inc %d\n", + offset0, offset1, row_inc, pix_inc); + + dispc_ovl_set_color_mode(dispc, plane, fourcc); + + dispc_ovl_configure_burst_type(dispc, plane, rotation_type); + + if (dispc->feat->reverse_ilace_field_order) + swap(offset0, offset1); + + dispc_ovl_set_ba0(dispc, plane, paddr + offset0); + dispc_ovl_set_ba1(dispc, plane, paddr + offset1); + + if (fourcc == DRM_FORMAT_NV12) { + dispc_ovl_set_ba0_uv(dispc, plane, p_uv_addr + offset0); + dispc_ovl_set_ba1_uv(dispc, plane, p_uv_addr + offset1); + } + + if (dispc->feat->last_pixel_inc_missing) + row_inc += pix_inc - 1; + + dispc_ovl_set_row_inc(dispc, plane, row_inc); + dispc_ovl_set_pix_inc(dispc, plane, pix_inc); + + DSSDBG("%d,%d %dx%d -> %dx%d\n", pos_x, pos_y, in_width, + in_height, out_width, out_height); + + dispc_ovl_set_pos(dispc, plane, caps, pos_x, pos_y); + + dispc_ovl_set_input_size(dispc, plane, in_width, in_height); + + if (caps & OMAP_DSS_OVL_CAP_SCALE) { + dispc_ovl_set_scaling(dispc, plane, in_width, in_height, + out_width, out_height, ilace, five_taps, + fieldmode, fourcc, rotation); + dispc_ovl_set_output_size(dispc, plane, out_width, out_height); + dispc_ovl_set_vid_color_conv(dispc, plane, cconv); + + if (plane != OMAP_DSS_WB) + dispc_ovl_set_csc(dispc, plane, color_encoding, color_range); + } + + dispc_ovl_set_rotation_attrs(dispc, plane, rotation, rotation_type, + fourcc); + + dispc_ovl_set_zorder(dispc, plane, caps, zorder); + dispc_ovl_set_pre_mult_alpha(dispc, plane, caps, pre_mult_alpha); + dispc_ovl_setup_global_alpha(dispc, plane, caps, global_alpha); + + dispc_ovl_enable_replication(dispc, plane, caps, replication); + + return 0; +} + +int dispc_ovl_setup(struct dispc_device *dispc, + enum omap_plane_id plane, + const struct omap_overlay_info *oi, + const struct videomode *vm, bool mem_to_mem, + enum omap_channel channel) +{ + int r; + enum omap_overlay_caps caps = dispc->feat->overlay_caps[plane]; + const bool replication = true; + + DSSDBG("dispc_ovl_setup %d, pa %pad, pa_uv %pad, sw %d, %d,%d, %dx%d ->" + " %dx%d, cmode %x, rot %d, chan %d repl %d\n", + plane, &oi->paddr, &oi->p_uv_addr, oi->screen_width, oi->pos_x, + oi->pos_y, oi->width, oi->height, oi->out_width, oi->out_height, + oi->fourcc, oi->rotation, channel, replication); + + dispc_ovl_set_channel_out(dispc, plane, channel); + + r = dispc_ovl_setup_common(dispc, plane, caps, oi->paddr, oi->p_uv_addr, + oi->screen_width, oi->pos_x, oi->pos_y, oi->width, oi->height, + oi->out_width, oi->out_height, oi->fourcc, oi->rotation, + oi->zorder, oi->pre_mult_alpha, oi->global_alpha, + oi->rotation_type, replication, vm, mem_to_mem, + oi->color_encoding, oi->color_range); + + return r; +} + +int dispc_ovl_enable(struct dispc_device *dispc, + enum omap_plane_id plane, bool enable) +{ + DSSDBG("dispc_enable_plane %d, %d\n", plane, enable); + + REG_FLD_MOD(dispc, DISPC_OVL_ATTRIBUTES(plane), enable ? 1 : 0, 0, 0); + + return 0; +} + +static void dispc_lcd_enable_signal_polarity(struct dispc_device *dispc, + bool act_high) +{ + if (!dispc_has_feature(dispc, FEAT_LCDENABLEPOL)) + return; + + REG_FLD_MOD(dispc, DISPC_CONTROL, act_high ? 1 : 0, 29, 29); +} + +void dispc_lcd_enable_signal(struct dispc_device *dispc, bool enable) +{ + if (!dispc_has_feature(dispc, FEAT_LCDENABLESIGNAL)) + return; + + REG_FLD_MOD(dispc, DISPC_CONTROL, enable ? 1 : 0, 28, 28); +} + +void dispc_pck_free_enable(struct dispc_device *dispc, bool enable) +{ + if (!dispc_has_feature(dispc, FEAT_PCKFREEENABLE)) + return; + + REG_FLD_MOD(dispc, DISPC_CONTROL, enable ? 1 : 0, 27, 27); +} + +static void dispc_mgr_enable_fifohandcheck(struct dispc_device *dispc, + enum omap_channel channel, + bool enable) +{ + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_FIFOHANDCHECK, enable); +} + + +static void dispc_mgr_set_lcd_type_tft(struct dispc_device *dispc, + enum omap_channel channel) +{ + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_STNTFT, 1); +} + +static void dispc_set_loadmode(struct dispc_device *dispc, + enum omap_dss_load_mode mode) +{ + REG_FLD_MOD(dispc, DISPC_CONFIG, mode, 2, 1); +} + + +static void dispc_mgr_set_default_color(struct dispc_device *dispc, + enum omap_channel channel, u32 color) +{ + dispc_write_reg(dispc, DISPC_DEFAULT_COLOR(channel), color); +} + +static void dispc_mgr_set_trans_key(struct dispc_device *dispc, + enum omap_channel ch, + enum omap_dss_trans_key_type type, + u32 trans_key) +{ + mgr_fld_write(dispc, ch, DISPC_MGR_FLD_TCKSELECTION, type); + + dispc_write_reg(dispc, DISPC_TRANS_COLOR(ch), trans_key); +} + +static void dispc_mgr_enable_trans_key(struct dispc_device *dispc, + enum omap_channel ch, bool enable) +{ + mgr_fld_write(dispc, ch, DISPC_MGR_FLD_TCKENABLE, enable); +} + +static void dispc_mgr_enable_alpha_fixed_zorder(struct dispc_device *dispc, + enum omap_channel ch, + bool enable) +{ + if (!dispc_has_feature(dispc, FEAT_ALPHA_FIXED_ZORDER)) + return; + + if (ch == OMAP_DSS_CHANNEL_LCD) + REG_FLD_MOD(dispc, DISPC_CONFIG, enable, 18, 18); + else if (ch == OMAP_DSS_CHANNEL_DIGIT) + REG_FLD_MOD(dispc, DISPC_CONFIG, enable, 19, 19); +} + +void dispc_mgr_setup(struct dispc_device *dispc, + enum omap_channel channel, + const struct omap_overlay_manager_info *info) +{ + dispc_mgr_set_default_color(dispc, channel, info->default_color); + dispc_mgr_set_trans_key(dispc, channel, info->trans_key_type, + info->trans_key); + dispc_mgr_enable_trans_key(dispc, channel, info->trans_enabled); + dispc_mgr_enable_alpha_fixed_zorder(dispc, channel, + info->partial_alpha_enabled); + if (dispc_has_feature(dispc, FEAT_CPR)) { + dispc_mgr_enable_cpr(dispc, channel, info->cpr_enable); + dispc_mgr_set_cpr_coef(dispc, channel, &info->cpr_coefs); + } +} + +static void dispc_mgr_set_tft_data_lines(struct dispc_device *dispc, + enum omap_channel channel, + u8 data_lines) +{ + int code; + + switch (data_lines) { + case 12: + code = 0; + break; + case 16: + code = 1; + break; + case 18: + code = 2; + break; + case 24: + code = 3; + break; + default: + BUG(); + return; + } + + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_TFTDATALINES, code); +} + +static void dispc_mgr_set_io_pad_mode(struct dispc_device *dispc, + enum dss_io_pad_mode mode) +{ + u32 l; + int gpout0, gpout1; + + switch (mode) { + case DSS_IO_PAD_MODE_RESET: + gpout0 = 0; + gpout1 = 0; + break; + case DSS_IO_PAD_MODE_RFBI: + gpout0 = 1; + gpout1 = 0; + break; + case DSS_IO_PAD_MODE_BYPASS: + gpout0 = 1; + gpout1 = 1; + break; + default: + BUG(); + return; + } + + l = dispc_read_reg(dispc, DISPC_CONTROL); + l = FLD_MOD(l, gpout0, 15, 15); + l = FLD_MOD(l, gpout1, 16, 16); + dispc_write_reg(dispc, DISPC_CONTROL, l); +} + +static void dispc_mgr_enable_stallmode(struct dispc_device *dispc, + enum omap_channel channel, bool enable) +{ + mgr_fld_write(dispc, channel, DISPC_MGR_FLD_STALLMODE, enable); +} + +void dispc_mgr_set_lcd_config(struct dispc_device *dispc, + enum omap_channel channel, + const struct dss_lcd_mgr_config *config) +{ + dispc_mgr_set_io_pad_mode(dispc, config->io_pad_mode); + + dispc_mgr_enable_stallmode(dispc, channel, config->stallmode); + dispc_mgr_enable_fifohandcheck(dispc, channel, config->fifohandcheck); + + dispc_mgr_set_clock_div(dispc, channel, &config->clock_info); + + dispc_mgr_set_tft_data_lines(dispc, channel, config->video_port_width); + + dispc_lcd_enable_signal_polarity(dispc, config->lcden_sig_polarity); + + dispc_mgr_set_lcd_type_tft(dispc, channel); +} + +static bool _dispc_mgr_size_ok(struct dispc_device *dispc, + u16 width, u16 height) +{ + return width <= dispc->feat->mgr_width_max && + height <= dispc->feat->mgr_height_max; +} + +static bool _dispc_lcd_timings_ok(struct dispc_device *dispc, + int hsync_len, int hfp, int hbp, + int vsw, int vfp, int vbp) +{ + if (hsync_len < 1 || hsync_len > dispc->feat->sw_max || + hfp < 1 || hfp > dispc->feat->hp_max || + hbp < 1 || hbp > dispc->feat->hp_max || + vsw < 1 || vsw > dispc->feat->sw_max || + vfp < 0 || vfp > dispc->feat->vp_max || + vbp < 0 || vbp > dispc->feat->vp_max) + return false; + return true; +} + +static bool _dispc_mgr_pclk_ok(struct dispc_device *dispc, + enum omap_channel channel, + unsigned long pclk) +{ + if (dss_mgr_is_lcd(channel)) + return pclk <= dispc->feat->max_lcd_pclk; + else + return pclk <= dispc->feat->max_tv_pclk; +} + +int dispc_mgr_check_timings(struct dispc_device *dispc, + enum omap_channel channel, + const struct videomode *vm) +{ + if (!_dispc_mgr_size_ok(dispc, vm->hactive, vm->vactive)) + return MODE_BAD; + + if (!_dispc_mgr_pclk_ok(dispc, channel, vm->pixelclock)) + return MODE_BAD; + + if (dss_mgr_is_lcd(channel)) { + /* TODO: OMAP4+ supports interlace for LCD outputs */ + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + return MODE_BAD; + + if (!_dispc_lcd_timings_ok(dispc, vm->hsync_len, + vm->hfront_porch, vm->hback_porch, + vm->vsync_len, vm->vfront_porch, + vm->vback_porch)) + return MODE_BAD; + } + + return MODE_OK; +} + +static void _dispc_mgr_set_lcd_timings(struct dispc_device *dispc, + enum omap_channel channel, + const struct videomode *vm) +{ + u32 timing_h, timing_v, l; + bool onoff, rf, ipc, vs, hs, de; + + timing_h = FLD_VAL(vm->hsync_len - 1, dispc->feat->sw_start, 0) | + FLD_VAL(vm->hfront_porch - 1, dispc->feat->fp_start, 8) | + FLD_VAL(vm->hback_porch - 1, dispc->feat->bp_start, 20); + timing_v = FLD_VAL(vm->vsync_len - 1, dispc->feat->sw_start, 0) | + FLD_VAL(vm->vfront_porch, dispc->feat->fp_start, 8) | + FLD_VAL(vm->vback_porch, dispc->feat->bp_start, 20); + + dispc_write_reg(dispc, DISPC_TIMING_H(channel), timing_h); + dispc_write_reg(dispc, DISPC_TIMING_V(channel), timing_v); + + vs = !!(vm->flags & DISPLAY_FLAGS_VSYNC_LOW); + hs = !!(vm->flags & DISPLAY_FLAGS_HSYNC_LOW); + de = !!(vm->flags & DISPLAY_FLAGS_DE_LOW); + ipc = !!(vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE); + onoff = true; /* always use the 'rf' setting */ + rf = !!(vm->flags & DISPLAY_FLAGS_SYNC_POSEDGE); + + l = FLD_VAL(onoff, 17, 17) | + FLD_VAL(rf, 16, 16) | + FLD_VAL(de, 15, 15) | + FLD_VAL(ipc, 14, 14) | + FLD_VAL(hs, 13, 13) | + FLD_VAL(vs, 12, 12); + + /* always set ALIGN bit when available */ + if (dispc->feat->supports_sync_align) + l |= (1 << 18); + + dispc_write_reg(dispc, DISPC_POL_FREQ(channel), l); + + if (dispc->syscon_pol) { + const int shifts[] = { + [OMAP_DSS_CHANNEL_LCD] = 0, + [OMAP_DSS_CHANNEL_LCD2] = 1, + [OMAP_DSS_CHANNEL_LCD3] = 2, + }; + + u32 mask, val; + + mask = (1 << 0) | (1 << 3) | (1 << 6); + val = (rf << 0) | (ipc << 3) | (onoff << 6); + + mask <<= 16 + shifts[channel]; + val <<= 16 + shifts[channel]; + + regmap_update_bits(dispc->syscon_pol, dispc->syscon_pol_offset, + mask, val); + } +} + +static int vm_flag_to_int(enum display_flags flags, enum display_flags high, + enum display_flags low) +{ + if (flags & high) + return 1; + if (flags & low) + return -1; + return 0; +} + +/* change name to mode? */ +void dispc_mgr_set_timings(struct dispc_device *dispc, + enum omap_channel channel, + const struct videomode *vm) +{ + unsigned int xtot, ytot; + unsigned long ht, vt; + struct videomode t = *vm; + + DSSDBG("channel %d xres %u yres %u\n", channel, t.hactive, t.vactive); + + if (dispc_mgr_check_timings(dispc, channel, &t)) { + BUG(); + return; + } + + if (dss_mgr_is_lcd(channel)) { + _dispc_mgr_set_lcd_timings(dispc, channel, &t); + + xtot = t.hactive + t.hfront_porch + t.hsync_len + t.hback_porch; + ytot = t.vactive + t.vfront_porch + t.vsync_len + t.vback_porch; + + ht = vm->pixelclock / xtot; + vt = vm->pixelclock / xtot / ytot; + + DSSDBG("pck %lu\n", vm->pixelclock); + DSSDBG("hsync_len %d hfp %d hbp %d vsw %d vfp %d vbp %d\n", + t.hsync_len, t.hfront_porch, t.hback_porch, + t.vsync_len, t.vfront_porch, t.vback_porch); + DSSDBG("vsync_level %d hsync_level %d data_pclk_edge %d de_level %d sync_pclk_edge %d\n", + vm_flag_to_int(t.flags, DISPLAY_FLAGS_VSYNC_HIGH, DISPLAY_FLAGS_VSYNC_LOW), + vm_flag_to_int(t.flags, DISPLAY_FLAGS_HSYNC_HIGH, DISPLAY_FLAGS_HSYNC_LOW), + vm_flag_to_int(t.flags, DISPLAY_FLAGS_PIXDATA_POSEDGE, DISPLAY_FLAGS_PIXDATA_NEGEDGE), + vm_flag_to_int(t.flags, DISPLAY_FLAGS_DE_HIGH, DISPLAY_FLAGS_DE_LOW), + vm_flag_to_int(t.flags, DISPLAY_FLAGS_SYNC_POSEDGE, DISPLAY_FLAGS_SYNC_NEGEDGE)); + + DSSDBG("hsync %luHz, vsync %luHz\n", ht, vt); + } else { + if (t.flags & DISPLAY_FLAGS_INTERLACED) + t.vactive /= 2; + + if (dispc->feat->supports_double_pixel) + REG_FLD_MOD(dispc, DISPC_CONTROL, + !!(t.flags & DISPLAY_FLAGS_DOUBLECLK), + 19, 17); + } + + dispc_mgr_set_size(dispc, channel, t.hactive, t.vactive); +} + +static void dispc_mgr_set_lcd_divisor(struct dispc_device *dispc, + enum omap_channel channel, u16 lck_div, + u16 pck_div) +{ + BUG_ON(lck_div < 1); + BUG_ON(pck_div < 1); + + dispc_write_reg(dispc, DISPC_DIVISORo(channel), + FLD_VAL(lck_div, 23, 16) | FLD_VAL(pck_div, 7, 0)); + + if (!dispc_has_feature(dispc, FEAT_CORE_CLK_DIV) && + channel == OMAP_DSS_CHANNEL_LCD) + dispc->core_clk_rate = dispc_fclk_rate(dispc) / lck_div; +} + +static void dispc_mgr_get_lcd_divisor(struct dispc_device *dispc, + enum omap_channel channel, int *lck_div, + int *pck_div) +{ + u32 l; + l = dispc_read_reg(dispc, DISPC_DIVISORo(channel)); + *lck_div = FLD_GET(l, 23, 16); + *pck_div = FLD_GET(l, 7, 0); +} + +static unsigned long dispc_fclk_rate(struct dispc_device *dispc) +{ + unsigned long r; + enum dss_clk_source src; + + src = dss_get_dispc_clk_source(dispc->dss); + + if (src == DSS_CLK_SRC_FCK) { + r = dss_get_dispc_clk_rate(dispc->dss); + } else { + struct dss_pll *pll; + unsigned int clkout_idx; + + pll = dss_pll_find_by_src(dispc->dss, src); + clkout_idx = dss_pll_get_clkout_idx_for_src(src); + + r = pll->cinfo.clkout[clkout_idx]; + } + + return r; +} + +static unsigned long dispc_mgr_lclk_rate(struct dispc_device *dispc, + enum omap_channel channel) +{ + int lcd; + unsigned long r; + enum dss_clk_source src; + + /* for TV, LCLK rate is the FCLK rate */ + if (!dss_mgr_is_lcd(channel)) + return dispc_fclk_rate(dispc); + + src = dss_get_lcd_clk_source(dispc->dss, channel); + + if (src == DSS_CLK_SRC_FCK) { + r = dss_get_dispc_clk_rate(dispc->dss); + } else { + struct dss_pll *pll; + unsigned int clkout_idx; + + pll = dss_pll_find_by_src(dispc->dss, src); + clkout_idx = dss_pll_get_clkout_idx_for_src(src); + + r = pll->cinfo.clkout[clkout_idx]; + } + + lcd = REG_GET(dispc, DISPC_DIVISORo(channel), 23, 16); + + return r / lcd; +} + +static unsigned long dispc_mgr_pclk_rate(struct dispc_device *dispc, + enum omap_channel channel) +{ + unsigned long r; + + if (dss_mgr_is_lcd(channel)) { + int pcd; + u32 l; + + l = dispc_read_reg(dispc, DISPC_DIVISORo(channel)); + + pcd = FLD_GET(l, 7, 0); + + r = dispc_mgr_lclk_rate(dispc, channel); + + return r / pcd; + } else { + return dispc->tv_pclk_rate; + } +} + +void dispc_set_tv_pclk(struct dispc_device *dispc, unsigned long pclk) +{ + dispc->tv_pclk_rate = pclk; +} + +static unsigned long dispc_core_clk_rate(struct dispc_device *dispc) +{ + return dispc->core_clk_rate; +} + +static unsigned long dispc_plane_pclk_rate(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + enum omap_channel channel; + + if (plane == OMAP_DSS_WB) + return 0; + + channel = dispc_ovl_get_channel_out(dispc, plane); + + return dispc_mgr_pclk_rate(dispc, channel); +} + +static unsigned long dispc_plane_lclk_rate(struct dispc_device *dispc, + enum omap_plane_id plane) +{ + enum omap_channel channel; + + if (plane == OMAP_DSS_WB) + return 0; + + channel = dispc_ovl_get_channel_out(dispc, plane); + + return dispc_mgr_lclk_rate(dispc, channel); +} + +static void dispc_dump_clocks_channel(struct dispc_device *dispc, + struct seq_file *s, + enum omap_channel channel) +{ + int lcd, pcd; + enum dss_clk_source lcd_clk_src; + + seq_printf(s, "- %s -\n", mgr_desc[channel].name); + + lcd_clk_src = dss_get_lcd_clk_source(dispc->dss, channel); + + seq_printf(s, "%s clk source = %s\n", mgr_desc[channel].name, + dss_get_clk_source_name(lcd_clk_src)); + + dispc_mgr_get_lcd_divisor(dispc, channel, &lcd, &pcd); + + seq_printf(s, "lck\t\t%-16lulck div\t%u\n", + dispc_mgr_lclk_rate(dispc, channel), lcd); + seq_printf(s, "pck\t\t%-16lupck div\t%u\n", + dispc_mgr_pclk_rate(dispc, channel), pcd); +} + +void dispc_dump_clocks(struct dispc_device *dispc, struct seq_file *s) +{ + enum dss_clk_source dispc_clk_src; + int lcd; + u32 l; + + if (dispc_runtime_get(dispc)) + return; + + seq_printf(s, "- DISPC -\n"); + + dispc_clk_src = dss_get_dispc_clk_source(dispc->dss); + seq_printf(s, "dispc fclk source = %s\n", + dss_get_clk_source_name(dispc_clk_src)); + + seq_printf(s, "fck\t\t%-16lu\n", dispc_fclk_rate(dispc)); + + if (dispc_has_feature(dispc, FEAT_CORE_CLK_DIV)) { + seq_printf(s, "- DISPC-CORE-CLK -\n"); + l = dispc_read_reg(dispc, DISPC_DIVISOR); + lcd = FLD_GET(l, 23, 16); + + seq_printf(s, "lck\t\t%-16lulck div\t%u\n", + (dispc_fclk_rate(dispc)/lcd), lcd); + } + + dispc_dump_clocks_channel(dispc, s, OMAP_DSS_CHANNEL_LCD); + + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) + dispc_dump_clocks_channel(dispc, s, OMAP_DSS_CHANNEL_LCD2); + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) + dispc_dump_clocks_channel(dispc, s, OMAP_DSS_CHANNEL_LCD3); + + dispc_runtime_put(dispc); +} + +static int dispc_dump_regs(struct seq_file *s, void *p) +{ + struct dispc_device *dispc = s->private; + int i, j; + const char *mgr_names[] = { + [OMAP_DSS_CHANNEL_LCD] = "LCD", + [OMAP_DSS_CHANNEL_DIGIT] = "TV", + [OMAP_DSS_CHANNEL_LCD2] = "LCD2", + [OMAP_DSS_CHANNEL_LCD3] = "LCD3", + }; + const char *ovl_names[] = { + [OMAP_DSS_GFX] = "GFX", + [OMAP_DSS_VIDEO1] = "VID1", + [OMAP_DSS_VIDEO2] = "VID2", + [OMAP_DSS_VIDEO3] = "VID3", + [OMAP_DSS_WB] = "WB", + }; + const char **p_names; + +#define DUMPREG(dispc, r) \ + seq_printf(s, "%-50s %08x\n", #r, dispc_read_reg(dispc, r)) + + if (dispc_runtime_get(dispc)) + return 0; + + /* DISPC common registers */ + DUMPREG(dispc, DISPC_REVISION); + DUMPREG(dispc, DISPC_SYSCONFIG); + DUMPREG(dispc, DISPC_SYSSTATUS); + DUMPREG(dispc, DISPC_IRQSTATUS); + DUMPREG(dispc, DISPC_IRQENABLE); + DUMPREG(dispc, DISPC_CONTROL); + DUMPREG(dispc, DISPC_CONFIG); + DUMPREG(dispc, DISPC_CAPABLE); + DUMPREG(dispc, DISPC_LINE_STATUS); + DUMPREG(dispc, DISPC_LINE_NUMBER); + if (dispc_has_feature(dispc, FEAT_ALPHA_FIXED_ZORDER) || + dispc_has_feature(dispc, FEAT_ALPHA_FREE_ZORDER)) + DUMPREG(dispc, DISPC_GLOBAL_ALPHA); + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) { + DUMPREG(dispc, DISPC_CONTROL2); + DUMPREG(dispc, DISPC_CONFIG2); + } + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) { + DUMPREG(dispc, DISPC_CONTROL3); + DUMPREG(dispc, DISPC_CONFIG3); + } + if (dispc_has_feature(dispc, FEAT_MFLAG)) + DUMPREG(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE); + +#undef DUMPREG + +#define DISPC_REG(i, name) name(i) +#define DUMPREG(dispc, i, r) seq_printf(s, "%s(%s)%*s %08x\n", #r, p_names[i], \ + (int)(48 - strlen(#r) - strlen(p_names[i])), " ", \ + dispc_read_reg(dispc, DISPC_REG(i, r))) + + p_names = mgr_names; + + /* DISPC channel specific registers */ + for (i = 0; i < dispc_get_num_mgrs(dispc); i++) { + DUMPREG(dispc, i, DISPC_DEFAULT_COLOR); + DUMPREG(dispc, i, DISPC_TRANS_COLOR); + DUMPREG(dispc, i, DISPC_SIZE_MGR); + + if (i == OMAP_DSS_CHANNEL_DIGIT) + continue; + + DUMPREG(dispc, i, DISPC_TIMING_H); + DUMPREG(dispc, i, DISPC_TIMING_V); + DUMPREG(dispc, i, DISPC_POL_FREQ); + DUMPREG(dispc, i, DISPC_DIVISORo); + + DUMPREG(dispc, i, DISPC_DATA_CYCLE1); + DUMPREG(dispc, i, DISPC_DATA_CYCLE2); + DUMPREG(dispc, i, DISPC_DATA_CYCLE3); + + if (dispc_has_feature(dispc, FEAT_CPR)) { + DUMPREG(dispc, i, DISPC_CPR_COEF_R); + DUMPREG(dispc, i, DISPC_CPR_COEF_G); + DUMPREG(dispc, i, DISPC_CPR_COEF_B); + } + } + + p_names = ovl_names; + + for (i = 0; i < dispc_get_num_ovls(dispc); i++) { + DUMPREG(dispc, i, DISPC_OVL_BA0); + DUMPREG(dispc, i, DISPC_OVL_BA1); + DUMPREG(dispc, i, DISPC_OVL_POSITION); + DUMPREG(dispc, i, DISPC_OVL_SIZE); + DUMPREG(dispc, i, DISPC_OVL_ATTRIBUTES); + DUMPREG(dispc, i, DISPC_OVL_FIFO_THRESHOLD); + DUMPREG(dispc, i, DISPC_OVL_FIFO_SIZE_STATUS); + DUMPREG(dispc, i, DISPC_OVL_ROW_INC); + DUMPREG(dispc, i, DISPC_OVL_PIXEL_INC); + + if (dispc_has_feature(dispc, FEAT_PRELOAD)) + DUMPREG(dispc, i, DISPC_OVL_PRELOAD); + if (dispc_has_feature(dispc, FEAT_MFLAG)) + DUMPREG(dispc, i, DISPC_OVL_MFLAG_THRESHOLD); + + if (i == OMAP_DSS_GFX) { + DUMPREG(dispc, i, DISPC_OVL_WINDOW_SKIP); + DUMPREG(dispc, i, DISPC_OVL_TABLE_BA); + continue; + } + + DUMPREG(dispc, i, DISPC_OVL_FIR); + DUMPREG(dispc, i, DISPC_OVL_PICTURE_SIZE); + DUMPREG(dispc, i, DISPC_OVL_ACCU0); + DUMPREG(dispc, i, DISPC_OVL_ACCU1); + if (dispc_has_feature(dispc, FEAT_HANDLE_UV_SEPARATE)) { + DUMPREG(dispc, i, DISPC_OVL_BA0_UV); + DUMPREG(dispc, i, DISPC_OVL_BA1_UV); + DUMPREG(dispc, i, DISPC_OVL_FIR2); + DUMPREG(dispc, i, DISPC_OVL_ACCU2_0); + DUMPREG(dispc, i, DISPC_OVL_ACCU2_1); + } + if (dispc_has_feature(dispc, FEAT_ATTR2)) + DUMPREG(dispc, i, DISPC_OVL_ATTRIBUTES2); + } + + if (dispc->feat->has_writeback) { + i = OMAP_DSS_WB; + DUMPREG(dispc, i, DISPC_OVL_BA0); + DUMPREG(dispc, i, DISPC_OVL_BA1); + DUMPREG(dispc, i, DISPC_OVL_SIZE); + DUMPREG(dispc, i, DISPC_OVL_ATTRIBUTES); + DUMPREG(dispc, i, DISPC_OVL_FIFO_THRESHOLD); + DUMPREG(dispc, i, DISPC_OVL_FIFO_SIZE_STATUS); + DUMPREG(dispc, i, DISPC_OVL_ROW_INC); + DUMPREG(dispc, i, DISPC_OVL_PIXEL_INC); + + if (dispc_has_feature(dispc, FEAT_MFLAG)) + DUMPREG(dispc, i, DISPC_OVL_MFLAG_THRESHOLD); + + DUMPREG(dispc, i, DISPC_OVL_FIR); + DUMPREG(dispc, i, DISPC_OVL_PICTURE_SIZE); + DUMPREG(dispc, i, DISPC_OVL_ACCU0); + DUMPREG(dispc, i, DISPC_OVL_ACCU1); + if (dispc_has_feature(dispc, FEAT_HANDLE_UV_SEPARATE)) { + DUMPREG(dispc, i, DISPC_OVL_BA0_UV); + DUMPREG(dispc, i, DISPC_OVL_BA1_UV); + DUMPREG(dispc, i, DISPC_OVL_FIR2); + DUMPREG(dispc, i, DISPC_OVL_ACCU2_0); + DUMPREG(dispc, i, DISPC_OVL_ACCU2_1); + } + if (dispc_has_feature(dispc, FEAT_ATTR2)) + DUMPREG(dispc, i, DISPC_OVL_ATTRIBUTES2); + } + +#undef DISPC_REG +#undef DUMPREG + +#define DISPC_REG(plane, name, i) name(plane, i) +#define DUMPREG(dispc, plane, name, i) \ + seq_printf(s, "%s_%d(%s)%*s %08x\n", #name, i, p_names[plane], \ + (int)(46 - strlen(#name) - strlen(p_names[plane])), " ", \ + dispc_read_reg(dispc, DISPC_REG(plane, name, i))) + + /* Video pipeline coefficient registers */ + + /* start from OMAP_DSS_VIDEO1 */ + for (i = 1; i < dispc_get_num_ovls(dispc); i++) { + for (j = 0; j < 8; j++) + DUMPREG(dispc, i, DISPC_OVL_FIR_COEF_H, j); + + for (j = 0; j < 8; j++) + DUMPREG(dispc, i, DISPC_OVL_FIR_COEF_HV, j); + + for (j = 0; j < 5; j++) + DUMPREG(dispc, i, DISPC_OVL_CONV_COEF, j); + + if (dispc_has_feature(dispc, FEAT_FIR_COEF_V)) { + for (j = 0; j < 8; j++) + DUMPREG(dispc, i, DISPC_OVL_FIR_COEF_V, j); + } + + if (dispc_has_feature(dispc, FEAT_HANDLE_UV_SEPARATE)) { + for (j = 0; j < 8; j++) + DUMPREG(dispc, i, DISPC_OVL_FIR_COEF_H2, j); + + for (j = 0; j < 8; j++) + DUMPREG(dispc, i, DISPC_OVL_FIR_COEF_HV2, j); + + for (j = 0; j < 8; j++) + DUMPREG(dispc, i, DISPC_OVL_FIR_COEF_V2, j); + } + } + + dispc_runtime_put(dispc); + +#undef DISPC_REG +#undef DUMPREG + + return 0; +} + +/* calculate clock rates using dividers in cinfo */ +int dispc_calc_clock_rates(struct dispc_device *dispc, + unsigned long dispc_fclk_rate, + struct dispc_clock_info *cinfo) +{ + if (cinfo->lck_div > 255 || cinfo->lck_div == 0) + return -EINVAL; + if (cinfo->pck_div < 1 || cinfo->pck_div > 255) + return -EINVAL; + + cinfo->lck = dispc_fclk_rate / cinfo->lck_div; + cinfo->pck = cinfo->lck / cinfo->pck_div; + + return 0; +} + +bool dispc_div_calc(struct dispc_device *dispc, unsigned long dispc_freq, + unsigned long pck_min, unsigned long pck_max, + dispc_div_calc_func func, void *data) +{ + int lckd, lckd_start, lckd_stop; + int pckd, pckd_start, pckd_stop; + unsigned long pck, lck; + unsigned long lck_max; + unsigned long pckd_hw_min, pckd_hw_max; + unsigned int min_fck_per_pck; + unsigned long fck; + +#ifdef CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK + min_fck_per_pck = CONFIG_OMAP2_DSS_MIN_FCK_PER_PCK; +#else + min_fck_per_pck = 0; +#endif + + pckd_hw_min = dispc->feat->min_pcd; + pckd_hw_max = 255; + + lck_max = dss_get_max_fck_rate(dispc->dss); + + pck_min = pck_min ? pck_min : 1; + pck_max = pck_max ? pck_max : ULONG_MAX; + + lckd_start = max(DIV_ROUND_UP(dispc_freq, lck_max), 1ul); + lckd_stop = min(dispc_freq / pck_min, 255ul); + + for (lckd = lckd_start; lckd <= lckd_stop; ++lckd) { + lck = dispc_freq / lckd; + + pckd_start = max(DIV_ROUND_UP(lck, pck_max), pckd_hw_min); + pckd_stop = min(lck / pck_min, pckd_hw_max); + + for (pckd = pckd_start; pckd <= pckd_stop; ++pckd) { + pck = lck / pckd; + + /* + * For OMAP2/3 the DISPC fclk is the same as LCD's logic + * clock, which means we're configuring DISPC fclk here + * also. Thus we need to use the calculated lck. For + * OMAP4+ the DISPC fclk is a separate clock. + */ + if (dispc_has_feature(dispc, FEAT_CORE_CLK_DIV)) + fck = dispc_core_clk_rate(dispc); + else + fck = lck; + + if (fck < pck * min_fck_per_pck) + continue; + + if (func(lckd, pckd, lck, pck, data)) + return true; + } + } + + return false; +} + +void dispc_mgr_set_clock_div(struct dispc_device *dispc, + enum omap_channel channel, + const struct dispc_clock_info *cinfo) +{ + DSSDBG("lck = %lu (%u)\n", cinfo->lck, cinfo->lck_div); + DSSDBG("pck = %lu (%u)\n", cinfo->pck, cinfo->pck_div); + + dispc_mgr_set_lcd_divisor(dispc, channel, cinfo->lck_div, + cinfo->pck_div); +} + +u32 dispc_read_irqstatus(struct dispc_device *dispc) +{ + return dispc_read_reg(dispc, DISPC_IRQSTATUS); +} + +void dispc_clear_irqstatus(struct dispc_device *dispc, u32 mask) +{ + dispc_write_reg(dispc, DISPC_IRQSTATUS, mask); +} + +void dispc_write_irqenable(struct dispc_device *dispc, u32 mask) +{ + u32 old_mask = dispc_read_reg(dispc, DISPC_IRQENABLE); + + /* clear the irqstatus for newly enabled irqs */ + dispc_clear_irqstatus(dispc, (mask ^ old_mask) & mask); + + dispc_write_reg(dispc, DISPC_IRQENABLE, mask); + + /* flush posted write */ + dispc_read_reg(dispc, DISPC_IRQENABLE); +} + +void dispc_enable_sidle(struct dispc_device *dispc) +{ + /* SIDLEMODE: smart idle */ + REG_FLD_MOD(dispc, DISPC_SYSCONFIG, 2, 4, 3); +} + +void dispc_disable_sidle(struct dispc_device *dispc) +{ + REG_FLD_MOD(dispc, DISPC_SYSCONFIG, 1, 4, 3); /* SIDLEMODE: no idle */ +} + +u32 dispc_mgr_gamma_size(struct dispc_device *dispc, + enum omap_channel channel) +{ + const struct dispc_gamma_desc *gdesc = &mgr_desc[channel].gamma; + + if (!dispc->feat->has_gamma_table) + return 0; + + return gdesc->len; +} + +static void dispc_mgr_write_gamma_table(struct dispc_device *dispc, + enum omap_channel channel) +{ + const struct dispc_gamma_desc *gdesc = &mgr_desc[channel].gamma; + u32 *table = dispc->gamma_table[channel]; + unsigned int i; + + DSSDBG("%s: channel %d\n", __func__, channel); + + for (i = 0; i < gdesc->len; ++i) { + u32 v = table[i]; + + if (gdesc->has_index) + v |= i << 24; + else if (i == 0) + v |= 1 << 31; + + dispc_write_reg(dispc, gdesc->reg, v); + } +} + +static void dispc_restore_gamma_tables(struct dispc_device *dispc) +{ + DSSDBG("%s()\n", __func__); + + if (!dispc->feat->has_gamma_table) + return; + + dispc_mgr_write_gamma_table(dispc, OMAP_DSS_CHANNEL_LCD); + + dispc_mgr_write_gamma_table(dispc, OMAP_DSS_CHANNEL_DIGIT); + + if (dispc_has_feature(dispc, FEAT_MGR_LCD2)) + dispc_mgr_write_gamma_table(dispc, OMAP_DSS_CHANNEL_LCD2); + + if (dispc_has_feature(dispc, FEAT_MGR_LCD3)) + dispc_mgr_write_gamma_table(dispc, OMAP_DSS_CHANNEL_LCD3); +} + +static const struct drm_color_lut dispc_mgr_gamma_default_lut[] = { + { .red = 0, .green = 0, .blue = 0, }, + { .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, }, +}; + +void dispc_mgr_set_gamma(struct dispc_device *dispc, + enum omap_channel channel, + const struct drm_color_lut *lut, + unsigned int length) +{ + const struct dispc_gamma_desc *gdesc = &mgr_desc[channel].gamma; + u32 *table = dispc->gamma_table[channel]; + uint i; + + DSSDBG("%s: channel %d, lut len %u, hw len %u\n", __func__, + channel, length, gdesc->len); + + if (!dispc->feat->has_gamma_table) + return; + + if (lut == NULL || length < 2) { + lut = dispc_mgr_gamma_default_lut; + length = ARRAY_SIZE(dispc_mgr_gamma_default_lut); + } + + for (i = 0; i < length - 1; ++i) { + uint first = i * (gdesc->len - 1) / (length - 1); + uint last = (i + 1) * (gdesc->len - 1) / (length - 1); + uint w = last - first; + u16 r, g, b; + uint 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 - gdesc->bits; + g >>= 16 - gdesc->bits; + b >>= 16 - gdesc->bits; + + table[first + j] = (r << (gdesc->bits * 2)) | + (g << gdesc->bits) | b; + } + } + + if (dispc->is_enabled) + dispc_mgr_write_gamma_table(dispc, channel); +} + +static int dispc_init_gamma_tables(struct dispc_device *dispc) +{ + int channel; + + if (!dispc->feat->has_gamma_table) + return 0; + + for (channel = 0; channel < ARRAY_SIZE(dispc->gamma_table); channel++) { + const struct dispc_gamma_desc *gdesc = &mgr_desc[channel].gamma; + u32 *gt; + + if (channel == OMAP_DSS_CHANNEL_LCD2 && + !dispc_has_feature(dispc, FEAT_MGR_LCD2)) + continue; + + if (channel == OMAP_DSS_CHANNEL_LCD3 && + !dispc_has_feature(dispc, FEAT_MGR_LCD3)) + continue; + + gt = devm_kmalloc_array(&dispc->pdev->dev, gdesc->len, + sizeof(u32), GFP_KERNEL); + if (!gt) + return -ENOMEM; + + dispc->gamma_table[channel] = gt; + + dispc_mgr_set_gamma(dispc, channel, NULL, 0); + } + return 0; +} + +static void _omap_dispc_initial_config(struct dispc_device *dispc) +{ + u32 l; + + /* Exclusively enable DISPC_CORE_CLK and set divider to 1 */ + if (dispc_has_feature(dispc, FEAT_CORE_CLK_DIV)) { + l = dispc_read_reg(dispc, DISPC_DIVISOR); + /* Use DISPC_DIVISOR.LCD, instead of DISPC_DIVISOR1.LCD */ + l = FLD_MOD(l, 1, 0, 0); + l = FLD_MOD(l, 1, 23, 16); + dispc_write_reg(dispc, DISPC_DIVISOR, l); + + dispc->core_clk_rate = dispc_fclk_rate(dispc); + } + + /* Use gamma table mode, instead of palette mode */ + if (dispc->feat->has_gamma_table) + REG_FLD_MOD(dispc, DISPC_CONFIG, 1, 3, 3); + + /* For older DSS versions (FEAT_FUNCGATED) this enables + * func-clock auto-gating. For newer versions + * (dispc->feat->has_gamma_table) this enables tv-out gamma tables. + */ + if (dispc_has_feature(dispc, FEAT_FUNCGATED) || + dispc->feat->has_gamma_table) + REG_FLD_MOD(dispc, DISPC_CONFIG, 1, 9, 9); + + dispc_set_loadmode(dispc, OMAP_DSS_LOAD_FRAME_ONLY); + + dispc_init_fifos(dispc); + + dispc_configure_burst_sizes(dispc); + + dispc_ovl_enable_zorder_planes(dispc); + + if (dispc->feat->mstandby_workaround) + REG_FLD_MOD(dispc, DISPC_MSTANDBY_CTRL, 1, 0, 0); + + if (dispc_has_feature(dispc, FEAT_MFLAG)) + dispc_init_mflag(dispc); +} + +static const enum dispc_feature_id omap2_dispc_features_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, +}; + +static const enum dispc_feature_id omap3_dispc_features_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_FIFO_MERGE, + FEAT_OMAP3_DSI_FIFO_BUG, +}; + +static const enum dispc_feature_id am43xx_dispc_features_list[] = { + FEAT_LCDENABLEPOL, + FEAT_LCDENABLESIGNAL, + FEAT_PCKFREEENABLE, + FEAT_FUNCGATED, + FEAT_LINEBUFFERSPLIT, + FEAT_ROWREPEATENABLE, + FEAT_RESIZECONF, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FIXED_ZORDER, + FEAT_FIFO_MERGE, +}; + +static const enum dispc_feature_id omap4_dispc_features_list[] = { + FEAT_MGR_LCD2, + FEAT_CORE_CLK_DIV, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + FEAT_BURST_2D, +}; + +static const enum dispc_feature_id omap5_dispc_features_list[] = { + FEAT_MGR_LCD2, + FEAT_MGR_LCD3, + FEAT_CORE_CLK_DIV, + FEAT_HANDLE_UV_SEPARATE, + FEAT_ATTR2, + FEAT_CPR, + FEAT_PRELOAD, + FEAT_FIR_COEF_V, + FEAT_ALPHA_FREE_ZORDER, + FEAT_FIFO_MERGE, + FEAT_BURST_2D, + FEAT_MFLAG, +}; + +static const struct dss_reg_field omap2_dispc_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 11, 0 }, + [FEAT_REG_FIRVINC] = { 27, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 8, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 24, 16 }, + [FEAT_REG_FIFOSIZE] = { 8, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 9, 0 }, + [FEAT_REG_VERTICALACCU] = { 25, 16 }, +}; + +static const struct dss_reg_field omap3_dispc_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 12, 0 }, + [FEAT_REG_FIRVINC] = { 28, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 11, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 27, 16 }, + [FEAT_REG_FIFOSIZE] = { 10, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 9, 0 }, + [FEAT_REG_VERTICALACCU] = { 25, 16 }, +}; + +static const struct dss_reg_field omap4_dispc_reg_fields[] = { + [FEAT_REG_FIRHINC] = { 12, 0 }, + [FEAT_REG_FIRVINC] = { 28, 16 }, + [FEAT_REG_FIFOLOWTHRESHOLD] = { 15, 0 }, + [FEAT_REG_FIFOHIGHTHRESHOLD] = { 31, 16 }, + [FEAT_REG_FIFOSIZE] = { 15, 0 }, + [FEAT_REG_HORIZONTALACCU] = { 10, 0 }, + [FEAT_REG_VERTICALACCU] = { 26, 16 }, +}; + +static const enum omap_overlay_caps omap2_dispc_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap3430_dispc_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap3630_dispc_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, +}; + +static const enum omap_overlay_caps omap4_dispc_overlay_caps[] = { + /* OMAP_DSS_GFX */ + OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | + OMAP_DSS_OVL_CAP_ZORDER | OMAP_DSS_OVL_CAP_POS | + OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO1 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO2 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, + + /* OMAP_DSS_VIDEO3 */ + OMAP_DSS_OVL_CAP_SCALE | OMAP_DSS_OVL_CAP_GLOBAL_ALPHA | + OMAP_DSS_OVL_CAP_PRE_MULT_ALPHA | OMAP_DSS_OVL_CAP_ZORDER | + OMAP_DSS_OVL_CAP_POS | OMAP_DSS_OVL_CAP_REPLICATION, +}; + +#define COLOR_ARRAY(arr...) (const u32[]) { arr, 0 } + +static const u32 *omap2_dispc_supported_color_modes[] = { + + /* OMAP_DSS_GFX */ + COLOR_ARRAY( + DRM_FORMAT_RGBX4444, DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB888), + + /* OMAP_DSS_VIDEO1 */ + COLOR_ARRAY( + DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY), + + /* OMAP_DSS_VIDEO2 */ + COLOR_ARRAY( + DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY), +}; + +static const u32 *omap3_dispc_supported_color_modes[] = { + /* OMAP_DSS_GFX */ + COLOR_ARRAY( + DRM_FORMAT_RGBX4444, DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_RGBX8888), + + /* OMAP_DSS_VIDEO1 */ + COLOR_ARRAY( + DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB888, + DRM_FORMAT_RGBX4444, DRM_FORMAT_RGB565, + DRM_FORMAT_YUYV, DRM_FORMAT_UYVY), + + /* OMAP_DSS_VIDEO2 */ + COLOR_ARRAY( + DRM_FORMAT_RGBX4444, DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_RGBX8888), +}; + +static const u32 *omap4_dispc_supported_color_modes[] = { + /* OMAP_DSS_GFX */ + COLOR_ARRAY( + DRM_FORMAT_RGBX4444, DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, DRM_FORMAT_RGBX8888, + DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB4444, + DRM_FORMAT_RGBA4444, DRM_FORMAT_XRGB1555), + + /* OMAP_DSS_VIDEO1 */ + COLOR_ARRAY( + DRM_FORMAT_RGB565, DRM_FORMAT_RGBX4444, + DRM_FORMAT_YUYV, DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGBA8888, DRM_FORMAT_NV12, + DRM_FORMAT_RGBA4444, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_UYVY, + DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB4444, + DRM_FORMAT_RGBX8888), + + /* OMAP_DSS_VIDEO2 */ + COLOR_ARRAY( + DRM_FORMAT_RGB565, DRM_FORMAT_RGBX4444, + DRM_FORMAT_YUYV, DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGBA8888, DRM_FORMAT_NV12, + DRM_FORMAT_RGBA4444, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_UYVY, + DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB4444, + DRM_FORMAT_RGBX8888), + + /* OMAP_DSS_VIDEO3 */ + COLOR_ARRAY( + DRM_FORMAT_RGB565, DRM_FORMAT_RGBX4444, + DRM_FORMAT_YUYV, DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGBA8888, DRM_FORMAT_NV12, + DRM_FORMAT_RGBA4444, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_UYVY, + DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB4444, + DRM_FORMAT_RGBX8888), + + /* OMAP_DSS_WB */ + COLOR_ARRAY( + DRM_FORMAT_RGB565, DRM_FORMAT_RGBX4444, + DRM_FORMAT_YUYV, DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGBA8888, DRM_FORMAT_NV12, + DRM_FORMAT_RGBA4444, DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_UYVY, + DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB1555, + DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB4444, + DRM_FORMAT_RGBX8888), +}; + +static const u32 omap3_dispc_supported_scaler_color_modes[] = { + DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565, DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, + 0, +}; + +static const struct dispc_features omap24xx_dispc_feats = { + .sw_start = 5, + .fp_start = 15, + .bp_start = 27, + .sw_max = 64, + .vp_max = 255, + .hp_max = 256, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .ovl_width_max = 2048, + .ovl_height_max = 2048, + .max_lcd_pclk = 66500000, + .max_downscale = 2, + /* + * Assume the line width buffer to be 768 pixels as OMAP2 DISPC scaler + * cannot scale an image width larger than 768. + */ + .max_line_width = 768, + .min_pcd = 2, + .calc_scaling = dispc_ovl_calc_scaling_24xx, + .calc_core_clk = calc_core_clk_24xx, + .num_fifos = 3, + .features = omap2_dispc_features_list, + .num_features = ARRAY_SIZE(omap2_dispc_features_list), + .reg_fields = omap2_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap2_dispc_reg_fields), + .overlay_caps = omap2_dispc_overlay_caps, + .supported_color_modes = omap2_dispc_supported_color_modes, + .supported_scaler_color_modes = COLOR_ARRAY(DRM_FORMAT_XRGB8888), + .num_mgrs = 2, + .num_ovls = 3, + .buffer_size_unit = 1, + .burst_size_unit = 8, + .no_framedone_tv = true, + .set_max_preload = false, + .last_pixel_inc_missing = true, +}; + +static const struct dispc_features omap34xx_rev1_0_dispc_feats = { + .sw_start = 5, + .fp_start = 15, + .bp_start = 27, + .sw_max = 64, + .vp_max = 255, + .hp_max = 256, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .ovl_width_max = 2048, + .ovl_height_max = 2048, + .max_lcd_pclk = 173000000, + .max_tv_pclk = 59000000, + .max_downscale = 4, + .max_line_width = 1024, + .min_pcd = 1, + .calc_scaling = dispc_ovl_calc_scaling_34xx, + .calc_core_clk = calc_core_clk_34xx, + .num_fifos = 3, + .features = omap3_dispc_features_list, + .num_features = ARRAY_SIZE(omap3_dispc_features_list), + .reg_fields = omap3_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dispc_reg_fields), + .overlay_caps = omap3430_dispc_overlay_caps, + .supported_color_modes = omap3_dispc_supported_color_modes, + .supported_scaler_color_modes = omap3_dispc_supported_scaler_color_modes, + .num_mgrs = 2, + .num_ovls = 3, + .buffer_size_unit = 1, + .burst_size_unit = 8, + .no_framedone_tv = true, + .set_max_preload = false, + .last_pixel_inc_missing = true, +}; + +static const struct dispc_features omap34xx_rev3_0_dispc_feats = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .ovl_width_max = 2048, + .ovl_height_max = 2048, + .max_lcd_pclk = 173000000, + .max_tv_pclk = 59000000, + .max_downscale = 4, + .max_line_width = 1024, + .min_pcd = 1, + .calc_scaling = dispc_ovl_calc_scaling_34xx, + .calc_core_clk = calc_core_clk_34xx, + .num_fifos = 3, + .features = omap3_dispc_features_list, + .num_features = ARRAY_SIZE(omap3_dispc_features_list), + .reg_fields = omap3_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dispc_reg_fields), + .overlay_caps = omap3430_dispc_overlay_caps, + .supported_color_modes = omap3_dispc_supported_color_modes, + .supported_scaler_color_modes = omap3_dispc_supported_scaler_color_modes, + .num_mgrs = 2, + .num_ovls = 3, + .buffer_size_unit = 1, + .burst_size_unit = 8, + .no_framedone_tv = true, + .set_max_preload = false, + .last_pixel_inc_missing = true, +}; + +static const struct dispc_features omap36xx_dispc_feats = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .ovl_width_max = 2048, + .ovl_height_max = 2048, + .max_lcd_pclk = 173000000, + .max_tv_pclk = 59000000, + .max_downscale = 4, + .max_line_width = 1024, + .min_pcd = 1, + .calc_scaling = dispc_ovl_calc_scaling_34xx, + .calc_core_clk = calc_core_clk_34xx, + .num_fifos = 3, + .features = omap3_dispc_features_list, + .num_features = ARRAY_SIZE(omap3_dispc_features_list), + .reg_fields = omap3_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dispc_reg_fields), + .overlay_caps = omap3630_dispc_overlay_caps, + .supported_color_modes = omap3_dispc_supported_color_modes, + .supported_scaler_color_modes = omap3_dispc_supported_scaler_color_modes, + .num_mgrs = 2, + .num_ovls = 3, + .buffer_size_unit = 1, + .burst_size_unit = 8, + .no_framedone_tv = true, + .set_max_preload = false, + .last_pixel_inc_missing = true, +}; + +static const struct dispc_features am43xx_dispc_feats = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .ovl_width_max = 2048, + .ovl_height_max = 2048, + .max_lcd_pclk = 173000000, + .max_tv_pclk = 59000000, + .max_downscale = 4, + .max_line_width = 1024, + .min_pcd = 1, + .calc_scaling = dispc_ovl_calc_scaling_34xx, + .calc_core_clk = calc_core_clk_34xx, + .num_fifos = 3, + .features = am43xx_dispc_features_list, + .num_features = ARRAY_SIZE(am43xx_dispc_features_list), + .reg_fields = omap3_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap3_dispc_reg_fields), + .overlay_caps = omap3430_dispc_overlay_caps, + .supported_color_modes = omap3_dispc_supported_color_modes, + .supported_scaler_color_modes = omap3_dispc_supported_scaler_color_modes, + .num_mgrs = 1, + .num_ovls = 3, + .buffer_size_unit = 1, + .burst_size_unit = 8, + .no_framedone_tv = true, + .set_max_preload = false, + .last_pixel_inc_missing = true, +}; + +static const struct dispc_features omap44xx_dispc_feats = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 10, + .mgr_height_start = 26, + .mgr_width_max = 2048, + .mgr_height_max = 2048, + .ovl_width_max = 2048, + .ovl_height_max = 2048, + .max_lcd_pclk = 170000000, + .max_tv_pclk = 185625000, + .max_downscale = 4, + .max_line_width = 2048, + .min_pcd = 1, + .calc_scaling = dispc_ovl_calc_scaling_44xx, + .calc_core_clk = calc_core_clk_44xx, + .num_fifos = 5, + .features = omap4_dispc_features_list, + .num_features = ARRAY_SIZE(omap4_dispc_features_list), + .reg_fields = omap4_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap4_dispc_reg_fields), + .overlay_caps = omap4_dispc_overlay_caps, + .supported_color_modes = omap4_dispc_supported_color_modes, + .num_mgrs = 3, + .num_ovls = 4, + .buffer_size_unit = 16, + .burst_size_unit = 16, + .gfx_fifo_workaround = true, + .set_max_preload = true, + .supports_sync_align = true, + .has_writeback = true, + .supports_double_pixel = true, + .reverse_ilace_field_order = true, + .has_gamma_table = true, + .has_gamma_i734_bug = true, +}; + +static const struct dispc_features omap54xx_dispc_feats = { + .sw_start = 7, + .fp_start = 19, + .bp_start = 31, + .sw_max = 256, + .vp_max = 4095, + .hp_max = 4096, + .mgr_width_start = 11, + .mgr_height_start = 27, + .mgr_width_max = 4096, + .mgr_height_max = 4096, + .ovl_width_max = 2048, + .ovl_height_max = 4096, + .max_lcd_pclk = 170000000, + .max_tv_pclk = 192000000, + .max_downscale = 4, + .max_line_width = 2048, + .min_pcd = 1, + .calc_scaling = dispc_ovl_calc_scaling_44xx, + .calc_core_clk = calc_core_clk_44xx, + .num_fifos = 5, + .features = omap5_dispc_features_list, + .num_features = ARRAY_SIZE(omap5_dispc_features_list), + .reg_fields = omap4_dispc_reg_fields, + .num_reg_fields = ARRAY_SIZE(omap4_dispc_reg_fields), + .overlay_caps = omap4_dispc_overlay_caps, + .supported_color_modes = omap4_dispc_supported_color_modes, + .num_mgrs = 4, + .num_ovls = 4, + .buffer_size_unit = 16, + .burst_size_unit = 16, + .gfx_fifo_workaround = true, + .mstandby_workaround = true, + .set_max_preload = true, + .supports_sync_align = true, + .has_writeback = true, + .supports_double_pixel = true, + .reverse_ilace_field_order = true, + .has_gamma_table = true, + .has_gamma_i734_bug = true, +}; + +static irqreturn_t dispc_irq_handler(int irq, void *arg) +{ + struct dispc_device *dispc = arg; + + if (!dispc->is_enabled) + return IRQ_NONE; + + return dispc->user_handler(irq, dispc->user_data); +} + +int dispc_request_irq(struct dispc_device *dispc, irq_handler_t handler, + void *dev_id) +{ + int r; + + if (dispc->user_handler != NULL) + return -EBUSY; + + dispc->user_handler = handler; + dispc->user_data = dev_id; + + /* ensure the dispc_irq_handler sees the values above */ + smp_wmb(); + + r = devm_request_irq(&dispc->pdev->dev, dispc->irq, dispc_irq_handler, + IRQF_SHARED, "OMAP DISPC", dispc); + if (r) { + dispc->user_handler = NULL; + dispc->user_data = NULL; + } + + return r; +} + +void dispc_free_irq(struct dispc_device *dispc, void *dev_id) +{ + devm_free_irq(&dispc->pdev->dev, dispc->irq, dispc); + + dispc->user_handler = NULL; + dispc->user_data = NULL; +} + +u32 dispc_get_memory_bandwidth_limit(struct dispc_device *dispc) +{ + u32 limit = 0; + + /* Optional maximum memory bandwidth */ + of_property_read_u32(dispc->pdev->dev.of_node, "max-memory-bandwidth", + &limit); + + return limit; +} + +/* + * Workaround for errata i734 in DSS dispc + * - LCD1 Gamma Correction Is Not Working When GFX Pipe Is Disabled + * + * For gamma tables to work on LCD1 the GFX plane has to be used at + * least once after DSS HW has come out of reset. The workaround + * sets up a minimal LCD setup with GFX plane and waits for one + * vertical sync irq before disabling the setup and continuing with + * the context restore. The physical outputs are gated during the + * operation. This workaround requires that gamma table's LOADMODE + * is set to 0x2 in DISPC_CONTROL1 register. + * + * For details see: + * OMAP543x Multimedia Device Silicon Revision 2.0 Silicon Errata + * Literature Number: SWPZ037E + * Or some other relevant errata document for the DSS IP version. + */ + +static const struct dispc_errata_i734_data { + struct videomode vm; + struct omap_overlay_info ovli; + struct omap_overlay_manager_info mgri; + struct dss_lcd_mgr_config lcd_conf; +} i734 = { + .vm = { + .hactive = 8, .vactive = 1, + .pixelclock = 16000000, + .hsync_len = 8, .hfront_porch = 4, .hback_porch = 4, + .vsync_len = 1, .vfront_porch = 1, .vback_porch = 1, + + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_SYNC_POSEDGE | + DISPLAY_FLAGS_PIXDATA_POSEDGE, + }, + .ovli = { + .screen_width = 1, + .width = 1, .height = 1, + .fourcc = DRM_FORMAT_XRGB8888, + .rotation = DRM_MODE_ROTATE_0, + .rotation_type = OMAP_DSS_ROT_NONE, + .pos_x = 0, .pos_y = 0, + .out_width = 0, .out_height = 0, + .global_alpha = 0xff, + .pre_mult_alpha = 0, + .zorder = 0, + }, + .mgri = { + .default_color = 0, + .trans_enabled = false, + .partial_alpha_enabled = false, + .cpr_enable = false, + }, + .lcd_conf = { + .io_pad_mode = DSS_IO_PAD_MODE_BYPASS, + .stallmode = false, + .fifohandcheck = false, + .clock_info = { + .lck_div = 1, + .pck_div = 2, + }, + .video_port_width = 24, + .lcden_sig_polarity = 0, + }, +}; + +static struct i734_buf { + size_t size; + dma_addr_t paddr; + void *vaddr; +} i734_buf; + +static int dispc_errata_i734_wa_init(struct dispc_device *dispc) +{ + if (!dispc->feat->has_gamma_i734_bug) + return 0; + + i734_buf.size = i734.ovli.width * i734.ovli.height * + color_mode_to_bpp(i734.ovli.fourcc) / 8; + + i734_buf.vaddr = dma_alloc_wc(&dispc->pdev->dev, i734_buf.size, + &i734_buf.paddr, GFP_KERNEL); + if (!i734_buf.vaddr) { + dev_err(&dispc->pdev->dev, "%s: dma_alloc_wc failed\n", + __func__); + return -ENOMEM; + } + + return 0; +} + +static void dispc_errata_i734_wa_fini(struct dispc_device *dispc) +{ + if (!dispc->feat->has_gamma_i734_bug) + return; + + dma_free_wc(&dispc->pdev->dev, i734_buf.size, i734_buf.vaddr, + i734_buf.paddr); +} + +static void dispc_errata_i734_wa(struct dispc_device *dispc) +{ + u32 framedone_irq = dispc_mgr_get_framedone_irq(dispc, + OMAP_DSS_CHANNEL_LCD); + struct omap_overlay_info ovli; + struct dss_lcd_mgr_config lcd_conf; + u32 gatestate; + unsigned int count; + + if (!dispc->feat->has_gamma_i734_bug) + return; + + gatestate = REG_GET(dispc, DISPC_CONFIG, 8, 4); + + ovli = i734.ovli; + ovli.paddr = i734_buf.paddr; + lcd_conf = i734.lcd_conf; + + /* Gate all LCD1 outputs */ + REG_FLD_MOD(dispc, DISPC_CONFIG, 0x1f, 8, 4); + + /* Setup and enable GFX plane */ + dispc_ovl_setup(dispc, OMAP_DSS_GFX, &ovli, &i734.vm, false, + OMAP_DSS_CHANNEL_LCD); + dispc_ovl_enable(dispc, OMAP_DSS_GFX, true); + + /* Set up and enable display manager for LCD1 */ + dispc_mgr_setup(dispc, OMAP_DSS_CHANNEL_LCD, &i734.mgri); + dispc_calc_clock_rates(dispc, dss_get_dispc_clk_rate(dispc->dss), + &lcd_conf.clock_info); + dispc_mgr_set_lcd_config(dispc, OMAP_DSS_CHANNEL_LCD, &lcd_conf); + dispc_mgr_set_timings(dispc, OMAP_DSS_CHANNEL_LCD, &i734.vm); + + dispc_clear_irqstatus(dispc, framedone_irq); + + /* Enable and shut the channel to produce just one frame */ + dispc_mgr_enable(dispc, OMAP_DSS_CHANNEL_LCD, true); + dispc_mgr_enable(dispc, OMAP_DSS_CHANNEL_LCD, false); + + /* Busy wait for framedone. We can't fiddle with irq handlers + * in PM resume. Typically the loop runs less than 5 times and + * waits less than a micro second. + */ + count = 0; + while (!(dispc_read_irqstatus(dispc) & framedone_irq)) { + if (count++ > 10000) { + dev_err(&dispc->pdev->dev, "%s: framedone timeout\n", + __func__); + break; + } + } + dispc_ovl_enable(dispc, OMAP_DSS_GFX, false); + + /* Clear all irq bits before continuing */ + dispc_clear_irqstatus(dispc, 0xffffffff); + + /* Restore the original state to LCD1 output gates */ + REG_FLD_MOD(dispc, DISPC_CONFIG, gatestate, 8, 4); +} + +/* DISPC HW IP initialisation */ +static const struct of_device_id dispc_of_match[] = { + { .compatible = "ti,omap2-dispc", .data = &omap24xx_dispc_feats }, + { .compatible = "ti,omap3-dispc", .data = &omap36xx_dispc_feats }, + { .compatible = "ti,omap4-dispc", .data = &omap44xx_dispc_feats }, + { .compatible = "ti,omap5-dispc", .data = &omap54xx_dispc_feats }, + { .compatible = "ti,dra7-dispc", .data = &omap54xx_dispc_feats }, + {}, +}; + +static const struct soc_device_attribute dispc_soc_devices[] = { + { .machine = "OMAP3[45]*", + .revision = "ES[12].?", .data = &omap34xx_rev1_0_dispc_feats }, + { .machine = "OMAP3[45]*", .data = &omap34xx_rev3_0_dispc_feats }, + { .machine = "AM35*", .data = &omap34xx_rev3_0_dispc_feats }, + { .machine = "AM43*", .data = &am43xx_dispc_feats }, + { /* sentinel */ } +}; + +static int dispc_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct soc_device_attribute *soc; + struct dss_device *dss = dss_get_device(master); + struct dispc_device *dispc; + u32 rev; + int r = 0; + struct device_node *np = pdev->dev.of_node; + + dispc = kzalloc_obj(*dispc); + if (!dispc) + return -ENOMEM; + + dispc->pdev = pdev; + platform_set_drvdata(pdev, dispc); + dispc->dss = dss; + + /* + * The OMAP3-based models can't be told apart using the compatible + * string, use SoC device matching. + */ + soc = soc_device_match(dispc_soc_devices); + if (soc) + dispc->feat = soc->data; + else + dispc->feat = device_get_match_data(&pdev->dev); + + r = dispc_errata_i734_wa_init(dispc); + if (r) + goto err_free; + + dispc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dispc->base)) { + r = PTR_ERR(dispc->base); + goto err_free; + } + + dispc->irq = platform_get_irq(dispc->pdev, 0); + if (dispc->irq < 0) { + DSSERR("platform_get_irq failed\n"); + r = -ENODEV; + goto err_free; + } + + if (np && of_property_read_bool(np, "syscon-pol")) { + dispc->syscon_pol = syscon_regmap_lookup_by_phandle(np, "syscon-pol"); + if (IS_ERR(dispc->syscon_pol)) { + dev_err(&pdev->dev, "failed to get syscon-pol regmap\n"); + r = PTR_ERR(dispc->syscon_pol); + goto err_free; + } + + if (of_property_read_u32_index(np, "syscon-pol", 1, + &dispc->syscon_pol_offset)) { + dev_err(&pdev->dev, "failed to get syscon-pol offset\n"); + r = -EINVAL; + goto err_free; + } + } + + r = dispc_init_gamma_tables(dispc); + if (r) + goto err_free; + + pm_runtime_enable(&pdev->dev); + + r = dispc_runtime_get(dispc); + if (r) + goto err_runtime_get; + + _omap_dispc_initial_config(dispc); + + rev = dispc_read_reg(dispc, DISPC_REVISION); + dev_dbg(&pdev->dev, "OMAP DISPC rev %d.%d\n", + FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); + + dispc_runtime_put(dispc); + + dss->dispc = dispc; + + dispc->debugfs = dss_debugfs_create_file(dss, "dispc", dispc_dump_regs, + dispc); + + return 0; + +err_runtime_get: + pm_runtime_disable(&pdev->dev); +err_free: + kfree(dispc); + return r; +} + +static void dispc_unbind(struct device *dev, struct device *master, void *data) +{ + struct dispc_device *dispc = dev_get_drvdata(dev); + struct dss_device *dss = dispc->dss; + + dss_debugfs_remove_file(dispc->debugfs); + + dss->dispc = NULL; + + pm_runtime_disable(dev); + + dispc_errata_i734_wa_fini(dispc); + + kfree(dispc); +} + +static const struct component_ops dispc_component_ops = { + .bind = dispc_bind, + .unbind = dispc_unbind, +}; + +static int dispc_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &dispc_component_ops); +} + +static void dispc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dispc_component_ops); +} + +static __maybe_unused int dispc_runtime_suspend(struct device *dev) +{ + struct dispc_device *dispc = dev_get_drvdata(dev); + + dispc->is_enabled = false; + /* ensure the dispc_irq_handler sees the is_enabled value */ + smp_wmb(); + /* wait for current handler to finish before turning the DISPC off */ + synchronize_irq(dispc->irq); + + dispc_save_context(dispc); + + return 0; +} + +static __maybe_unused int dispc_runtime_resume(struct device *dev) +{ + struct dispc_device *dispc = dev_get_drvdata(dev); + + /* + * The reset value for load mode is 0 (OMAP_DSS_LOAD_CLUT_AND_FRAME) + * but we always initialize it to 2 (OMAP_DSS_LOAD_FRAME_ONLY) in + * _omap_dispc_initial_config(). We can thus use it to detect if + * we have lost register context. + */ + if (REG_GET(dispc, DISPC_CONFIG, 2, 1) != OMAP_DSS_LOAD_FRAME_ONLY) { + _omap_dispc_initial_config(dispc); + + dispc_errata_i734_wa(dispc); + + dispc_restore_context(dispc); + + dispc_restore_gamma_tables(dispc); + } + + dispc->is_enabled = true; + /* ensure the dispc_irq_handler sees the is_enabled value */ + smp_wmb(); + + return 0; +} + +static const struct dev_pm_ops dispc_pm_ops = { + SET_RUNTIME_PM_OPS(dispc_runtime_suspend, dispc_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +struct platform_driver omap_dispchw_driver = { + .probe = dispc_probe, + .remove = dispc_remove, + .driver = { + .name = "omapdss_dispc", + .pm = &dispc_pm_ops, + .of_match_table = dispc_of_match, + .suppress_bind_attrs = true, + }, +}; diff --git a/drivers/gpu/drm/omapdrm/dss/dispc.h b/drivers/gpu/drm/omapdrm/dss/dispc.h new file mode 100644 index 000000000..3f842c1ff --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dispc.h @@ -0,0 +1,909 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Archit Taneja + */ + +#ifndef __OMAP2_DISPC_REG_H +#define __OMAP2_DISPC_REG_H + +/* DISPC common registers */ +#define DISPC_REVISION 0x0000 +#define DISPC_SYSCONFIG 0x0010 +#define DISPC_SYSSTATUS 0x0014 +#define DISPC_IRQSTATUS 0x0018 +#define DISPC_IRQENABLE 0x001C +#define DISPC_CONTROL 0x0040 +#define DISPC_CONFIG 0x0044 +#define DISPC_CAPABLE 0x0048 +#define DISPC_LINE_STATUS 0x005C +#define DISPC_LINE_NUMBER 0x0060 +#define DISPC_GLOBAL_ALPHA 0x0074 +#define DISPC_CONTROL2 0x0238 +#define DISPC_CONFIG2 0x0620 +#define DISPC_DIVISOR 0x0804 +#define DISPC_GLOBAL_BUFFER 0x0800 +#define DISPC_CONTROL3 0x0848 +#define DISPC_CONFIG3 0x084C +#define DISPC_MSTANDBY_CTRL 0x0858 +#define DISPC_GLOBAL_MFLAG_ATTRIBUTE 0x085C + +#define DISPC_GAMMA_TABLE0 0x0630 +#define DISPC_GAMMA_TABLE1 0x0634 +#define DISPC_GAMMA_TABLE2 0x0638 +#define DISPC_GAMMA_TABLE3 0x0850 + +/* DISPC overlay registers */ +#define DISPC_OVL_BA0(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA0_OFFSET(n)) +#define DISPC_OVL_BA1(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA1_OFFSET(n)) +#define DISPC_OVL_BA0_UV(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA0_UV_OFFSET(n)) +#define DISPC_OVL_BA1_UV(n) (DISPC_OVL_BASE(n) + \ + DISPC_BA1_UV_OFFSET(n)) +#define DISPC_OVL_POSITION(n) (DISPC_OVL_BASE(n) + \ + DISPC_POS_OFFSET(n)) +#define DISPC_OVL_SIZE(n) (DISPC_OVL_BASE(n) + \ + DISPC_SIZE_OFFSET(n)) +#define DISPC_OVL_ATTRIBUTES(n) (DISPC_OVL_BASE(n) + \ + DISPC_ATTR_OFFSET(n)) +#define DISPC_OVL_ATTRIBUTES2(n) (DISPC_OVL_BASE(n) + \ + DISPC_ATTR2_OFFSET(n)) +#define DISPC_OVL_FIFO_THRESHOLD(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIFO_THRESH_OFFSET(n)) +#define DISPC_OVL_FIFO_SIZE_STATUS(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIFO_SIZE_STATUS_OFFSET(n)) +#define DISPC_OVL_ROW_INC(n) (DISPC_OVL_BASE(n) + \ + DISPC_ROW_INC_OFFSET(n)) +#define DISPC_OVL_PIXEL_INC(n) (DISPC_OVL_BASE(n) + \ + DISPC_PIX_INC_OFFSET(n)) +#define DISPC_OVL_WINDOW_SKIP(n) (DISPC_OVL_BASE(n) + \ + DISPC_WINDOW_SKIP_OFFSET(n)) +#define DISPC_OVL_TABLE_BA(n) (DISPC_OVL_BASE(n) + \ + DISPC_TABLE_BA_OFFSET(n)) +#define DISPC_OVL_FIR(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_OFFSET(n)) +#define DISPC_OVL_FIR2(n) (DISPC_OVL_BASE(n) + \ + DISPC_FIR2_OFFSET(n)) +#define DISPC_OVL_PICTURE_SIZE(n) (DISPC_OVL_BASE(n) + \ + DISPC_PIC_SIZE_OFFSET(n)) +#define DISPC_OVL_ACCU0(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU0_OFFSET(n)) +#define DISPC_OVL_ACCU1(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU1_OFFSET(n)) +#define DISPC_OVL_ACCU2_0(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU2_0_OFFSET(n)) +#define DISPC_OVL_ACCU2_1(n) (DISPC_OVL_BASE(n) + \ + DISPC_ACCU2_1_OFFSET(n)) +#define DISPC_OVL_FIR_COEF_H(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_H_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_HV(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_HV_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_H2(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_H2_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_HV2(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_HV2_OFFSET(n, i)) +#define DISPC_OVL_CONV_COEF(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_CONV_COEF_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_V(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_V_OFFSET(n, i)) +#define DISPC_OVL_FIR_COEF_V2(n, i) (DISPC_OVL_BASE(n) + \ + DISPC_FIR_COEF_V2_OFFSET(n, i)) +#define DISPC_OVL_PRELOAD(n) (DISPC_OVL_BASE(n) + \ + DISPC_PRELOAD_OFFSET(n)) +#define DISPC_OVL_MFLAG_THRESHOLD(n) DISPC_MFLAG_THRESHOLD_OFFSET(n) + +/* DISPC up/downsampling FIR filter coefficient structure */ +struct dispc_coef { + s8 hc4_vc22; + s8 hc3_vc2; + u8 hc2_vc1; + s8 hc1_vc0; + s8 hc0_vc00; +}; + +const struct dispc_coef *dispc_ovl_get_scale_coef(int inc, int five_taps); + +/* DISPC manager/channel specific registers */ +static inline u16 DISPC_DEFAULT_COLOR(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x004C; + case OMAP_DSS_CHANNEL_DIGIT: + return 0x0050; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03AC; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0814; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TRANS_COLOR(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0054; + case OMAP_DSS_CHANNEL_DIGIT: + return 0x0058; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03B0; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0818; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TIMING_H(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0064; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x0400; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0840; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TIMING_V(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0068; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x0404; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0844; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_POL_FREQ(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x006C; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x0408; + case OMAP_DSS_CHANNEL_LCD3: + return 0x083C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DIVISORo(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0070; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x040C; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0838; + default: + BUG(); + return 0; + } +} + +/* Named as DISPC_SIZE_LCD, DISPC_SIZE_DIGIT and DISPC_SIZE_LCD2 in TRM */ +static inline u16 DISPC_SIZE_MGR(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x007C; + case OMAP_DSS_CHANNEL_DIGIT: + return 0x0078; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03CC; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0834; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DATA_CYCLE1(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x01D4; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03C0; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0828; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DATA_CYCLE2(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x01D8; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03C4; + case OMAP_DSS_CHANNEL_LCD3: + return 0x082C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_DATA_CYCLE3(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x01DC; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03C8; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0830; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_CPR_COEF_R(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0220; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03BC; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0824; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_CPR_COEF_G(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0224; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03B8; + case OMAP_DSS_CHANNEL_LCD3: + return 0x0820; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_CPR_COEF_B(enum omap_channel channel) +{ + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return 0x0228; + case OMAP_DSS_CHANNEL_DIGIT: + BUG(); + return 0; + case OMAP_DSS_CHANNEL_LCD2: + return 0x03B4; + case OMAP_DSS_CHANNEL_LCD3: + return 0x081C; + default: + BUG(); + return 0; + } +} + +/* DISPC overlay register base addresses */ +static inline u16 DISPC_OVL_BASE(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0080; + case OMAP_DSS_VIDEO1: + return 0x00BC; + case OMAP_DSS_VIDEO2: + return 0x014C; + case OMAP_DSS_VIDEO3: + return 0x0300; + case OMAP_DSS_WB: + return 0x0500; + default: + BUG(); + return 0; + } +} + +/* DISPC overlay register offsets */ +static inline u16 DISPC_BA0_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0000; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0008; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_BA1_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0004; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x000C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_BA0_UV_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0544; + case OMAP_DSS_VIDEO2: + return 0x04BC; + case OMAP_DSS_VIDEO3: + return 0x0310; + case OMAP_DSS_WB: + return 0x0118; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_BA1_UV_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0548; + case OMAP_DSS_VIDEO2: + return 0x04C0; + case OMAP_DSS_VIDEO3: + return 0x0314; + case OMAP_DSS_WB: + return 0x011C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_POS_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0008; + case OMAP_DSS_VIDEO3: + return 0x009C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_SIZE_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x000C; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x00A8; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ATTR_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0020; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0010; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0070; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ATTR2_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0568; + case OMAP_DSS_VIDEO2: + return 0x04DC; + case OMAP_DSS_VIDEO3: + return 0x032C; + case OMAP_DSS_WB: + return 0x0310; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIFO_THRESH_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0024; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0014; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x008C; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIFO_SIZE_STATUS_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0028; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0018; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0088; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ROW_INC_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x002C; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x001C; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x00A4; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_PIX_INC_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0030; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0020; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0098; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_WINDOW_SKIP_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0034; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + BUG(); + return 0; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_TABLE_BA_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0038; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + BUG(); + return 0; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIR_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0024; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0090; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_FIR2_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0580; + case OMAP_DSS_VIDEO2: + return 0x055C; + case OMAP_DSS_VIDEO3: + return 0x0424; + case OMAP_DSS_WB: + return 0x290; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_PIC_SIZE_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0028; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0094; + default: + BUG(); + return 0; + } +} + + +static inline u16 DISPC_ACCU0_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x002C; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0000; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ACCU2_0_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0584; + case OMAP_DSS_VIDEO2: + return 0x0560; + case OMAP_DSS_VIDEO3: + return 0x0428; + case OMAP_DSS_WB: + return 0x0294; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ACCU1_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0030; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0004; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_ACCU2_1_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0588; + case OMAP_DSS_VIDEO2: + return 0x0564; + case OMAP_DSS_VIDEO3: + return 0x042C; + case OMAP_DSS_WB: + return 0x0298; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_H_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0034 + i * 0x8; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0010 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_H2_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x058C + i * 0x8; + case OMAP_DSS_VIDEO2: + return 0x0568 + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0430 + i * 0x8; + case OMAP_DSS_WB: + return 0x02A0 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_HV_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + return 0x0038 + i * 0x8; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0014 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_HV2_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0590 + i * 8; + case OMAP_DSS_VIDEO2: + return 0x056C + i * 0x8; + case OMAP_DSS_VIDEO3: + return 0x0434 + i * 0x8; + case OMAP_DSS_WB: + return 0x02A4 + i * 0x8; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4,} */ +static inline u16 DISPC_CONV_COEF_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + case OMAP_DSS_VIDEO2: + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0074 + i * 0x4; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_V_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x0124 + i * 0x4; + case OMAP_DSS_VIDEO2: + return 0x00B4 + i * 0x4; + case OMAP_DSS_VIDEO3: + case OMAP_DSS_WB: + return 0x0050 + i * 0x4; + default: + BUG(); + return 0; + } +} + +/* coef index i = {0, 1, 2, 3, 4, 5, 6, 7} */ +static inline u16 DISPC_FIR_COEF_V2_OFFSET(enum omap_plane_id plane, u16 i) +{ + switch (plane) { + case OMAP_DSS_GFX: + BUG(); + return 0; + case OMAP_DSS_VIDEO1: + return 0x05CC + i * 0x4; + case OMAP_DSS_VIDEO2: + return 0x05A8 + i * 0x4; + case OMAP_DSS_VIDEO3: + return 0x0470 + i * 0x4; + case OMAP_DSS_WB: + return 0x02E0 + i * 0x4; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_PRELOAD_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x01AC; + case OMAP_DSS_VIDEO1: + return 0x0174; + case OMAP_DSS_VIDEO2: + return 0x00E8; + case OMAP_DSS_VIDEO3: + return 0x00A0; + default: + BUG(); + return 0; + } +} + +static inline u16 DISPC_MFLAG_THRESHOLD_OFFSET(enum omap_plane_id plane) +{ + switch (plane) { + case OMAP_DSS_GFX: + return 0x0860; + case OMAP_DSS_VIDEO1: + return 0x0864; + case OMAP_DSS_VIDEO2: + return 0x0868; + case OMAP_DSS_VIDEO3: + return 0x086c; + case OMAP_DSS_WB: + return 0x0870; + default: + BUG(); + return 0; + } +} +#endif diff --git a/drivers/gpu/drm/omapdrm/dss/dispc_coefs.c b/drivers/gpu/drm/omapdrm/dss/dispc_coefs.c new file mode 100644 index 000000000..d1f3a93b8 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dispc_coefs.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Chandrabhanu Mahapatra + */ + +#include + +#include "omapdss.h" +#include "dispc.h" + +static const struct dispc_coef coef3_M8[8] = { + { 0, 0, 128, 0, 0 }, + { 0, -4, 123, 9, 0 }, + { 0, -4, 108, 24, 0 }, + { 0, -2, 87, 43, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 43, 87, -2, 0 }, + { 0, 24, 108, -4, 0 }, + { 0, 9, 123, -4, 0 }, +}; + +static const struct dispc_coef coef3_M9[8] = { + { 0, 6, 116, 6, 0 }, + { 0, 0, 112, 16, 0 }, + { 0, -2, 100, 30, 0 }, + { 0, -2, 83, 47, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 47, 83, -2, 0 }, + { 0, 30, 100, -2, 0 }, + { 0, 16, 112, 0, 0 }, +}; + +static const struct dispc_coef coef3_M10[8] = { + { 0, 10, 108, 10, 0 }, + { 0, 3, 104, 21, 0 }, + { 0, 0, 94, 34, 0 }, + { 0, -1, 80, 49, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 49, 80, -1, 0 }, + { 0, 34, 94, 0, 0 }, + { 0, 21, 104, 3, 0 }, +}; + +static const struct dispc_coef coef3_M11[8] = { + { 0, 14, 100, 14, 0 }, + { 0, 6, 98, 24, 0 }, + { 0, 2, 90, 36, 0 }, + { 0, 0, 78, 50, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 50, 78, 0, 0 }, + { 0, 36, 90, 2, 0 }, + { 0, 24, 98, 6, 0 }, +}; + +static const struct dispc_coef coef3_M12[8] = { + { 0, 16, 96, 16, 0 }, + { 0, 9, 93, 26, 0 }, + { 0, 4, 86, 38, 0 }, + { 0, 1, 76, 51, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 51, 76, 1, 0 }, + { 0, 38, 86, 4, 0 }, + { 0, 26, 93, 9, 0 }, +}; + +static const struct dispc_coef coef3_M13[8] = { + { 0, 18, 92, 18, 0 }, + { 0, 10, 90, 28, 0 }, + { 0, 5, 83, 40, 0 }, + { 0, 1, 75, 52, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 52, 75, 1, 0 }, + { 0, 40, 83, 5, 0 }, + { 0, 28, 90, 10, 0 }, +}; + +static const struct dispc_coef coef3_M14[8] = { + { 0, 20, 88, 20, 0 }, + { 0, 12, 86, 30, 0 }, + { 0, 6, 81, 41, 0 }, + { 0, 2, 74, 52, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 52, 74, 2, 0 }, + { 0, 41, 81, 6, 0 }, + { 0, 30, 86, 12, 0 }, +}; + +static const struct dispc_coef coef3_M16[8] = { + { 0, 22, 84, 22, 0 }, + { 0, 14, 82, 32, 0 }, + { 0, 8, 78, 42, 0 }, + { 0, 3, 72, 53, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 53, 72, 3, 0 }, + { 0, 42, 78, 8, 0 }, + { 0, 32, 82, 14, 0 }, +}; + +static const struct dispc_coef coef3_M19[8] = { + { 0, 24, 80, 24, 0 }, + { 0, 16, 79, 33, 0 }, + { 0, 9, 76, 43, 0 }, + { 0, 4, 70, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 70, 4, 0 }, + { 0, 43, 76, 9, 0 }, + { 0, 33, 79, 16, 0 }, +}; + +static const struct dispc_coef coef3_M22[8] = { + { 0, 25, 78, 25, 0 }, + { 0, 17, 77, 34, 0 }, + { 0, 10, 74, 44, 0 }, + { 0, 5, 69, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 69, 5, 0 }, + { 0, 44, 74, 10, 0 }, + { 0, 34, 77, 17, 0 }, +}; + +static const struct dispc_coef coef3_M26[8] = { + { 0, 26, 76, 26, 0 }, + { 0, 19, 74, 35, 0 }, + { 0, 11, 72, 45, 0 }, + { 0, 5, 69, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 69, 5, 0 }, + { 0, 45, 72, 11, 0 }, + { 0, 35, 74, 19, 0 }, +}; + +static const struct dispc_coef coef3_M32[8] = { + { 0, 27, 74, 27, 0 }, + { 0, 19, 73, 36, 0 }, + { 0, 12, 71, 45, 0 }, + { 0, 6, 68, 54, 0 }, + { 0, 64, 64, 0, 0 }, + { 0, 54, 68, 6, 0 }, + { 0, 45, 71, 12, 0 }, + { 0, 36, 73, 19, 0 }, +}; + +static const struct dispc_coef coef5_M8[8] = { + { 0, 0, 128, 0, 0 }, + { -2, 14, 125, -10, 1 }, + { -6, 33, 114, -15, 2 }, + { -10, 55, 98, -16, 1 }, + { 0, -14, 78, 78, -14 }, + { 1, -16, 98, 55, -10 }, + { 2, -15, 114, 33, -6 }, + { 1, -10, 125, 14, -2 }, +}; + +static const struct dispc_coef coef5_M9[8] = { + { -3, 10, 114, 10, -3 }, + { -6, 24, 111, 0, -1 }, + { -8, 40, 103, -7, 0 }, + { -11, 58, 91, -11, 1 }, + { 0, -12, 76, 76, -12 }, + { 1, -11, 91, 58, -11 }, + { 0, -7, 103, 40, -8 }, + { -1, 0, 111, 24, -6 }, +}; + +static const struct dispc_coef coef5_M10[8] = { + { -4, 18, 100, 18, -4 }, + { -6, 30, 99, 8, -3 }, + { -8, 44, 93, 0, -1 }, + { -9, 58, 84, -5, 0 }, + { 0, -8, 72, 72, -8 }, + { 0, -5, 84, 58, -9 }, + { -1, 0, 93, 44, -8 }, + { -3, 8, 99, 30, -6 }, +}; + +static const struct dispc_coef coef5_M11[8] = { + { -5, 23, 92, 23, -5 }, + { -6, 34, 90, 13, -3 }, + { -6, 45, 85, 6, -2 }, + { -6, 57, 78, 0, -1 }, + { 0, -4, 68, 68, -4 }, + { -1, 0, 78, 57, -6 }, + { -2, 6, 85, 45, -6 }, + { -3, 13, 90, 34, -6 }, +}; + +static const struct dispc_coef coef5_M12[8] = { + { -4, 26, 84, 26, -4 }, + { -5, 36, 82, 18, -3 }, + { -4, 46, 78, 10, -2 }, + { -3, 55, 72, 5, -1 }, + { 0, 0, 64, 64, 0 }, + { -1, 5, 72, 55, -3 }, + { -2, 10, 78, 46, -4 }, + { -3, 18, 82, 36, -5 }, +}; + +static const struct dispc_coef coef5_M13[8] = { + { -3, 28, 78, 28, -3 }, + { -3, 37, 76, 21, -3 }, + { -2, 45, 73, 14, -2 }, + { 0, 53, 68, 8, -1 }, + { 0, 3, 61, 61, 3 }, + { -1, 8, 68, 53, 0 }, + { -2, 14, 73, 45, -2 }, + { -3, 21, 76, 37, -3 }, +}; + +static const struct dispc_coef coef5_M14[8] = { + { -2, 30, 72, 30, -2 }, + { -1, 37, 71, 23, -2 }, + { 0, 45, 69, 16, -2 }, + { 3, 52, 64, 10, -1 }, + { 0, 6, 58, 58, 6 }, + { -1, 10, 64, 52, 3 }, + { -2, 16, 69, 45, 0 }, + { -2, 23, 71, 37, -1 }, +}; + +static const struct dispc_coef coef5_M16[8] = { + { 0, 31, 66, 31, 0 }, + { 1, 38, 65, 25, -1 }, + { 3, 44, 62, 20, -1 }, + { 6, 49, 59, 14, 0 }, + { 0, 10, 54, 54, 10 }, + { 0, 14, 59, 49, 6 }, + { -1, 20, 62, 44, 3 }, + { -1, 25, 65, 38, 1 }, +}; + +static const struct dispc_coef coef5_M19[8] = { + { 3, 32, 58, 32, 3 }, + { 4, 38, 58, 27, 1 }, + { 7, 42, 55, 23, 1 }, + { 10, 46, 54, 18, 0 }, + { 0, 14, 50, 50, 14 }, + { 0, 18, 54, 46, 10 }, + { 1, 23, 55, 42, 7 }, + { 1, 27, 58, 38, 4 }, +}; + +static const struct dispc_coef coef5_M22[8] = { + { 4, 33, 54, 33, 4 }, + { 6, 37, 54, 28, 3 }, + { 9, 41, 53, 24, 1 }, + { 12, 45, 51, 20, 0 }, + { 0, 16, 48, 48, 16 }, + { 0, 20, 51, 45, 12 }, + { 1, 24, 53, 41, 9 }, + { 3, 28, 54, 37, 6 }, +}; + +static const struct dispc_coef coef5_M26[8] = { + { 6, 33, 50, 33, 6 }, + { 8, 36, 51, 29, 4 }, + { 11, 40, 50, 25, 2 }, + { 14, 43, 48, 22, 1 }, + { 0, 18, 46, 46, 18 }, + { 1, 22, 48, 43, 14 }, + { 2, 25, 50, 40, 11 }, + { 4, 29, 51, 36, 8 }, +}; + +static const struct dispc_coef coef5_M32[8] = { + { 7, 33, 48, 33, 7 }, + { 10, 36, 48, 29, 5 }, + { 13, 39, 47, 26, 3 }, + { 16, 42, 46, 23, 1 }, + { 0, 19, 45, 45, 19 }, + { 1, 23, 46, 42, 16 }, + { 3, 26, 47, 39, 13 }, + { 5, 29, 48, 36, 10 }, +}; + +const struct dispc_coef *dispc_ovl_get_scale_coef(int inc, int five_taps) +{ + int i; + static const struct { + int Mmin; + int Mmax; + const struct dispc_coef *coef_3; + const struct dispc_coef *coef_5; + } coefs[] = { + { 27, 32, coef3_M32, coef5_M32 }, + { 23, 26, coef3_M26, coef5_M26 }, + { 20, 22, coef3_M22, coef5_M22 }, + { 17, 19, coef3_M19, coef5_M19 }, + { 15, 16, coef3_M16, coef5_M16 }, + { 14, 14, coef3_M14, coef5_M14 }, + { 13, 13, coef3_M13, coef5_M13 }, + { 12, 12, coef3_M12, coef5_M12 }, + { 11, 11, coef3_M11, coef5_M11 }, + { 10, 10, coef3_M10, coef5_M10 }, + { 9, 9, coef3_M9, coef5_M9 }, + { 4, 8, coef3_M8, coef5_M8 }, + /* + * When upscaling more than two times, blockiness and outlines + * around the image are observed when M8 tables are used. M11, + * M16 and M19 tables are used to prevent this. + */ + { 3, 3, coef3_M11, coef5_M11 }, + { 2, 2, coef3_M16, coef5_M16 }, + { 0, 1, coef3_M19, coef5_M19 }, + }; + + inc /= 128; + for (i = 0; i < ARRAY_SIZE(coefs); ++i) + if (inc >= coefs[i].Mmin && inc <= coefs[i].Mmax) + return five_taps ? coefs[i].coef_5 : coefs[i].coef_3; + return NULL; +} diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c new file mode 100644 index 000000000..419915e33 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dpi.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + */ + +#define DSS_SUBSYS_NAME "DPI" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dss.h" +#include "omapdss.h" + +struct dpi_data { + struct platform_device *pdev; + enum dss_model dss_model; + struct dss_device *dss; + unsigned int id; + + struct regulator *vdds_dsi_reg; + enum dss_clk_source clk_src; + struct dss_pll *pll; + + struct dss_lcd_mgr_config mgr_config; + unsigned long pixelclock; + int data_lines; + + struct omap_dss_device output; + struct drm_bridge bridge; +}; + +#define drm_bridge_to_dpi(bridge) container_of(bridge, struct dpi_data, bridge) + +/* ----------------------------------------------------------------------------- + * Clock Handling and PLL + */ + +static enum dss_clk_source dpi_get_clk_src_dra7xx(struct dpi_data *dpi, + enum omap_channel channel) +{ + /* + * Possible clock sources: + * LCD1: FCK/PLL1_1/HDMI_PLL + * LCD2: FCK/PLL1_3/HDMI_PLL (DRA74x: PLL2_3) + * LCD3: FCK/PLL1_3/HDMI_PLL (DRA74x: PLL2_1) + */ + + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + { + if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL1_1)) + return DSS_CLK_SRC_PLL1_1; + break; + } + case OMAP_DSS_CHANNEL_LCD2: + { + if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL1_3)) + return DSS_CLK_SRC_PLL1_3; + if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL2_3)) + return DSS_CLK_SRC_PLL2_3; + break; + } + case OMAP_DSS_CHANNEL_LCD3: + { + if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL2_1)) + return DSS_CLK_SRC_PLL2_1; + if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL1_3)) + return DSS_CLK_SRC_PLL1_3; + break; + } + default: + break; + } + + return DSS_CLK_SRC_FCK; +} + +static enum dss_clk_source dpi_get_clk_src(struct dpi_data *dpi) +{ + enum omap_channel channel = dpi->output.dispc_channel; + + /* + * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL + * would also be used for DISPC fclk. Meaning, when the DPI output is + * disabled, DISPC clock will be disabled, and TV out will stop. + */ + switch (dpi->dss_model) { + case DSS_MODEL_OMAP2: + case DSS_MODEL_OMAP3: + return DSS_CLK_SRC_FCK; + + case DSS_MODEL_OMAP4: + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return DSS_CLK_SRC_PLL1_1; + case OMAP_DSS_CHANNEL_LCD2: + return DSS_CLK_SRC_PLL2_1; + default: + return DSS_CLK_SRC_FCK; + } + + case DSS_MODEL_OMAP5: + switch (channel) { + case OMAP_DSS_CHANNEL_LCD: + return DSS_CLK_SRC_PLL1_1; + case OMAP_DSS_CHANNEL_LCD3: + return DSS_CLK_SRC_PLL2_1; + case OMAP_DSS_CHANNEL_LCD2: + default: + return DSS_CLK_SRC_FCK; + } + + case DSS_MODEL_DRA7: + return dpi_get_clk_src_dra7xx(dpi, channel); + + default: + return DSS_CLK_SRC_FCK; + } +} + +struct dpi_clk_calc_ctx { + struct dpi_data *dpi; + unsigned int clkout_idx; + + /* inputs */ + + unsigned long pck_min, pck_max; + + /* outputs */ + + struct dss_pll_clock_info pll_cinfo; + unsigned long fck; + struct dispc_clock_info dispc_cinfo; +}; + +static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, + unsigned long pck, void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + /* + * Odd dividers give us uneven duty cycle, causing problem when level + * shifted. So skip all odd dividers when the pixel clock is on the + * higher side. + */ + if (ctx->pck_min >= 100000000) { + if (lckd > 1 && lckd % 2 != 0) + return false; + + if (pckd > 1 && pckd % 2 != 0) + return false; + } + + ctx->dispc_cinfo.lck_div = lckd; + ctx->dispc_cinfo.pck_div = pckd; + ctx->dispc_cinfo.lck = lck; + ctx->dispc_cinfo.pck = pck; + + return true; +} + + +static bool dpi_calc_hsdiv_cb(int m_dispc, unsigned long dispc, + void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + ctx->pll_cinfo.mX[ctx->clkout_idx] = m_dispc; + ctx->pll_cinfo.clkout[ctx->clkout_idx] = dispc; + + return dispc_div_calc(ctx->dpi->dss->dispc, dispc, + ctx->pck_min, ctx->pck_max, + dpi_calc_dispc_cb, ctx); +} + + +static bool dpi_calc_pll_cb(int n, int m, unsigned long fint, + unsigned long clkdco, + void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + ctx->pll_cinfo.n = n; + ctx->pll_cinfo.m = m; + ctx->pll_cinfo.fint = fint; + ctx->pll_cinfo.clkdco = clkdco; + + return dss_pll_hsdiv_calc_a(ctx->dpi->pll, clkdco, + ctx->pck_min, dss_get_max_fck_rate(ctx->dpi->dss), + dpi_calc_hsdiv_cb, ctx); +} + +static bool dpi_calc_dss_cb(unsigned long fck, void *data) +{ + struct dpi_clk_calc_ctx *ctx = data; + + ctx->fck = fck; + + return dispc_div_calc(ctx->dpi->dss->dispc, fck, + ctx->pck_min, ctx->pck_max, + dpi_calc_dispc_cb, ctx); +} + +static bool dpi_pll_clk_calc(struct dpi_data *dpi, unsigned long pck, + struct dpi_clk_calc_ctx *ctx) +{ + unsigned long clkin; + + memset(ctx, 0, sizeof(*ctx)); + ctx->dpi = dpi; + ctx->clkout_idx = dss_pll_get_clkout_idx_for_src(dpi->clk_src); + + clkin = clk_get_rate(dpi->pll->clkin); + + if (dpi->pll->hw->type == DSS_PLL_TYPE_A) { + unsigned long pll_min, pll_max; + + ctx->pck_min = pck - 1000; + ctx->pck_max = pck + 1000; + + pll_min = 0; + pll_max = 0; + + return dss_pll_calc_a(ctx->dpi->pll, clkin, + pll_min, pll_max, + dpi_calc_pll_cb, ctx); + } else { /* DSS_PLL_TYPE_B */ + dss_pll_calc_b(dpi->pll, clkin, pck, &ctx->pll_cinfo); + + ctx->dispc_cinfo.lck_div = 1; + ctx->dispc_cinfo.pck_div = 1; + ctx->dispc_cinfo.lck = ctx->pll_cinfo.clkout[0]; + ctx->dispc_cinfo.pck = ctx->dispc_cinfo.lck; + + return true; + } +} + +static bool dpi_dss_clk_calc(struct dpi_data *dpi, unsigned long pck, + struct dpi_clk_calc_ctx *ctx) +{ + int i; + + /* + * DSS fck gives us very few possibilities, so finding a good pixel + * clock may not be possible. We try multiple times to find the clock, + * each time widening the pixel clock range we look for, up to + * +/- ~15MHz. + */ + + for (i = 0; i < 25; ++i) { + bool ok; + + memset(ctx, 0, sizeof(*ctx)); + ctx->dpi = dpi; + if (pck > 1000 * i * i * i) + ctx->pck_min = max(pck - 1000 * i * i * i, 0lu); + else + ctx->pck_min = 0; + ctx->pck_max = pck + 1000 * i * i * i; + + ok = dss_div_calc(dpi->dss, pck, ctx->pck_min, + dpi_calc_dss_cb, ctx); + if (ok) + return ok; + } + + return false; +} + + + +static int dpi_set_pll_clk(struct dpi_data *dpi, unsigned long pck_req) +{ + struct dpi_clk_calc_ctx ctx; + int r; + bool ok; + + ok = dpi_pll_clk_calc(dpi, pck_req, &ctx); + if (!ok) + return -EINVAL; + + r = dss_pll_set_config(dpi->pll, &ctx.pll_cinfo); + if (r) + return r; + + dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel, + dpi->clk_src); + + dpi->mgr_config.clock_info = ctx.dispc_cinfo; + + return 0; +} + +static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req) +{ + struct dpi_clk_calc_ctx ctx; + int r; + bool ok; + + ok = dpi_dss_clk_calc(dpi, pck_req, &ctx); + if (!ok) + return -EINVAL; + + r = dss_set_fck_rate(dpi->dss, ctx.fck); + if (r) + return r; + + dpi->mgr_config.clock_info = ctx.dispc_cinfo; + + return 0; +} + +static int dpi_set_mode(struct dpi_data *dpi) +{ + int r; + + if (dpi->pll) + r = dpi_set_pll_clk(dpi, dpi->pixelclock); + else + r = dpi_set_dispc_clk(dpi, dpi->pixelclock); + + return r; +} + +static void dpi_config_lcd_manager(struct dpi_data *dpi) +{ + dpi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; + + dpi->mgr_config.stallmode = false; + dpi->mgr_config.fifohandcheck = false; + + dpi->mgr_config.video_port_width = dpi->data_lines; + + dpi->mgr_config.lcden_sig_polarity = 0; + + dss_mgr_set_lcd_config(&dpi->output, &dpi->mgr_config); +} + +static int dpi_clock_update(struct dpi_data *dpi, unsigned long *clock) +{ + int lck_div, pck_div; + unsigned long fck; + struct dpi_clk_calc_ctx ctx; + + if (dpi->pll) { + if (!dpi_pll_clk_calc(dpi, *clock, &ctx)) + return -EINVAL; + + fck = ctx.pll_cinfo.clkout[ctx.clkout_idx]; + } else { + if (!dpi_dss_clk_calc(dpi, *clock, &ctx)) + return -EINVAL; + + fck = ctx.fck; + } + + lck_div = ctx.dispc_cinfo.lck_div; + pck_div = ctx.dispc_cinfo.pck_div; + + *clock = fck / lck_div / pck_div; + + return 0; +} + +static int dpi_verify_pll(struct dss_pll *pll) +{ + int r; + + /* do initial setup with the PLL to see if it is operational */ + + r = dss_pll_enable(pll); + if (r) + return r; + + dss_pll_disable(pll); + + return 0; +} + +static void dpi_init_pll(struct dpi_data *dpi) +{ + struct dss_pll *pll; + + if (dpi->pll) + return; + + dpi->clk_src = dpi_get_clk_src(dpi); + + pll = dss_pll_find_by_src(dpi->dss, dpi->clk_src); + if (!pll) + return; + + if (dpi_verify_pll(pll)) { + DSSWARN("PLL not operational\n"); + return; + } + + dpi->pll = pll; +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +static int dpi_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct dpi_data *dpi = drm_bridge_to_dpi(bridge); + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + dpi_init_pll(dpi); + + return drm_bridge_attach(encoder, dpi->output.next_bridge, + bridge, flags); +} + +static enum drm_mode_status +dpi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct dpi_data *dpi = drm_bridge_to_dpi(bridge); + unsigned long clock = mode->clock * 1000; + int ret; + + if (mode->hdisplay % 8 != 0) + return MODE_BAD_WIDTH; + + if (mode->clock == 0) + return MODE_NOCLOCK; + + ret = dpi_clock_update(dpi, &clock); + if (ret < 0) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static bool dpi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dpi_data *dpi = drm_bridge_to_dpi(bridge); + unsigned long clock = mode->clock * 1000; + int ret; + + ret = dpi_clock_update(dpi, &clock); + if (ret < 0) + return false; + + adjusted_mode->clock = clock / 1000; + + return true; +} + +static void dpi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct dpi_data *dpi = drm_bridge_to_dpi(bridge); + + dpi->pixelclock = adjusted_mode->clock * 1000; +} + +static void dpi_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct dpi_data *dpi = drm_bridge_to_dpi(bridge); + int r; + + if (dpi->vdds_dsi_reg) { + r = regulator_enable(dpi->vdds_dsi_reg); + if (r) + return; + } + + r = dispc_runtime_get(dpi->dss->dispc); + if (r) + goto err_get_dispc; + + r = dss_dpi_select_source(dpi->dss, dpi->id, dpi->output.dispc_channel); + if (r) + goto err_src_sel; + + if (dpi->pll) { + r = dss_pll_enable(dpi->pll); + if (r) + goto err_pll_init; + } + + r = dpi_set_mode(dpi); + if (r) + goto err_set_mode; + + dpi_config_lcd_manager(dpi); + + mdelay(2); + + r = dss_mgr_enable(&dpi->output); + if (r) + goto err_mgr_enable; + + return; + +err_mgr_enable: +err_set_mode: + if (dpi->pll) + dss_pll_disable(dpi->pll); +err_pll_init: +err_src_sel: + dispc_runtime_put(dpi->dss->dispc); +err_get_dispc: + if (dpi->vdds_dsi_reg) + regulator_disable(dpi->vdds_dsi_reg); +} + +static void dpi_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct dpi_data *dpi = drm_bridge_to_dpi(bridge); + + dss_mgr_disable(&dpi->output); + + if (dpi->pll) { + dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel, + DSS_CLK_SRC_FCK); + dss_pll_disable(dpi->pll); + } + + dispc_runtime_put(dpi->dss->dispc); + + if (dpi->vdds_dsi_reg) + regulator_disable(dpi->vdds_dsi_reg); +} + +static const struct drm_bridge_funcs dpi_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .attach = dpi_bridge_attach, + .mode_valid = dpi_bridge_mode_valid, + .mode_fixup = dpi_bridge_mode_fixup, + .mode_set = dpi_bridge_mode_set, + .atomic_enable = dpi_bridge_enable, + .atomic_disable = dpi_bridge_disable, +}; + +static void dpi_bridge_init(struct dpi_data *dpi) +{ + dpi->bridge.of_node = dpi->pdev->dev.of_node; + dpi->bridge.type = DRM_MODE_CONNECTOR_DPI; + + drm_bridge_add(&dpi->bridge); +} + +static void dpi_bridge_cleanup(struct dpi_data *dpi) +{ + drm_bridge_remove(&dpi->bridge); +} + +/* ----------------------------------------------------------------------------- + * Initialisation and Cleanup + */ + +/* + * Return a hardcoded channel for the DPI output. This should work for + * current use cases, but this can be later expanded to either resolve + * the channel in some more dynamic manner, or get the channel as a user + * parameter. + */ +static enum omap_channel dpi_get_channel(struct dpi_data *dpi) +{ + switch (dpi->dss_model) { + case DSS_MODEL_OMAP2: + case DSS_MODEL_OMAP3: + return OMAP_DSS_CHANNEL_LCD; + + case DSS_MODEL_DRA7: + switch (dpi->id) { + case 2: + return OMAP_DSS_CHANNEL_LCD3; + case 1: + return OMAP_DSS_CHANNEL_LCD2; + case 0: + default: + return OMAP_DSS_CHANNEL_LCD; + } + + case DSS_MODEL_OMAP4: + return OMAP_DSS_CHANNEL_LCD2; + + case DSS_MODEL_OMAP5: + return OMAP_DSS_CHANNEL_LCD3; + + default: + DSSWARN("unsupported DSS version\n"); + return OMAP_DSS_CHANNEL_LCD; + } +} + +static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port) +{ + struct omap_dss_device *out = &dpi->output; + u32 port_num = 0; + int r; + + dpi_bridge_init(dpi); + + of_property_read_u32(port, "reg", &port_num); + dpi->id = port_num <= 2 ? port_num : 0; + + switch (port_num) { + case 2: + out->name = "dpi.2"; + break; + case 1: + out->name = "dpi.1"; + break; + case 0: + default: + out->name = "dpi.0"; + break; + } + + out->dev = &dpi->pdev->dev; + out->id = OMAP_DSS_OUTPUT_DPI; + out->type = OMAP_DISPLAY_TYPE_DPI; + out->dispc_channel = dpi_get_channel(dpi); + out->of_port = port_num; + + r = omapdss_device_init_output(out, &dpi->bridge); + if (r < 0) { + dpi_bridge_cleanup(dpi); + return r; + } + + omapdss_device_register(out); + + return 0; +} + +static void dpi_uninit_output_port(struct device_node *port) +{ + struct dpi_data *dpi = port->data; + struct omap_dss_device *out = &dpi->output; + + omapdss_device_unregister(out); + omapdss_device_cleanup_output(out); + + dpi_bridge_cleanup(dpi); +} + +/* ----------------------------------------------------------------------------- + * Initialisation and Cleanup + */ + +static const struct soc_device_attribute dpi_soc_devices[] = { + { .machine = "OMAP3[456]*" }, + { .machine = "[AD]M37*" }, + { /* sentinel */ } +}; + +static int dpi_init_regulator(struct dpi_data *dpi) +{ + struct regulator *vdds_dsi; + + /* + * The DPI uses the DSI VDDS on OMAP34xx, OMAP35xx, OMAP36xx, AM37xx and + * DM37xx only. + */ + if (!soc_device_match(dpi_soc_devices)) + return 0; + + vdds_dsi = devm_regulator_get(&dpi->pdev->dev, "vdds_dsi"); + if (IS_ERR(vdds_dsi)) { + if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) + DSSERR("can't get VDDS_DSI regulator\n"); + return PTR_ERR(vdds_dsi); + } + + dpi->vdds_dsi_reg = vdds_dsi; + + return 0; +} + +int dpi_init_port(struct dss_device *dss, struct platform_device *pdev, + struct device_node *port, enum dss_model dss_model) +{ + struct dpi_data *dpi; + struct device_node *ep; + u32 datalines; + int r; + + dpi = devm_drm_bridge_alloc(&pdev->dev, struct dpi_data, bridge, &dpi_bridge_funcs); + if (IS_ERR(dpi)) + return PTR_ERR(dpi); + + ep = of_graph_get_next_port_endpoint(port, NULL); + if (!ep) + return 0; + + r = of_property_read_u32(ep, "data-lines", &datalines); + of_node_put(ep); + if (r) { + DSSERR("failed to parse datalines\n"); + return r; + } + + dpi->data_lines = datalines; + + dpi->pdev = pdev; + dpi->dss_model = dss_model; + dpi->dss = dss; + port->data = dpi; + + r = dpi_init_regulator(dpi); + if (r) + return r; + + return dpi_init_output_port(dpi, port); +} + +void dpi_uninit_port(struct device_node *port) +{ + struct dpi_data *dpi = port->data; + + if (!dpi) + return; + + dpi_uninit_output_port(port); +} diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.c b/drivers/gpu/drm/omapdrm/dss/dsi.c new file mode 100644 index 000000000..6da720e87 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/dss/dsi.c @@ -0,0 +1,5104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2009 Nokia Corporation + * Author: Tomi Valkeinen + */ + +#define DSS_SUBSYS_NAME "DSI" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include