aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Airlie <[email protected]>2022-05-06 17:19:11 +1000
committerDave Airlie <[email protected]>2022-05-06 17:20:13 +1000
commitc67f84e97bafe73c47d5773105b114118ffb84df (patch)
treed8e01ee364dda98fe99426ab62b4dd9fbd89c980
parentaf3847a7472d2def8358b7ae94b14f1d20fd8661 (diff)
parent6071c4c2a319da360b0bf2bc397d4fefad10b2c8 (diff)
Merge tag 'drm-misc-next-2022-05-05' of git://anongit.freedesktop.org/drm/drm-misc into drm-next
drm-misc-next for 5.19: UAPI Changes: Cross-subsystem Changes: Core Changes: - Add DRM-managed mutex initialisation - edid: Doc improvements - fbdev: deferred io improvements - format-helper: consolidate format conversion helpers - gem: Rework fence handling in drm_gem_plane_helper_prepare_fb Driver Changes: - ast: DisplayPort support, locking improvements - exynos: Revert conversion to devm_drm_of_get_bridge for DSI - mgag200: locking improvements - mxsfb: LCDIF CRC support - nouveau: switch to drm_gem_plane_helper_prepare_fb - rockchip: Refactor IOMMU initialisation, make some structures static, replace drm_detect_hdmi_monitor with drm_display_info.is_hdmi, support swapped YUV formats, clock improvements, rk3568 support, VOP2 support - bridge: - adv7511: Enable CEC for ADV7535 - it6505: Send DPCD SET_POWER to monitor at disable - mcde_dsi: Revert conversion to devm_drm_of_get_bridge - tc358767: Fix for eDP and DP DT endpoint parsing - new bridge: i.MX8MP LDB - panel: - new panel: Startek KD070WVFPA043-C069A Signed-off-by: Dave Airlie <[email protected]> From: Maxime Ripard <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/20220505131127.lcqvsywo7qt3eywk@houat
-rw-r--r--Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml92
-rw-r--r--Documentation/devicetree/bindings/display/panel/panel-simple.yaml2
-rw-r--r--Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml46
-rw-r--r--Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml140
-rw-r--r--Documentation/gpu/todo.rst14
-rw-r--r--drivers/dma-buf/sync_file.c21
-rw-r--r--drivers/gpu/drm/arm/malidp_planes.c2
-rw-r--r--drivers/gpu/drm/ast/Makefile2
-rw-r--r--drivers/gpu/drm/ast/ast_dp.c282
-rw-r--r--drivers/gpu/drm/ast/ast_drv.c7
-rw-r--r--drivers/gpu/drm/ast/ast_drv.h116
-rw-r--r--drivers/gpu/drm/ast/ast_main.c9
-rw-r--r--drivers/gpu/drm/ast/ast_mode.c172
-rw-r--r--drivers/gpu/drm/ast/ast_post.c4
-rw-r--r--drivers/gpu/drm/bridge/Kconfig8
-rw-r--r--drivers/gpu/drm/bridge/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511.h27
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_cec.c116
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_drv.c19
-rw-r--r--drivers/gpu/drm/bridge/fsl-ldb.c342
-rw-r--r--drivers/gpu/drm/bridge/ite-it6505.c29
-rw-r--r--drivers/gpu/drm/bridge/tc358767.c8
-rw-r--r--drivers/gpu/drm/bridge/ti-tfp410.c12
-rw-r--r--drivers/gpu/drm/display/Kconfig6
-rw-r--r--drivers/gpu/drm/display/drm_dp_mst_topology.c5
-rw-r--r--drivers/gpu/drm/drm_atomic_uapi.c47
-rw-r--r--drivers/gpu/drm/drm_color_mgmt.c2
-rw-r--r--drivers/gpu/drm/drm_edid.c65
-rw-r--r--drivers/gpu/drm/drm_fb_helper.c15
-rw-r--r--drivers/gpu/drm/drm_format_helper.c478
-rw-r--r--drivers/gpu/drm/drm_gem_atomic_helper.c73
-rw-r--r--drivers/gpu/drm/drm_managed.c27
-rw-r--r--drivers/gpu/drm/drm_plane.c14
-rw-r--r--drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c10
-rw-r--r--drivers/gpu/drm/exynos/exynos_drm_dsi.c12
-rw-r--r--drivers/gpu/drm/mcde/mcde_dsi.c43
-rw-r--r--drivers/gpu/drm/mgag200/mgag200_drv.c6
-rw-r--r--drivers/gpu/drm/mgag200/mgag200_drv.h1
-rw-r--r--drivers/gpu/drm/mgag200/mgag200_mode.c14
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.c14
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.h3
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_kms.c61
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_regs.h1
-rw-r--r--drivers/gpu/drm/nouveau/dispnv50/wndw.c5
-rw-r--r--drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h2
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_debugfs.c12
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_fence.c1
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c14
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c6
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c6
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c3
-rw-r--r--drivers/gpu/drm/panel/panel-simple.c40
-rw-r--r--drivers/gpu/drm/qxl/qxl_display.c8
-rw-r--r--drivers/gpu/drm/rockchip/Kconfig14
-rw-r--r--drivers/gpu/drm/rockchip/Makefile4
-rw-r--r--drivers/gpu/drm/rockchip/analogix_dp-rockchip.c32
-rw-r--r--drivers/gpu/drm/rockchip/cdn-dp-core.c20
-rw-r--r--drivers/gpu/drm/rockchip/cdn-dp-core.h2
-rw-r--r--drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c17
-rw-r--r--drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c108
-rw-r--r--drivers/gpu/drm/rockchip/inno_hdmi.c40
-rw-r--r--drivers/gpu/drm/rockchip/rk3066_hdmi.c40
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_drv.c96
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_drv.h23
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_fb.c2
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_vop.c24
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_vop.h15
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_vop2.c2706
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_drm_vop2.h477
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_lvds.c26
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_vop2_reg.c281
-rw-r--r--drivers/gpu/drm/rockchip/rockchip_vop_reg.c11
-rw-r--r--drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c7
-rw-r--r--drivers/gpu/drm/tiny/simpledrm.c1
-rw-r--r--drivers/gpu/drm/vc4/vc4_bo.c8
-rw-r--r--drivers/gpu/drm/vc4/vc4_plane.c7
-rw-r--r--drivers/gpu/drm/vmwgfx/vmwgfx_fb.c10
-rw-r--r--drivers/hid/hid-picolcd_fb.c3
-rw-r--r--drivers/staging/fbtft/fbtft-core.c21
-rw-r--r--drivers/video/fbdev/broadsheetfb.c27
-rw-r--r--drivers/video/fbdev/core/fb_defio.c212
-rw-r--r--drivers/video/fbdev/core/fbmem.c22
-rw-r--r--drivers/video/fbdev/hecubafb.c4
-rw-r--r--drivers/video/fbdev/hyperv_fb.c10
-rw-r--r--drivers/video/fbdev/metronomefb.c23
-rw-r--r--drivers/video/fbdev/sh_mobile_lcdcfb.c27
-rw-r--r--drivers/video/fbdev/smscufx.c13
-rw-r--r--drivers/video/fbdev/ssd1307fb.c4
-rw-r--r--drivers/video/fbdev/udlfb.c18
-rw-r--r--drivers/video/fbdev/xen-fbfront.c10
-rw-r--r--include/drm/drm_atomic_uapi.h2
-rw-r--r--include/drm/drm_fb_helper.h3
-rw-r--r--include/drm/drm_managed.h3
-rw-r--r--include/drm/drm_plane.h4
-rw-r--r--include/dt-bindings/soc/rockchip,vop2.h14
-rw-r--r--include/linux/fb.h17
98 files changed, 6001 insertions, 878 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml b/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml
new file mode 100644
index 000000000000..77f174eee424
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/fsl,ldb.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8MP DPI to LVDS bridge chip
+
+maintainers:
+ - Marek Vasut <[email protected]>
+
+description: |
+ The i.MX8MP mediamix contains two registers which are responsible
+ for configuring the on-SoC DPI-to-LVDS serializer. This describes
+ those registers as bridge within the DT.
+
+properties:
+ compatible:
+ const: fsl,imx8mp-ldb
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: ldb
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Video port for DPI input.
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Video port for LVDS Channel-A output (panel or bridge).
+
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Video port for LVDS Channel-B output (panel or bridge).
+
+ required:
+ - port@0
+ - port@1
+
+required:
+ - compatible
+ - clocks
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/imx8mp-clock.h>
+
+ blk-ctrl {
+ bridge {
+ compatible = "fsl,imx8mp-ldb";
+ clocks = <&clk IMX8MP_CLK_MEDIA_LDB>;
+ clock-names = "ldb";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ ldb_from_lcdif2: endpoint {
+ remote-endpoint = <&lcdif2_to_ldb>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ ldb_lvds_ch0: endpoint {
+ remote-endpoint = <&ldb_to_lvdsx4panel>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ ldb_lvds_ch1: endpoint {
+ };
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
index cfe7bb9f89de..21ba90c9fe33 100644
--- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
+++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
@@ -296,6 +296,8 @@ properties:
- starry,kr070pe2t
# Starry 12.2" (1920x1200 pixels) TFT LCD panel
- starry,kr122ea0sra
+ # Startek KD070WVFPA043-C069A 7" TFT LCD panel
+ - startek,kd070wvfpa
# Team Source Display Technology TST043015CMHX 4.3" WQVGA TFT LCD panel
- team-source-display,tst043015cmhx
# Tianma Micro-electronics TM070JDHG30 7.0" WXGA TFT LCD panel
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
index da3b889ad8fc..7e59dee15a5f 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
@@ -23,10 +23,22 @@ properties:
- rockchip,rk3288-dw-hdmi
- rockchip,rk3328-dw-hdmi
- rockchip,rk3399-dw-hdmi
+ - rockchip,rk3568-dw-hdmi
reg-io-width:
const: 4
+ avdd-0v9-supply:
+ description:
+ A 0.9V supply that powers up the SoC internal circuitry. The actual pin name
+ varies between the different SoCs and is usually HDMI_TX_AVDD_0V9 or sometimes
+ HDMI_AVDD_1V0.
+
+ avdd-1v8-supply:
+ description:
+ A 1.8V supply that powers up the SoC internal circuitry. The pin name on the
+ SoC usually is HDMI_TX_AVDD_1V8.
+
clocks:
minItems: 2
items:
@@ -36,7 +48,8 @@ properties:
# order when present.
- description: The HDMI CEC controller main clock
- description: Power for GRF IO
- - description: External clock for some HDMI PHY
+ - description: External clock for some HDMI PHY (old clock name, deprecated)
+ - description: External clock for some HDMI PHY (new name)
clock-names:
minItems: 2
@@ -47,10 +60,14 @@ properties:
- cec
- grf
- vpll
+ - ref
- enum:
- grf
- vpll
- - const: vpll
+ - ref
+ - enum:
+ - vpll
+ - ref
ddc-i2c-bus:
$ref: /schemas/types.yaml#/definitions/phandle
@@ -72,6 +89,7 @@ properties:
The unwedge pinctrl entry shall drive the DDC SDA line low. This is
intended to work around a hardware errata that can cause the DDC I2C
bus to be wedged.
+ minItems: 1
items:
- const: default
- const: unwedge
@@ -79,27 +97,21 @@ properties:
ports:
$ref: /schemas/graph.yaml#/properties/ports
- properties:
- port:
- $ref: /schemas/graph.yaml#/$defs/port-base
- unevaluatedProperties: false
+ patternProperties:
+ "^port(@0)?$":
+ $ref: /schemas/graph.yaml#/properties/port
description: Input of the DWC HDMI TX
-
properties:
+ endpoint:
+ description: Connection to the VOP
endpoint@0:
- $ref: /schemas/graph.yaml#/properties/endpoint
description: Connection to the VOPB
-
endpoint@1:
- $ref: /schemas/graph.yaml#/properties/endpoint
description: Connection to the VOPL
-
- required:
- - endpoint@0
- - endpoint@1
-
- required:
- - port
+ properties:
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Output of the DWC HDMI TX
rockchip,grf:
$ref: /schemas/types.yaml#/definitions/phandle
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml
new file mode 100644
index 000000000000..655d9b327f7d
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml
@@ -0,0 +1,140 @@
+# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/rockchip/rockchip-vop2.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip SoC display controller (VOP2)
+
+description:
+ VOP2 (Video Output Processor v2) is the display controller for the Rockchip
+ series of SoCs which transfers the image data from a video memory
+ buffer to an external LCD interface.
+
+maintainers:
+ - Sandy Huang <[email protected]>
+ - Heiko Stuebner <[email protected]>
+
+properties:
+ compatible:
+ enum:
+ - rockchip,rk3566-vop
+ - rockchip,rk3568-vop
+
+ reg:
+ minItems: 1
+ items:
+ - description:
+ Must contain one entry corresponding to the base address and length
+ of the register space.
+ - description:
+ Can optionally contain a second entry corresponding to
+ the CRTC gamma LUT address.
+
+ interrupts:
+ maxItems: 1
+ description:
+ The VOP interrupt is shared by several interrupt sources, such as
+ frame start (VSYNC), line flag and other status interrupts.
+
+ clocks:
+ items:
+ - description: Clock for ddr buffer transfer.
+ - description: Clock for the ahb bus to R/W the phy regs.
+ - description: Pixel clock for video port 0.
+ - description: Pixel clock for video port 1.
+ - description: Pixel clock for video port 2.
+
+ clock-names:
+ items:
+ - const: aclk
+ - const: hclk
+ - const: dclk_vp0
+ - const: dclk_vp1
+ - const: dclk_vp2
+
+ rockchip,grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to GRF regs used for misc control
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Output endpoint of VP0
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Output endpoint of VP1
+
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Output endpoint of VP2
+
+ iommus:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/rk3568-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/power/rk3568-power.h>
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ vop: vop@fe040000 {
+ compatible = "rockchip,rk3568-vop";
+ reg = <0x0 0xfe040000 0x0 0x3000>, <0x0 0xfe044000 0x0 0x1000>;
+ interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_VOP>,
+ <&cru HCLK_VOP>,
+ <&cru DCLK_VOP0>,
+ <&cru DCLK_VOP1>,
+ <&cru DCLK_VOP2>;
+ clock-names = "aclk",
+ "hclk",
+ "dclk_vp0",
+ "dclk_vp1",
+ "dclk_vp2";
+ power-domains = <&power RK3568_PD_VO>;
+ iommus = <&vop_mmu>;
+ vop_out: ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ vp0: port@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ vp1: port@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ vp2: port@2 {
+ reg = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index 127e76ee0b2d..10bfb50908d1 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -603,6 +603,20 @@ Level: Advanced
Better Testing
==============
+Add unit tests using the Kernel Unit Testing (KUnit) framework
+--------------------------------------------------------------
+
+The `KUnit <https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html>`_
+provides a common framework for unit tests within the Linux kernel. Having a
+test suite would allow to identify regressions earlier.
+
+A good candidate for the first unit tests are the format-conversion helpers in
+``drm_format_helper.c``.
+
+Contact: Javier Martinez Canillas <[email protected]>
+
+Level: Intermediate
+
Enable trinity for DRM
----------------------
diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c
index 514d213261df..0fe564539166 100644
--- a/drivers/dma-buf/sync_file.c
+++ b/drivers/dma-buf/sync_file.c
@@ -157,9 +157,15 @@ static int sync_file_set_fence(struct sync_file *sync_file,
* we already own a new reference to the fence. For num_fence > 1
* we own the reference of the dma_fence_array creation.
*/
- if (num_fences == 1) {
+
+ if (num_fences == 0) {
+ sync_file->fence = dma_fence_get_stub();
+ kfree(fences);
+
+ } else if (num_fences == 1) {
sync_file->fence = fences[0];
kfree(fences);
+
} else {
array = dma_fence_array_create(num_fences, fences,
dma_fence_context_alloc(1),
@@ -261,19 +267,6 @@ static struct sync_file *sync_file_merge(const char *name, struct sync_file *a,
}
}
- if (index == 0)
- fences[index++] = dma_fence_get_stub();
-
- if (num_fences > index) {
- struct dma_fence **tmp;
-
- /* Keep going even when reducing the size failed */
- tmp = krealloc_array(fences, index, sizeof(*fences),
- GFP_KERNEL);
- if (tmp)
- fences = tmp;
- }
-
if (sync_file_set_fence(sync_file, fences, index) < 0)
goto err_put_fences;
diff --git a/drivers/gpu/drm/arm/malidp_planes.c b/drivers/gpu/drm/arm/malidp_planes.c
index 81d9f5004025..338cec4a3fff 100644
--- a/drivers/gpu/drm/arm/malidp_planes.c
+++ b/drivers/gpu/drm/arm/malidp_planes.c
@@ -344,7 +344,7 @@ static bool malidp_check_pages_threshold(struct malidp_plane_state *ms,
else
sgt = obj->funcs->get_sg_table(obj);
- if (!sgt)
+ if (IS_ERR(sgt))
return false;
sgl = sgt->sgl;
diff --git a/drivers/gpu/drm/ast/Makefile b/drivers/gpu/drm/ast/Makefile
index 21f71160bc3e..5a53ce51fb24 100644
--- a/drivers/gpu/drm/ast/Makefile
+++ b/drivers/gpu/drm/ast/Makefile
@@ -3,6 +3,6 @@
# Makefile for the drm device driver. This driver provides support for the
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
-ast-y := ast_drv.o ast_i2c.o ast_main.o ast_mm.o ast_mode.o ast_post.o ast_dp501.o
+ast-y := ast_drv.o ast_i2c.o ast_main.o ast_mm.o ast_mode.o ast_post.o ast_dp501.o ast_dp.o
obj-$(CONFIG_DRM_AST) := ast.o
diff --git a/drivers/gpu/drm/ast/ast_dp.c b/drivers/gpu/drm/ast/ast_dp.c
new file mode 100644
index 000000000000..4551bc8a3ecf
--- /dev/null
+++ b/drivers/gpu/drm/ast/ast_dp.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021, ASPEED Technology Inc.
+// Authors: KuoHsiang Chou <[email protected]>
+
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <drm/drm_print.h>
+#include "ast_drv.h"
+
+int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata)
+{
+ struct ast_private *ast = to_ast_private(dev);
+ u8 i = 0, j = 0;
+
+ /*
+ * CRD1[b5]: DP MCU FW is executing
+ * CRDC[b0]: DP link success
+ * CRDF[b0]: DP HPD
+ * CRE5[b0]: Host reading EDID process is done
+ */
+ if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, ASTDP_MCU_FW_EXECUTING) &&
+ ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, ASTDP_LINK_SUCCESS) &&
+ ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD) &&
+ ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5,
+ ASTDP_HOST_EDID_READ_DONE_MASK))) {
+ goto err_astdp_edid_not_ready;
+ }
+
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK,
+ 0x00);
+
+ for (i = 0; i < 32; i++) {
+ /*
+ * CRE4[7:0]: Read-Pointer for EDID (Unit: 4bytes); valid range: 0~64
+ */
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE4,
+ (u8) ~ASTDP_EDID_READ_POINTER_MASK, (u8) i);
+ j = 0;
+
+ /*
+ * CRD7[b0]: valid flag for EDID
+ * CRD6[b0]: mirror read pointer for EDID
+ */
+ while ((ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD7,
+ ASTDP_EDID_VALID_FLAG_MASK) != 0x01) ||
+ (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD6,
+ ASTDP_EDID_READ_POINTER_MASK) != i)) {
+ /*
+ * Delay are getting longer with each retry.
+ * 1. The Delays are often 2 loops when users request "Display Settings"
+ * of right-click of mouse.
+ * 2. The Delays are often longer a lot when system resume from S3/S4.
+ */
+ mdelay(j+1);
+
+ if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1,
+ ASTDP_MCU_FW_EXECUTING) &&
+ ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC,
+ ASTDP_LINK_SUCCESS) &&
+ ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD))) {
+ goto err_astdp_jump_out_loop_of_edid;
+ }
+
+ j++;
+ if (j > 200)
+ goto err_astdp_jump_out_loop_of_edid;
+ }
+
+ *(ediddata) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT,
+ 0xD8, ASTDP_EDID_READ_DATA_MASK);
+ *(ediddata + 1) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD9,
+ ASTDP_EDID_READ_DATA_MASK);
+ *(ediddata + 2) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDA,
+ ASTDP_EDID_READ_DATA_MASK);
+ *(ediddata + 3) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDB,
+ ASTDP_EDID_READ_DATA_MASK);
+
+ if (i == 31) {
+ /*
+ * For 128-bytes EDID_1.3,
+ * 1. Add the value of Bytes-126 to Bytes-127.
+ * The Bytes-127 is Checksum. Sum of all 128bytes should
+ * equal 0 (mod 256).
+ * 2. Modify Bytes-126 to be 0.
+ * The Bytes-126 indicates the Number of extensions to
+ * follow. 0 represents noextensions.
+ */
+ *(ediddata + 3) = *(ediddata + 3) + *(ediddata + 2);
+ *(ediddata + 2) = 0;
+ }
+
+ ediddata += 4;
+ }
+
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK,
+ ASTDP_HOST_EDID_READ_DONE);
+
+ return 0;
+
+err_astdp_jump_out_loop_of_edid:
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5,
+ (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK,
+ ASTDP_HOST_EDID_READ_DONE);
+ return (~(j+256) + 1);
+
+err_astdp_edid_not_ready:
+ if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, ASTDP_MCU_FW_EXECUTING)))
+ return (~0xD1 + 1);
+ if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, ASTDP_LINK_SUCCESS)))
+ return (~0xDC + 1);
+ if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD)))
+ return (~0xDF + 1);
+ if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, ASTDP_HOST_EDID_READ_DONE_MASK)))
+ return (~0xE5 + 1);
+
+ return 0;
+}
+
+/*
+ * Launch Aspeed DP
+ */
+void ast_dp_launch(struct drm_device *dev, u8 bPower)
+{
+ u32 i = 0, j = 0, WaitCount = 1;
+ u8 bDPTX = 0;
+ u8 bDPExecute = 1;
+
+ struct ast_private *ast = to_ast_private(dev);
+ // S3 come back, need more time to wait BMC ready.
+ if (bPower)
+ WaitCount = 300;
+
+
+ // Wait total count by different condition.
+ for (j = 0; j < WaitCount; j++) {
+ bDPTX = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, TX_TYPE_MASK);
+
+ if (bDPTX)
+ break;
+
+ msleep(100);
+ }
+
+ // 0xE : ASTDP with DPMCU FW handling
+ if (bDPTX == ASTDP_DPMCU_TX) {
+ // Wait one second then timeout.
+ i = 0;
+
+ while (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, COPROCESSOR_LAUNCH) !=
+ COPROCESSOR_LAUNCH) {
+ i++;
+ // wait 100 ms
+ msleep(100);
+
+ if (i >= 10) {
+ // DP would not be ready.
+ bDPExecute = 0;
+ break;
+ }
+ }
+
+ if (bDPExecute)
+ ast->tx_chip_type = AST_TX_ASTDP;
+
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5,
+ (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK,
+ ASTDP_HOST_EDID_READ_DONE);
+ } else
+ ast->tx_chip_type = AST_TX_NONE;
+}
+
+
+
+void ast_dp_power_on_off(struct drm_device *dev, bool on)
+{
+ struct ast_private *ast = to_ast_private(dev);
+ // Read and Turn off DP PHY sleep
+ u8 bE3 = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, AST_DP_VIDEO_ENABLE);
+
+ // Turn on DP PHY sleep
+ if (!on)
+ bE3 |= AST_DP_PHY_SLEEP;
+
+ // DP Power on/off
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, (u8) ~AST_DP_PHY_SLEEP, bE3);
+}
+
+
+
+void ast_dp_set_on_off(struct drm_device *dev, bool on)
+{
+ struct ast_private *ast = to_ast_private(dev);
+ u8 video_on_off = on;
+
+ // Video On/Off
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, (u8) ~AST_DP_VIDEO_ENABLE, on);
+
+ // If DP plug in and link successful then check video on / off status
+ if (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, ASTDP_LINK_SUCCESS) &&
+ ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD)) {
+ video_on_off <<= 4;
+ while (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF,
+ ASTDP_MIRROR_VIDEO_ENABLE) != video_on_off) {
+ // wait 1 ms
+ mdelay(1);
+ }
+ }
+}
+
+void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mode)
+{
+ struct ast_private *ast = to_ast_private(crtc->dev);
+
+ u32 ulRefreshRateIndex;
+ u8 ModeIdx;
+
+ ulRefreshRateIndex = vbios_mode->enh_table->refresh_rate_index - 1;
+
+ switch (crtc->mode.crtc_hdisplay) {
+ case 320:
+ ModeIdx = ASTDP_320x240_60;
+ break;
+ case 400:
+ ModeIdx = ASTDP_400x300_60;
+ break;
+ case 512:
+ ModeIdx = ASTDP_512x384_60;
+ break;
+ case 640:
+ ModeIdx = (ASTDP_640x480_60 + (u8) ulRefreshRateIndex);
+ break;
+ case 800:
+ ModeIdx = (ASTDP_800x600_56 + (u8) ulRefreshRateIndex);
+ break;
+ case 1024:
+ ModeIdx = (ASTDP_1024x768_60 + (u8) ulRefreshRateIndex);
+ break;
+ case 1152:
+ ModeIdx = ASTDP_1152x864_75;
+ break;
+ case 1280:
+ if (crtc->mode.crtc_vdisplay == 800)
+ ModeIdx = (ASTDP_1280x800_60_RB - (u8) ulRefreshRateIndex);
+ else // 1024
+ ModeIdx = (ASTDP_1280x1024_60 + (u8) ulRefreshRateIndex);
+ break;
+ case 1360:
+ case 1366:
+ ModeIdx = ASTDP_1366x768_60;
+ break;
+ case 1440:
+ ModeIdx = (ASTDP_1440x900_60_RB - (u8) ulRefreshRateIndex);
+ break;
+ case 1600:
+ if (crtc->mode.crtc_vdisplay == 900)
+ ModeIdx = (ASTDP_1600x900_60_RB - (u8) ulRefreshRateIndex);
+ else //1200
+ ModeIdx = ASTDP_1600x1200_60;
+ break;
+ case 1680:
+ ModeIdx = (ASTDP_1680x1050_60_RB - (u8) ulRefreshRateIndex);
+ break;
+ case 1920:
+ if (crtc->mode.crtc_vdisplay == 1080)
+ ModeIdx = ASTDP_1920x1080_60;
+ else //1200
+ ModeIdx = ASTDP_1920x1200_60;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp)
+ * CRE1[7:0]: MISC1 (default: 0x00)
+ * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50)
+ */
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE0, (u8) ~ASTDP_CLEAR_MASK,
+ ASTDP_MISC0_24bpp);
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE1, (u8) ~ASTDP_CLEAR_MASK, ASTDP_MISC1);
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE2, (u8) ~ASTDP_CLEAR_MASK, ModeIdx);
+}
diff --git a/drivers/gpu/drm/ast/ast_drv.c b/drivers/gpu/drm/ast/ast_drv.c
index 7465c4f0156a..760b27971557 100644
--- a/drivers/gpu/drm/ast/ast_drv.c
+++ b/drivers/gpu/drm/ast/ast_drv.c
@@ -159,15 +159,10 @@ static int ast_drm_thaw(struct drm_device *dev)
static int ast_drm_resume(struct drm_device *dev)
{
- int ret;
-
if (pci_enable_device(to_pci_dev(dev->dev)))
return -EIO;
- ret = ast_drm_thaw(dev);
- if (ret)
- return ret;
- return 0;
+ return ast_drm_thaw(dev);
}
static int ast_pm_suspend(struct device *dev)
diff --git a/drivers/gpu/drm/ast/ast_drv.h b/drivers/gpu/drm/ast/ast_drv.h
index a19315b2f7e5..afebe35f205e 100644
--- a/drivers/gpu/drm/ast/ast_drv.h
+++ b/drivers/gpu/drm/ast/ast_drv.h
@@ -70,6 +70,7 @@ enum ast_tx_chip {
AST_TX_NONE,
AST_TX_SIL164,
AST_TX_DP501,
+ AST_TX_ASTDP,
};
#define AST_DRAM_512Mx16 0
@@ -158,6 +159,7 @@ to_ast_sil164_connector(struct drm_connector *connector)
struct ast_private {
struct drm_device base;
+ struct mutex ioregs_lock; /* Protects access to I/O registers in ioregs */
void __iomem *regs;
void __iomem *ioregs;
void __iomem *dp501_fw_buf;
@@ -184,6 +186,10 @@ struct ast_private {
struct drm_encoder encoder;
struct drm_connector connector;
} dp501;
+ struct {
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ } astdp;
} output;
bool support_wide_screen;
@@ -357,10 +363,113 @@ int ast_mode_config_init(struct ast_private *ast);
#define AST_DP501_EDID_DATA 0xf020
/* Define for Soc scratched reg */
+#define COPROCESSOR_LAUNCH BIT(5)
+
+/*
+ * Display Transmitter Type:
+ */
+#define TX_TYPE_MASK GENMASK(3, 1)
+#define NO_TX (0 << 1)
+#define ITE66121_VBIOS_TX (1 << 1)
+#define SI164_VBIOS_TX (2 << 1)
+#define CH7003_VBIOS_TX (3 << 1)
+#define DP501_VBIOS_TX (4 << 1)
+#define ANX9807_VBIOS_TX (5 << 1)
+#define TX_FW_EMBEDDED_FW_TX (6 << 1)
+#define ASTDP_DPMCU_TX (7 << 1)
+
#define AST_VRAM_INIT_STATUS_MASK GENMASK(7, 6)
//#define AST_VRAM_INIT_BY_BMC BIT(7)
//#define AST_VRAM_INIT_READY BIT(6)
+/* Define for Soc scratched reg used on ASTDP */
+#define AST_DP_PHY_SLEEP BIT(4)
+#define AST_DP_VIDEO_ENABLE BIT(0)
+
+#define AST_DP_POWER_ON true
+#define AST_DP_POWER_OFF false
+
+/*
+ * CRD1[b5]: DP MCU FW is executing
+ * CRDC[b0]: DP link success
+ * CRDF[b0]: DP HPD
+ * CRE5[b0]: Host reading EDID process is done
+ */
+#define ASTDP_MCU_FW_EXECUTING BIT(5)
+#define ASTDP_LINK_SUCCESS BIT(0)
+#define ASTDP_HPD BIT(0)
+#define ASTDP_HOST_EDID_READ_DONE BIT(0)
+#define ASTDP_HOST_EDID_READ_DONE_MASK GENMASK(0, 0)
+
+/*
+ * CRB8[b1]: Enable VSYNC off
+ * CRB8[b0]: Enable HSYNC off
+ */
+#define AST_DPMS_VSYNC_OFF BIT(1)
+#define AST_DPMS_HSYNC_OFF BIT(0)
+
+/*
+ * CRDF[b4]: Mirror of AST_DP_VIDEO_ENABLE
+ * Precondition: A. ~AST_DP_PHY_SLEEP &&
+ * B. DP_HPD &&
+ * C. DP_LINK_SUCCESS
+ */
+#define ASTDP_MIRROR_VIDEO_ENABLE BIT(4)
+
+#define ASTDP_EDID_READ_POINTER_MASK GENMASK(7, 0)
+#define ASTDP_EDID_VALID_FLAG_MASK GENMASK(0, 0)
+#define ASTDP_EDID_READ_DATA_MASK GENMASK(7, 0)
+
+/*
+ * ASTDP setmode registers:
+ * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp)
+ * CRE1[7:0]: MISC1 (default: 0x00)
+ * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50)
+ */
+#define ASTDP_MISC0_24bpp BIT(5)
+#define ASTDP_MISC1 0
+#define ASTDP_CLEAR_MASK GENMASK(7, 0)
+
+/*
+ * ASTDP resoultion table:
+ * EX: ASTDP_A_B_C:
+ * A: Resolution
+ * B: Refresh Rate
+ * C: Misc information, such as CVT, Reduce Blanked
+ */
+#define ASTDP_640x480_60 0x00
+#define ASTDP_640x480_72 0x01
+#define ASTDP_640x480_75 0x02
+#define ASTDP_640x480_85 0x03
+#define ASTDP_800x600_56 0x04
+#define ASTDP_800x600_60 0x05
+#define ASTDP_800x600_72 0x06
+#define ASTDP_800x600_75 0x07
+#define ASTDP_800x600_85 0x08
+#define ASTDP_1024x768_60 0x09
+#define ASTDP_1024x768_70 0x0A
+#define ASTDP_1024x768_75 0x0B
+#define ASTDP_1024x768_85 0x0C
+#define ASTDP_1280x1024_60 0x0D
+#define ASTDP_1280x1024_75 0x0E
+#define ASTDP_1280x1024_85 0x0F
+#define ASTDP_1600x1200_60 0x10
+#define ASTDP_320x240_60 0x11
+#define ASTDP_400x300_60 0x12
+#define ASTDP_512x384_60 0x13
+#define ASTDP_1920x1200_60 0x14
+#define ASTDP_1920x1080_60 0x15
+#define ASTDP_1280x800_60 0x16
+#define ASTDP_1280x800_60_RB 0x17
+#define ASTDP_1440x900_60 0x18
+#define ASTDP_1440x900_60_RB 0x19
+#define ASTDP_1680x1050_60 0x1A
+#define ASTDP_1680x1050_60_RB 0x1B
+#define ASTDP_1600x900_60 0x1C
+#define ASTDP_1600x900_60_RB 0x1D
+#define ASTDP_1366x768_60 0x1E
+#define ASTDP_1152x864_75 0x1F
+
int ast_mm_init(struct ast_private *ast);
/* ast post */
@@ -381,4 +490,11 @@ void ast_init_3rdtx(struct drm_device *dev);
/* ast_i2c.c */
struct ast_i2c_chan *ast_i2c_create(struct drm_device *dev);
+/* aspeed DP */
+int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata);
+void ast_dp_launch(struct drm_device *dev, u8 bPower);
+void ast_dp_power_on_off(struct drm_device *dev, bool no);
+void ast_dp_set_on_off(struct drm_device *dev, bool no);
+void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mode);
+
#endif
diff --git a/drivers/gpu/drm/ast/ast_main.c b/drivers/gpu/drm/ast/ast_main.c
index 22e9e2d3c71a..d770d5a23c1a 100644
--- a/drivers/gpu/drm/ast/ast_main.c
+++ b/drivers/gpu/drm/ast/ast_main.c
@@ -232,7 +232,7 @@ static int ast_detect_chip(struct drm_device *dev, bool *need_post)
ast->tx_chip_type = AST_TX_SIL164;
}
- if ((ast->chip == AST2300) || (ast->chip == AST2400)) {
+ if ((ast->chip == AST2300) || (ast->chip == AST2400) || (ast->chip == AST2500)) {
/*
* On AST2300 and 2400, look the configuration set by the SoC in
* the SOC scratch register #1 bits 11:8 (interestingly marked
@@ -256,7 +256,8 @@ static int ast_detect_chip(struct drm_device *dev, bool *need_post)
case 0x0c:
ast->tx_chip_type = AST_TX_DP501;
}
- }
+ } else if (ast->chip == AST2600)
+ ast_dp_launch(&ast->base, 0);
/* Print stuff for diagnostic purposes */
switch(ast->tx_chip_type) {
@@ -420,6 +421,10 @@ struct ast_private *ast_device_create(const struct drm_driver *drv,
pci_set_drvdata(pdev, dev);
+ ret = drmm_mutex_init(dev, &ast->ioregs_lock);
+ if (ret)
+ return ERR_PTR(ret);
+
ast->regs = pcim_iomap(pdev, 1, 0);
if (!ast->regs)
return ERR_PTR(-EIO);
diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c
index 45b56b39ad47..323af2746aa9 100644
--- a/drivers/gpu/drm/ast/ast_mode.c
+++ b/drivers/gpu/drm/ast/ast_mode.c
@@ -988,21 +988,41 @@ err_drm_gem_vram_put:
static void ast_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct ast_private *ast = to_ast_private(crtc->dev);
+ u8 ch = AST_DPMS_VSYNC_OFF | AST_DPMS_HSYNC_OFF;
/* TODO: Maybe control display signal generation with
* Sync Enable (bit CR17.7).
*/
switch (mode) {
case DRM_MODE_DPMS_ON:
- case DRM_MODE_DPMS_STANDBY:
- case DRM_MODE_DPMS_SUSPEND:
+ ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, 0);
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xfc, 0);
if (ast->tx_chip_type == AST_TX_DP501)
ast_set_dp501_video_output(crtc->dev, 1);
+
+ if (ast->tx_chip_type == AST_TX_ASTDP) {
+ ast_dp_power_on_off(crtc->dev, AST_DP_POWER_ON);
+ ast_wait_for_vretrace(ast);
+ ast_dp_set_on_off(crtc->dev, 1);
+ }
+
+ ast_crtc_load_lut(ast, crtc);
break;
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
+ ch = mode;
if (ast->tx_chip_type == AST_TX_DP501)
ast_set_dp501_video_output(crtc->dev, 0);
break;
+
+ if (ast->tx_chip_type == AST_TX_ASTDP) {
+ ast_dp_set_on_off(crtc->dev, 0);
+ ast_dp_power_on_off(crtc->dev, AST_DP_POWER_OFF);
+ }
+
+ ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, 0x20);
+ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xfc, ch);
}
}
@@ -1027,7 +1047,7 @@ ast_crtc_helper_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode
if ((ast->chip == AST2100) || (ast->chip == AST2200) ||
(ast->chip == AST2300) || (ast->chip == AST2400) ||
- (ast->chip == AST2500)) {
+ (ast->chip == AST2500) || (ast->chip == AST2600)) {
if ((mode->hdisplay == 1920) && (mode->vdisplay == 1080))
return MODE_OK;
@@ -1099,6 +1119,20 @@ static int ast_crtc_helper_atomic_check(struct drm_crtc *crtc,
return 0;
}
+static void ast_crtc_helper_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct ast_private *ast = to_ast_private(dev);
+
+ /*
+ * Concurrent operations could possibly trigger a call to
+ * drm_connector_helper_funcs.get_modes by trying to read the
+ * display modes. Protect access to I/O registers by acquiring
+ * the I/O-register lock. Released in atomic_flush().
+ */
+ mutex_lock(&ast->ioregs_lock);
+}
+
static void
ast_crtc_helper_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_state *state)
@@ -1107,9 +1141,11 @@ ast_crtc_helper_atomic_flush(struct drm_crtc *crtc,
crtc);
struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
crtc);
- struct ast_private *ast = to_ast_private(crtc->dev);
+ struct drm_device *dev = crtc->dev;
+ struct ast_private *ast = to_ast_private(dev);
struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state);
struct ast_crtc_state *old_ast_crtc_state = to_ast_crtc_state(old_crtc_state);
+ struct ast_vbios_mode_info *vbios_mode_info = &ast_crtc_state->vbios_mode_info;
/*
* The gamma LUT has to be reloaded after changing the primary
@@ -1117,6 +1153,12 @@ ast_crtc_helper_atomic_flush(struct drm_crtc *crtc,
*/
if (old_ast_crtc_state->format != ast_crtc_state->format)
ast_crtc_load_lut(ast, crtc);
+
+ //Set Aspeed Display-Port
+ if (ast->tx_chip_type == AST_TX_ASTDP)
+ ast_dp_set_mode(crtc, vbios_mode_info);
+
+ mutex_unlock(&ast->ioregs_lock);
}
static void
@@ -1175,6 +1217,7 @@ ast_crtc_helper_atomic_disable(struct drm_crtc *crtc,
static const struct drm_crtc_helper_funcs ast_crtc_helper_funcs = {
.mode_valid = ast_crtc_helper_mode_valid,
.atomic_check = ast_crtc_helper_atomic_check,
+ .atomic_begin = ast_crtc_helper_atomic_begin,
.atomic_flush = ast_crtc_helper_atomic_flush,
.atomic_enable = ast_crtc_helper_atomic_enable,
.atomic_disable = ast_crtc_helper_atomic_disable,
@@ -1260,21 +1303,33 @@ static int ast_crtc_init(struct drm_device *dev)
static int ast_vga_connector_helper_get_modes(struct drm_connector *connector)
{
struct ast_vga_connector *ast_vga_connector = to_ast_vga_connector(connector);
+ struct drm_device *dev = connector->dev;
+ struct ast_private *ast = to_ast_private(dev);
struct edid *edid;
int count;
if (!ast_vga_connector->i2c)
goto err_drm_connector_update_edid_property;
+ /*
+ * Protect access to I/O registers from concurrent modesetting
+ * by acquiring the I/O-register lock.
+ */
+ mutex_lock(&ast->ioregs_lock);
+
edid = drm_get_edid(connector, &ast_vga_connector->i2c->adapter);
if (!edid)
- goto err_drm_connector_update_edid_property;
+ goto err_mutex_unlock;
+
+ mutex_unlock(&ast->ioregs_lock);
count = drm_add_edid_modes(connector, edid);
kfree(edid);
return count;
+err_mutex_unlock:
+ mutex_unlock(&ast->ioregs_lock);
err_drm_connector_update_edid_property:
drm_connector_update_edid_property(connector, NULL);
return 0;
@@ -1354,21 +1409,33 @@ static int ast_vga_output_init(struct ast_private *ast)
static int ast_sil164_connector_helper_get_modes(struct drm_connector *connector)
{
struct ast_sil164_connector *ast_sil164_connector = to_ast_sil164_connector(connector);
+ struct drm_device *dev = connector->dev;
+ struct ast_private *ast = to_ast_private(dev);
struct edid *edid;
int count;
if (!ast_sil164_connector->i2c)
goto err_drm_connector_update_edid_property;
+ /*
+ * Protect access to I/O registers from concurrent modesetting
+ * by acquiring the I/O-register lock.
+ */
+ mutex_lock(&ast->ioregs_lock);
+
edid = drm_get_edid(connector, &ast_sil164_connector->i2c->adapter);
if (!edid)
- goto err_drm_connector_update_edid_property;
+ goto err_mutex_unlock;
+
+ mutex_unlock(&ast->ioregs_lock);
count = drm_add_edid_modes(connector, edid);
kfree(edid);
return count;
+err_mutex_unlock:
+ mutex_unlock(&ast->ioregs_lock);
err_drm_connector_update_edid_property:
drm_connector_update_edid_property(connector, NULL);
return 0;
@@ -1528,6 +1595,93 @@ static int ast_dp501_output_init(struct ast_private *ast)
}
/*
+ * ASPEED Display-Port Connector
+ */
+
+static int ast_astdp_connector_helper_get_modes(struct drm_connector *connector)
+{
+ void *edid;
+
+ int succ;
+ int count;
+
+ edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
+ if (!edid)
+ goto err_drm_connector_update_edid_property;
+
+ succ = ast_astdp_read_edid(connector->dev, edid);
+ if (succ < 0)
+ goto err_kfree;
+
+ drm_connector_update_edid_property(connector, edid);
+ count = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+
+ return count;
+
+err_kfree:
+ kfree(edid);
+err_drm_connector_update_edid_property:
+ drm_connector_update_edid_property(connector, NULL);
+ return 0;
+}
+
+static const struct drm_connector_helper_funcs ast_astdp_connector_helper_funcs = {
+ .get_modes = ast_astdp_connector_helper_get_modes,
+};
+
+static const struct drm_connector_funcs ast_astdp_connector_funcs = {
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int ast_astdp_connector_init(struct drm_device *dev, struct drm_connector *connector)
+{
+ int ret;
+
+ ret = drm_connector_init(dev, connector, &ast_astdp_connector_funcs,
+ DRM_MODE_CONNECTOR_DisplayPort);
+ if (ret)
+ return ret;
+
+ drm_connector_helper_add(connector, &ast_astdp_connector_helper_funcs);
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT;
+
+ return 0;
+}
+
+static int ast_astdp_output_init(struct ast_private *ast)
+{
+ struct drm_device *dev = &ast->base;
+ struct drm_crtc *crtc = &ast->crtc;
+ struct drm_encoder *encoder = &ast->output.astdp.encoder;
+ struct drm_connector *connector = &ast->output.astdp.connector;
+ int ret;
+
+ ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS);
+ if (ret)
+ return ret;
+ encoder->possible_crtcs = drm_crtc_mask(crtc);
+
+ ret = ast_astdp_connector_init(dev, connector);
+ if (ret)
+ return ret;
+
+ ret = drm_connector_attach_encoder(connector, encoder);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
* Mode config
*/
@@ -1563,7 +1717,8 @@ int ast_mode_config_init(struct ast_private *ast)
ast->chip == AST2200 ||
ast->chip == AST2300 ||
ast->chip == AST2400 ||
- ast->chip == AST2500) {
+ ast->chip == AST2500 ||
+ ast->chip == AST2600) {
dev->mode_config.max_width = 1920;
dev->mode_config.max_height = 2048;
} else {
@@ -1594,6 +1749,9 @@ int ast_mode_config_init(struct ast_private *ast)
case AST_TX_DP501:
ret = ast_dp501_output_init(ast);
break;
+ case AST_TX_ASTDP:
+ ret = ast_astdp_output_init(ast);
+ break;
}
if (ret)
return ret;
diff --git a/drivers/gpu/drm/ast/ast_post.c b/drivers/gpu/drm/ast/ast_post.c
index b5d92f652fd8..0aa9cf0fb5c3 100644
--- a/drivers/gpu/drm/ast/ast_post.c
+++ b/drivers/gpu/drm/ast/ast_post.c
@@ -379,7 +379,9 @@ void ast_post_gpu(struct drm_device *dev)
ast_enable_mmio(dev);
ast_set_def_ext_reg(dev);
- if (ast->config_mode == ast_use_p2a) {
+ if (ast->chip == AST2600) {
+ ast_dp_launch(dev, 1);
+ } else if (ast->config_mode == ast_use_p2a) {
if (ast->chip == AST2500)
ast_post_chip_2500(dev);
else if (ast->chip == AST2300 || ast->chip == AST2400)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index ef9f1b0d91bf..da3441830d46 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -75,6 +75,14 @@ config DRM_DISPLAY_CONNECTOR
on ARM-based platforms. Saying Y here when this driver is not needed
will not cause any issue.
+config DRM_FSL_LDB
+ tristate "Freescale i.MX8MP LDB bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select DRM_PANEL_BRIDGE
+ help
+ Support for i.MX8MP DPI-to-LVDS on-SoC encoder.
+
config DRM_ITE_IT6505
tristate "ITE IT6505 DisplayPort bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index b0edf2022fa0..f6c0a95de549 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
+obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o
obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
index 6a882891d91c..9e3bb8a8ee40 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
@@ -209,10 +209,16 @@
#define ADV7511_REG_CEC_TX_ENABLE 0x11
#define ADV7511_REG_CEC_TX_RETRY 0x12
#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14
-#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15
-#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16
-#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25
-#define ADV7511_REG_CEC_RX_ENABLE 0x26
+#define ADV7511_REG_CEC_RX1_FRAME_HDR 0x15
+#define ADV7511_REG_CEC_RX1_FRAME_DATA0 0x16
+#define ADV7511_REG_CEC_RX1_FRAME_LEN 0x25
+#define ADV7511_REG_CEC_RX_STATUS 0x26
+#define ADV7511_REG_CEC_RX2_FRAME_HDR 0x27
+#define ADV7511_REG_CEC_RX2_FRAME_DATA0 0x28
+#define ADV7511_REG_CEC_RX2_FRAME_LEN 0x37
+#define ADV7511_REG_CEC_RX3_FRAME_HDR 0x38
+#define ADV7511_REG_CEC_RX3_FRAME_DATA0 0x39
+#define ADV7511_REG_CEC_RX3_FRAME_LEN 0x48
#define ADV7511_REG_CEC_RX_BUFFERS 0x4a
#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b
#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c
@@ -220,6 +226,18 @@
#define ADV7511_REG_CEC_CLK_DIV 0x4e
#define ADV7511_REG_CEC_SOFT_RESET 0x50
+static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = {
+ ADV7511_REG_CEC_RX1_FRAME_HDR,
+ ADV7511_REG_CEC_RX2_FRAME_HDR,
+ ADV7511_REG_CEC_RX3_FRAME_HDR,
+};
+
+static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = {
+ ADV7511_REG_CEC_RX1_FRAME_LEN,
+ ADV7511_REG_CEC_RX2_FRAME_LEN,
+ ADV7511_REG_CEC_RX3_FRAME_LEN,
+};
+
#define ADV7533_REG_CEC_OFFSET 0x70
enum adv7511_input_clock {
@@ -335,6 +353,7 @@ struct adv7511 {
struct regmap *regmap;
struct regmap *regmap_cec;
+ unsigned int reg_cec_offset;
enum drm_connector_status status;
bool powered;
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
index 28d9becc939c..399f625a50c8 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
@@ -17,12 +17,12 @@
#define ADV7511_INT1_CEC_MASK \
(ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \
- ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1)
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1 | \
+ ADV7511_INT1_CEC_RX_READY2 | ADV7511_INT1_CEC_RX_READY3)
static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
unsigned int val;
if (regmap_read(adv7511->regmap_cec,
@@ -71,26 +71,16 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
}
}
-void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
- const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
- ADV7511_INT1_CEC_TX_ARBIT_LOST |
- ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ unsigned int offset = adv7511->reg_cec_offset;
struct cec_msg msg = {};
unsigned int len;
unsigned int val;
u8 i;
- if (irq1 & irq_tx_mask)
- adv_cec_tx_raw_status(adv7511, irq1);
-
- if (!(irq1 & ADV7511_INT1_CEC_RX_READY1))
- return;
-
if (regmap_read(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len))
+ ADV7511_REG_CEC_RX_FRAME_LEN[rx_buf] + offset, &len))
return;
msg.len = len & 0x1f;
@@ -103,23 +93,80 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
for (i = 0; i < msg.len; i++) {
regmap_read(adv7511->regmap_cec,
- i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val);
+ i + ADV7511_REG_CEC_RX_FRAME_HDR[rx_buf] + offset,
+ &val);
msg.msg[i] = val;
}
- /* toggle to re-enable rx 1 */
- regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 1);
- regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ /* Toggle RX Ready Clear bit to re-enable this RX buffer */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf),
+ BIT(rx_buf));
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), 0);
+
cec_received_msg(adv7511->cec_adap, &msg);
}
+void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+{
+ unsigned int offset = adv7511->reg_cec_offset;
+ const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
+ ADV7511_INT1_CEC_TX_ARBIT_LOST |
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ const u32 irq_rx_mask = ADV7511_INT1_CEC_RX_READY1 |
+ ADV7511_INT1_CEC_RX_READY2 |
+ ADV7511_INT1_CEC_RX_READY3;
+ unsigned int rx_status;
+ int rx_order[3] = { -1, -1, -1 };
+ int i;
+
+ if (irq1 & irq_tx_mask)
+ adv_cec_tx_raw_status(adv7511, irq1);
+
+ if (!(irq1 & irq_rx_mask))
+ return;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_STATUS + offset, &rx_status))
+ return;
+
+ /*
+ * ADV7511_REG_CEC_RX_STATUS[5:0] contains the reception order of RX
+ * buffers 0, 1, and 2 in bits [1:0], [3:2], and [5:4] respectively.
+ * The values are to be interpreted as follows:
+ *
+ * 0 = buffer unused
+ * 1 = buffer contains oldest received frame (if applicable)
+ * 2 = buffer contains second oldest received frame (if applicable)
+ * 3 = buffer contains third oldest received frame (if applicable)
+ *
+ * Fill rx_order with the sequence of RX buffer indices to
+ * read from in order, where -1 indicates that there are no
+ * more buffers to process.
+ */
+ for (i = 0; i < 3; i++) {
+ unsigned int timestamp = (rx_status >> (2 * i)) & 0x3;
+
+ if (timestamp)
+ rx_order[timestamp - 1] = i;
+ }
+
+ /* Read CEC RX buffers in the appropriate order as prescribed above */
+ for (i = 0; i < 3; i++) {
+ int rx_buf = rx_order[i];
+
+ if (rx_buf < 0)
+ break;
+
+ adv7511_cec_rx(adv7511, rx_buf);
+ }
+}
+
static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
if (adv7511->i2c_cec == NULL)
return -EIO;
@@ -129,11 +176,11 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
0x03, 0x01);
- /* legacy mode and clear all rx buffers */
+ /* non-legacy mode and clear all rx buffers */
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x0f);
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
/* initially disable tx */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
@@ -141,7 +188,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
/* tx: ready */
/* tx: arbitration lost */
/* tx: retry timeout */
- /* rx: ready 1 */
+ /* rx: ready 1-3 */
regmap_update_bits(adv7511->regmap,
ADV7511_REG_INT_ENABLE(1), 0x3f,
ADV7511_INT1_CEC_MASK);
@@ -165,8 +212,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
unsigned int i, free_idx = ADV7511_MAX_ADDRS;
if (!adv7511->cec_enabled_adap)
@@ -235,8 +281,7 @@ static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
u8 len = msg->len;
unsigned int i;
@@ -289,8 +334,7 @@ static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
int ret = adv7511_cec_parse_dt(dev, adv7511);
if (ret)
@@ -310,9 +354,9 @@ int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_SOFT_RESET + offset, 0x00);
- /* legacy mode */
+ /* non-legacy mode - use all three RX buffers */
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index b3f10c54e064..5bb9300040dd 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -1027,14 +1027,19 @@ static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg)
struct i2c_client *i2c = to_i2c_client(dev);
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
- if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
- reg -= ADV7533_REG_CEC_OFFSET;
+ reg -= adv7511->reg_cec_offset;
switch (reg) {
- case ADV7511_REG_CEC_RX_FRAME_HDR:
- case ADV7511_REG_CEC_RX_FRAME_DATA0...
- ADV7511_REG_CEC_RX_FRAME_DATA0 + 14:
- case ADV7511_REG_CEC_RX_FRAME_LEN:
+ case ADV7511_REG_CEC_RX1_FRAME_HDR:
+ case ADV7511_REG_CEC_RX1_FRAME_DATA0 ... ADV7511_REG_CEC_RX1_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX1_FRAME_LEN:
+ case ADV7511_REG_CEC_RX2_FRAME_HDR:
+ case ADV7511_REG_CEC_RX2_FRAME_DATA0 ... ADV7511_REG_CEC_RX2_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX2_FRAME_LEN:
+ case ADV7511_REG_CEC_RX3_FRAME_HDR:
+ case ADV7511_REG_CEC_RX3_FRAME_DATA0 ... ADV7511_REG_CEC_RX3_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX3_FRAME_LEN:
+ case ADV7511_REG_CEC_RX_STATUS:
case ADV7511_REG_CEC_RX_BUFFERS:
case ADV7511_REG_CEC_TX_LOW_DRV_CNT:
return true;
@@ -1073,6 +1078,8 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv)
ret = adv7533_patch_cec_registers(adv);
if (ret)
goto err;
+
+ adv->reg_cec_offset = ADV7533_REG_CEC_OFFSET;
}
return 0;
diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
new file mode 100644
index 000000000000..b2675c769a55
--- /dev/null
+++ b/drivers/gpu/drm/bridge/fsl-ldb.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2022 Marek Vasut <[email protected]>
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#define LDB_CTRL 0x5c
+#define LDB_CTRL_CH0_ENABLE BIT(0)
+#define LDB_CTRL_CH0_DI_SELECT BIT(1)
+#define LDB_CTRL_CH1_ENABLE BIT(2)
+#define LDB_CTRL_CH1_DI_SELECT BIT(3)
+#define LDB_CTRL_SPLIT_MODE BIT(4)
+#define LDB_CTRL_CH0_DATA_WIDTH BIT(5)
+#define LDB_CTRL_CH0_BIT_MAPPING BIT(6)
+#define LDB_CTRL_CH1_DATA_WIDTH BIT(7)
+#define LDB_CTRL_CH1_BIT_MAPPING BIT(8)
+#define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9)
+#define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10)
+#define LDB_CTRL_REG_CH0_FIFO_RESET BIT(11)
+#define LDB_CTRL_REG_CH1_FIFO_RESET BIT(12)
+#define LDB_CTRL_ASYNC_FIFO_ENABLE BIT(24)
+#define LDB_CTRL_ASYNC_FIFO_THRESHOLD_MASK GENMASK(27, 25)
+
+#define LVDS_CTRL 0x128
+#define LVDS_CTRL_CH0_EN BIT(0)
+#define LVDS_CTRL_CH1_EN BIT(1)
+#define LVDS_CTRL_VBG_EN BIT(2)
+#define LVDS_CTRL_HS_EN BIT(3)
+#define LVDS_CTRL_PRE_EMPH_EN BIT(4)
+#define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5)
+#define LVDS_CTRL_PRE_EMPH_ADJ_MASK GENMASK(7, 5)
+#define LVDS_CTRL_CM_ADJ(n) (((n) & 0x7) << 8)
+#define LVDS_CTRL_CM_ADJ_MASK GENMASK(10, 8)
+#define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11)
+#define LVDS_CTRL_CC_ADJ_MASK GENMASK(13, 11)
+#define LVDS_CTRL_SLEW_ADJ(n) (((n) & 0x7) << 14)
+#define LVDS_CTRL_SLEW_ADJ_MASK GENMASK(16, 14)
+#define LVDS_CTRL_VBG_ADJ(n) (((n) & 0x7) << 17)
+#define LVDS_CTRL_VBG_ADJ_MASK GENMASK(19, 17)
+
+struct fsl_ldb {
+ struct device *dev;
+ struct drm_bridge bridge;
+ struct drm_bridge *panel_bridge;
+ struct clk *clk;
+ struct regmap *regmap;
+ bool lvds_dual_link;
+};
+
+static inline struct fsl_ldb *to_fsl_ldb(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct fsl_ldb, bridge);
+}
+
+static int fsl_ldb_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+
+ return drm_bridge_attach(bridge->encoder, fsl_ldb->panel_bridge,
+ bridge, flags);
+}
+
+static int fsl_ldb_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ /* Invert DE signal polarity. */
+ bridge_state->input_bus_cfg.flags &= ~(DRM_BUS_FLAG_DE_LOW |
+ DRM_BUS_FLAG_DE_HIGH);
+ if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_LOW)
+ bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_HIGH;
+ else if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_HIGH)
+ bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_LOW;
+
+ return 0;
+}
+
+static void fsl_ldb_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+ struct drm_atomic_state *state = old_bridge_state->base.state;
+ const struct drm_bridge_state *bridge_state;
+ const struct drm_crtc_state *crtc_state;
+ const struct drm_display_mode *mode;
+ struct drm_connector *connector;
+ struct drm_crtc *crtc;
+ bool lvds_format_24bpp;
+ bool lvds_format_jeida;
+ u32 reg;
+
+ /* Get the LVDS format from the bridge state. */
+ bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+
+ switch (bridge_state->output_bus_cfg.format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ lvds_format_24bpp = false;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ break;
+ default:
+ /*
+ * Some bridges still don't set the correct LVDS bus pixel
+ * format, use SPWG24 default format until those are fixed.
+ */
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ dev_warn(fsl_ldb->dev,
+ "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
+ bridge_state->output_bus_cfg.format);
+ break;
+ }
+
+ /*
+ * Retrieve the CRTC adjusted mode. This requires a little dance to go
+ * from the bridge to the encoder, to the connector and to the CRTC.
+ */
+ connector = drm_atomic_get_new_connector_for_encoder(state,
+ bridge->encoder);
+ crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+ crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ mode = &crtc_state->adjusted_mode;
+
+ if (fsl_ldb->lvds_dual_link)
+ clk_set_rate(fsl_ldb->clk, mode->clock * 3500);
+ else
+ clk_set_rate(fsl_ldb->clk, mode->clock * 7000);
+ clk_prepare_enable(fsl_ldb->clk);
+
+ /* Program LDB_CTRL */
+ reg = LDB_CTRL_CH0_ENABLE;
+
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_CH1_ENABLE;
+
+ if (lvds_format_24bpp) {
+ reg |= LDB_CTRL_CH0_DATA_WIDTH;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_CH1_DATA_WIDTH;
+ }
+
+ if (lvds_format_jeida) {
+ reg |= LDB_CTRL_CH0_BIT_MAPPING;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_CH1_BIT_MAPPING;
+ }
+
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC) {
+ reg |= LDB_CTRL_DI0_VSYNC_POLARITY;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_DI1_VSYNC_POLARITY;
+ }
+
+ regmap_write(fsl_ldb->regmap, LDB_CTRL, reg);
+
+ /* Program LVDS_CTRL */
+ reg = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN |
+ LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN;
+ regmap_write(fsl_ldb->regmap, LVDS_CTRL, reg);
+
+ /* Wait for VBG to stabilize. */
+ usleep_range(15, 20);
+
+ reg |= LVDS_CTRL_CH0_EN;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LVDS_CTRL_CH1_EN;
+
+ regmap_write(fsl_ldb->regmap, LVDS_CTRL, reg);
+}
+
+static void fsl_ldb_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+
+ /* Stop both channels. */
+ regmap_write(fsl_ldb->regmap, LVDS_CTRL, 0);
+ regmap_write(fsl_ldb->regmap, LDB_CTRL, 0);
+
+ clk_disable_unprepare(fsl_ldb->clk);
+}
+
+#define MAX_INPUT_SEL_FORMATS 1
+static u32 *
+fsl_ldb_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts)
+{
+ u32 *input_fmts;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = MAX_INPUT_SEL_FORMATS;
+
+ return input_fmts;
+}
+
+static enum drm_mode_status
+fsl_ldb_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+
+ if (mode->clock > (fsl_ldb->lvds_dual_link ? 80000 : 160000))
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static const struct drm_bridge_funcs funcs = {
+ .attach = fsl_ldb_attach,
+ .atomic_check = fsl_ldb_atomic_check,
+ .atomic_enable = fsl_ldb_atomic_enable,
+ .atomic_disable = fsl_ldb_atomic_disable,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_get_input_bus_fmts = fsl_ldb_atomic_get_input_bus_fmts,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .mode_valid = fsl_ldb_mode_valid,
+};
+
+static int fsl_ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *panel_node;
+ struct device_node *port1, *port2;
+ struct drm_panel *panel;
+ struct fsl_ldb *fsl_ldb;
+ int dual_link;
+
+ fsl_ldb = devm_kzalloc(dev, sizeof(*fsl_ldb), GFP_KERNEL);
+ if (!fsl_ldb)
+ return -ENOMEM;
+
+ fsl_ldb->dev = &pdev->dev;
+ fsl_ldb->bridge.funcs = &funcs;
+ fsl_ldb->bridge.of_node = dev->of_node;
+
+ fsl_ldb->clk = devm_clk_get(dev, "ldb");
+ if (IS_ERR(fsl_ldb->clk))
+ return PTR_ERR(fsl_ldb->clk);
+
+ fsl_ldb->regmap = syscon_node_to_regmap(dev->of_node->parent);
+ if (IS_ERR(fsl_ldb->regmap))
+ return PTR_ERR(fsl_ldb->regmap);
+
+ /* Locate the panel DT node. */
+ panel_node = of_graph_get_remote_node(dev->of_node, 1, 0);
+ if (!panel_node)
+ return -ENXIO;
+
+ panel = of_drm_find_panel(panel_node);
+ of_node_put(panel_node);
+ if (IS_ERR(panel))
+ return PTR_ERR(panel);
+
+ fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(fsl_ldb->panel_bridge))
+ return PTR_ERR(fsl_ldb->panel_bridge);
+
+ /* Determine whether this is dual-link configuration */
+ port1 = of_graph_get_port_by_id(dev->of_node, 1);
+ port2 = of_graph_get_port_by_id(dev->of_node, 2);
+ dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);
+ of_node_put(port1);
+ of_node_put(port2);
+
+ if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
+ dev_err(dev, "LVDS channel pixel swap not supported.\n");
+ return -EINVAL;
+ }
+
+ if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS)
+ fsl_ldb->lvds_dual_link = true;
+
+ platform_set_drvdata(pdev, fsl_ldb);
+
+ drm_bridge_add(&fsl_ldb->bridge);
+
+ return 0;
+}
+
+static int fsl_ldb_remove(struct platform_device *pdev)
+{
+ struct fsl_ldb *fsl_ldb = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&fsl_ldb->bridge);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_ldb_match[] = {
+ { .compatible = "fsl,imx8mp-ldb", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, fsl_ldb_match);
+
+static struct platform_driver fsl_ldb_driver = {
+ .probe = fsl_ldb_probe,
+ .remove = fsl_ldb_remove,
+ .driver = {
+ .name = "fsl-ldb",
+ .of_match_table = fsl_ldb_match,
+ },
+};
+module_platform_driver(fsl_ldb_driver);
+
+MODULE_AUTHOR("Marek Vasut <[email protected]>");
+MODULE_DESCRIPTION("Freescale i.MX8MP LDB");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c
index 8fed30df08b0..4b673c4792d7 100644
--- a/drivers/gpu/drm/bridge/ite-it6505.c
+++ b/drivers/gpu/drm/bridge/ite-it6505.c
@@ -737,8 +737,9 @@ static int it6505_drm_dp_link_probe(struct drm_dp_aux *aux,
return 0;
}
-static int it6505_drm_dp_link_power_up(struct drm_dp_aux *aux,
- struct it6505_drm_dp_link *link)
+static int it6505_drm_dp_link_set_power(struct drm_dp_aux *aux,
+ struct it6505_drm_dp_link *link,
+ u8 mode)
{
u8 value;
int err;
@@ -752,18 +753,20 @@ static int it6505_drm_dp_link_power_up(struct drm_dp_aux *aux,
return err;
value &= ~DP_SET_POWER_MASK;
- value |= DP_SET_POWER_D0;
+ value |= mode;
err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
if (err < 0)
return err;
- /*
- * According to the DP 1.1 specification, a "Sink Device must exit the
- * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
- * Control Field" (register 0x600).
- */
- usleep_range(1000, 2000);
+ if (mode == DP_SET_POWER_D0) {
+ /*
+ * According to the DP 1.1 specification, a "Sink Device must
+ * exit the power saving state within 1 ms" (Section 2.5.3.1,
+ * Table 5-52, "Sink Control Field" (register 0x600).
+ */
+ usleep_range(1000, 2000);
+ }
return 0;
}
@@ -2624,7 +2627,8 @@ static enum drm_connector_status it6505_detect(struct it6505 *it6505)
if (it6505_get_sink_hpd_status(it6505)) {
it6505_aux_on(it6505);
it6505_drm_dp_link_probe(&it6505->aux, &it6505->link);
- it6505_drm_dp_link_power_up(&it6505->aux, &it6505->link);
+ it6505_drm_dp_link_set_power(&it6505->aux, &it6505->link,
+ DP_SET_POWER_D0);
it6505->auto_train_retry = AUTO_TRAIN_RETRY;
if (it6505->dpcd[0] == 0) {
@@ -2960,8 +2964,11 @@ static void it6505_bridge_atomic_disable(struct drm_bridge *bridge,
DRM_DEV_DEBUG_DRIVER(dev, "start");
- if (it6505->powered)
+ if (it6505->powered) {
it6505_video_disable(it6505);
+ it6505_drm_dp_link_set_power(&it6505->aux, &it6505->link,
+ DP_SET_POWER_D3);
+ }
}
static enum drm_connector_status
diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
index b20b38661002..485717c8f0b4 100644
--- a/drivers/gpu/drm/bridge/tc358767.c
+++ b/drivers/gpu/drm/bridge/tc358767.c
@@ -1955,7 +1955,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc)
tc->bridge.ops |= DRM_BRIDGE_OP_DETECT;
tc->bridge.ops |= DRM_BRIDGE_OP_EDID;
- return ret;
+ return 0;
}
static int tc_probe_bridge_endpoint(struct tc_data *tc)
@@ -1964,7 +1964,9 @@ static int tc_probe_bridge_endpoint(struct tc_data *tc)
struct of_endpoint endpoint;
struct device_node *node = NULL;
const u8 mode_dpi_to_edp = BIT(1) | BIT(2);
+ const u8 mode_dpi_to_dp = BIT(1);
const u8 mode_dsi_to_edp = BIT(0) | BIT(2);
+ const u8 mode_dsi_to_dp = BIT(0);
const u8 mode_dsi_to_dpi = BIT(0) | BIT(1);
u8 mode = 0;
@@ -1990,11 +1992,11 @@ static int tc_probe_bridge_endpoint(struct tc_data *tc)
mode |= BIT(endpoint.port);
}
- if (mode == mode_dpi_to_edp)
+ if (mode == mode_dpi_to_edp || mode == mode_dpi_to_dp)
return tc_probe_edp_bridge_endpoint(tc);
else if (mode == mode_dsi_to_dpi)
return tc_probe_dpi_bridge_endpoint(tc);
- else if (mode == mode_dsi_to_edp)
+ else if (mode == mode_dsi_to_edp || mode == mode_dsi_to_dp)
dev_warn(dev, "The mode DSI-to-(e)DP is not supported!\n");
else
dev_warn(dev, "Invalid mode (0x%x) is not supported!\n", mode);
diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index ba3fa2a9b8a4..756b3e6e776b 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -341,13 +341,11 @@ static int tfp410_init(struct device *dev, bool i2c)
return 0;
}
-static int tfp410_fini(struct device *dev)
+static void tfp410_fini(struct device *dev)
{
struct tfp410 *dvi = dev_get_drvdata(dev);
drm_bridge_remove(&dvi->bridge);
-
- return 0;
}
static int tfp410_probe(struct platform_device *pdev)
@@ -357,7 +355,9 @@ static int tfp410_probe(struct platform_device *pdev)
static int tfp410_remove(struct platform_device *pdev)
{
- return tfp410_fini(&pdev->dev);
+ tfp410_fini(&pdev->dev);
+
+ return 0;
}
static const struct of_device_id tfp410_match[] = {
@@ -394,7 +394,9 @@ static int tfp410_i2c_probe(struct i2c_client *client,
static int tfp410_i2c_remove(struct i2c_client *client)
{
- return tfp410_fini(&client->dev);
+ tfp410_fini(&client->dev);
+
+ return 0;
}
static const struct i2c_device_id tfp410_i2c_ids[] = {
diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig
index f84f1b0cd23f..1b6e6af37546 100644
--- a/drivers/gpu/drm/display/Kconfig
+++ b/drivers/gpu/drm/display/Kconfig
@@ -31,7 +31,8 @@ config DRM_DISPLAY_HDMI_HELPER
config DRM_DP_AUX_CHARDEV
bool "DRM DP AUX Interface"
- depends on DRM
+ depends on DRM && DRM_DISPLAY_HELPER
+ select DRM_DISPLAY_DP_HELPER
help
Choose this option to enable a /dev/drm_dp_auxN node that allows to
read and write values to arbitrary DPCD registers on the DP aux
@@ -39,7 +40,8 @@ config DRM_DP_AUX_CHARDEV
config DRM_DP_CEC
bool "Enable DisplayPort CEC-Tunneling-over-AUX HDMI support"
- depends on DRM
+ depends on DRM && DRM_DISPLAY_HELPER
+ select DRM_DISPLAY_DP_HELPER
select CEC_CORE
help
Choose this option if you want to enable HDMI CEC support for
diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c
index 8526aae75c6d..9aa2c20904e3 100644
--- a/drivers/gpu/drm/display/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c
@@ -3557,10 +3557,9 @@ static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr,
if (ret < 0)
goto fail_free;
- /* DPCD read should never be NACKed */
if (txmsg->reply.reply_type == 1) {
- drm_err(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n",
- mstb, port->port_num, offset, size);
+ drm_dbg_kms(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n",
+ mstb, port->port_num, offset, size);
ret = -EIO;
goto fail_free;
}
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index c6394ba13b24..434f3d4cb8a2 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -255,43 +255,6 @@ drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state,
EXPORT_SYMBOL(drm_atomic_set_fb_for_plane);
/**
- * drm_atomic_set_fence_for_plane - set fence for plane
- * @plane_state: atomic state object for the plane
- * @fence: dma_fence to use for the plane
- *
- * Helper to setup the plane_state fence in case it is not set yet.
- * By using this drivers doesn't need to worry if the user choose
- * implicit or explicit fencing.
- *
- * This function will not set the fence to the state if it was set
- * via explicit fencing interfaces on the atomic ioctl. In that case it will
- * drop the reference to the fence as we are not storing it anywhere.
- * Otherwise, if &drm_plane_state.fence is not set this function we just set it
- * with the received implicit fence. In both cases this function consumes a
- * reference for @fence.
- *
- * This way explicit fencing can be used to overrule implicit fencing, which is
- * important to make explicit fencing use-cases work: One example is using one
- * buffer for 2 screens with different refresh rates. Implicit fencing will
- * clamp rendering to the refresh rate of the slower screen, whereas explicit
- * fence allows 2 independent render and display loops on a single buffer. If a
- * driver allows obeys both implicit and explicit fences for plane updates, then
- * it will break all the benefits of explicit fencing.
- */
-void
-drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
- struct dma_fence *fence)
-{
- if (plane_state->fence) {
- dma_fence_put(fence);
- return;
- }
-
- plane_state->fence = fence;
-}
-EXPORT_SYMBOL(drm_atomic_set_fence_for_plane);
-
-/**
* drm_atomic_set_crtc_for_connector - set CRTC for connector
* @conn_state: atomic state object for the connector
* @crtc: CRTC to use for the connector
@@ -1077,10 +1040,10 @@ int drm_atomic_set_property(struct drm_atomic_state *state,
*
* As a contrast, with implicit fencing the kernel keeps track of any
* ongoing rendering, and automatically ensures that the atomic update waits
- * for any pending rendering to complete. For shared buffers represented with
- * a &struct dma_buf this is tracked in &struct dma_resv.
- * Implicit syncing is how Linux traditionally worked (e.g. DRI2/3 on X.org),
- * whereas explicit fencing is what Android wants.
+ * for any pending rendering to complete. This is usually tracked in &struct
+ * dma_resv which can also contain mandatory kernel fences. Implicit syncing
+ * is how Linux traditionally worked (e.g. DRI2/3 on X.org), whereas explicit
+ * fencing is what Android wants.
*
* "IN_FENCE_FD”:
* Use this property to pass a fence that DRM should wait on before
@@ -1095,7 +1058,7 @@ int drm_atomic_set_property(struct drm_atomic_state *state,
*
* On the driver side the fence is stored on the @fence parameter of
* &struct drm_plane_state. Drivers which also support implicit fencing
- * should set the implicit fence using drm_atomic_set_fence_for_plane(),
+ * should extract the implicit fence using drm_gem_plane_helper_prepare_fb(),
* to make sure there's consistent behaviour between drivers in precedence
* of implicit vs. explicit fencing.
*
diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c
index 9079fbe21d2f..17c6c3eefcd6 100644
--- a/drivers/gpu/drm/drm_color_mgmt.c
+++ b/drivers/gpu/drm/drm_color_mgmt.c
@@ -259,7 +259,7 @@ static bool drm_crtc_supports_legacy_gamma(struct drm_crtc *crtc)
* @crtc: CRTC object
* @red: red correction table
* @green: green correction table
- * @blue: green correction table
+ * @blue: blue correction table
* @size: size of the tables
* @ctx: lock acquire context
*
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 7a8482b75071..bc43e1b32092 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -1610,7 +1610,7 @@ static void edid_header_fix(void *edid)
/**
* drm_edid_header_is_valid - sanity check the header of the base EDID block
- * @raw_edid: pointer to raw base EDID block
+ * @_edid: pointer to raw base EDID block
*
* Sanity check the header of the base EDID block.
*
@@ -1827,7 +1827,7 @@ static void edid_block_dump(const char *level, const void *block, int block_num)
/**
* drm_edid_block_valid - Sanity check the EDID block (base or extension)
- * @raw_edid: pointer to raw EDID block
+ * @_block: pointer to raw EDID block
* @block_num: type of block to validate (0 for base, extension otherwise)
* @print_bad_edid: if true, dump bad EDID blocks to the console
* @edid_corrupt: if true, the header or checksum is invalid
@@ -2112,8 +2112,8 @@ static enum edid_block_status edid_block_read(void *block, unsigned int block_nu
/**
* drm_do_get_edid - get EDID data using a custom EDID block read function
* @connector: connector we're probing
- * @get_edid_block: EDID block read function
- * @data: private data passed to the block read function
+ * @read_block: EDID block read function
+ * @context: private data passed to the block read function
*
* When the I2C adapter connected to the DDC bus is hidden behind a device that
* exposes a different interface to read EDID blocks this function can be used
@@ -2384,13 +2384,9 @@ static u32 edid_get_quirks(const struct edid *edid)
#define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay)
#define MODE_REFRESH_DIFF(c,t) (abs((c) - (t)))
-/**
- * edid_fixup_preferred - set preferred modes based on quirk list
- * @connector: has mode list to fix up
- * @quirks: quirks list
- *
- * Walk the mode list for @connector, clearing the preferred status
- * on existing modes and setting it anew for the right mode ala @quirks.
+/*
+ * Walk the mode list for connector, clearing the preferred status on existing
+ * modes and setting it anew for the right mode ala quirks.
*/
static void edid_fixup_preferred(struct drm_connector *connector,
u32 quirks)
@@ -2659,10 +2655,7 @@ drm_gtf2_2j(const struct edid *edid)
return descriptor ? descriptor->data.other_data.data.range.formula.gtf2.j : 0;
}
-/**
- * standard_timing_level - get std. timing level(CVT/GTF/DMT)
- * @edid: EDID block to scan
- */
+/* Get standard timing level (CVT/GTF/DMT). */
static int standard_timing_level(const struct edid *edid)
{
if (edid->revision >= 2) {
@@ -2696,12 +2689,7 @@ static int drm_mode_hsync(const struct drm_display_mode *mode)
return DIV_ROUND_CLOSEST(mode->clock, mode->htotal);
}
-/**
- * drm_mode_std - convert standard mode info (width, height, refresh) into mode
- * @connector: connector of for the EDID block
- * @edid: EDID block to scan
- * @t: standard timing params
- *
+/*
* Take the standard timing params (in this case width, aspect, and refresh)
* and convert them into a real mode using CVT/GTF/DMT.
*/
@@ -2857,15 +2845,10 @@ drm_mode_do_interlace_quirk(struct drm_display_mode *mode,
mode->flags |= DRM_MODE_FLAG_INTERLACE;
}
-/**
- * drm_mode_detailed - create a new mode from an EDID detailed timing section
- * @dev: DRM device (needed to create new mode)
- * @edid: EDID block
- * @timing: EDID detailed timing info
- * @quirks: quirks to apply
- *
- * An EDID detailed timing block contains enough info for us to create and
- * return a new struct drm_display_mode.
+/*
+ * Create a new mode from an EDID detailed timing section. An EDID detailed
+ * timing block contains enough info for us to create and return a new struct
+ * drm_display_mode.
*/
static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev,
const struct edid *edid,
@@ -3247,13 +3230,10 @@ do_established_modes(const struct detailed_timing *timing, void *c)
closure->modes += drm_est3_modes(closure->connector, timing);
}
-/**
- * add_established_modes - get est. modes from EDID and add them
- * @connector: connector to add mode(s) to
- * @edid: EDID block to scan
- *
- * Each EDID block contains a bitmap of the supported "established modes" list
- * (defined above). Tease them out and add them to the global modes list.
+/*
+ * Get established modes from EDID and add them. Each EDID block contains a
+ * bitmap of the supported "established modes" list (defined above). Tease them
+ * out and add them to the global modes list.
*/
static int
add_established_modes(struct drm_connector *connector, const struct edid *edid)
@@ -3311,13 +3291,10 @@ do_standard_modes(const struct detailed_timing *timing, void *c)
}
}
-/**
- * add_standard_modes - get std. modes from EDID and add them
- * @connector: connector to add mode(s) to
- * @edid: EDID block to scan
- *
- * Standard modes can be calculated using the appropriate standard (DMT,
- * GTF or CVT. Grab them from @edid and add them to the list.
+/*
+ * Get standard modes from EDID and add them. Standard modes can be calculated
+ * using the appropriate standard (DMT, GTF, or CVT). Grab them from EDID and
+ * add them to the list.
*/
static int
add_standard_modes(struct drm_connector *connector, const struct edid *edid)
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index d265a73313c9..5ad2b6a2778c 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -708,22 +708,21 @@ static void drm_fb_helper_memory_range_to_clip(struct fb_info *info, off_t off,
/**
* drm_fb_helper_deferred_io() - fbdev deferred_io callback function
* @info: fb_info struct pointer
- * @pagelist: list of mmap framebuffer pages that have to be flushed
+ * @pagereflist: list of mmap framebuffer pages that have to be flushed
*
* This function is used as the &fb_deferred_io.deferred_io
* callback function for flushing the fbdev mmap writes.
*/
-void drm_fb_helper_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
unsigned long start, end, min, max;
- struct page *page;
+ struct fb_deferred_io_pageref *pageref;
struct drm_rect damage_area;
min = ULONG_MAX;
max = 0;
- list_for_each_entry(page, pagelist, lru) {
- start = page->index << PAGE_SHIFT;
+ list_for_each_entry(pageref, pagereflist, list) {
+ start = pageref->offset;
end = start + PAGE_SIZE;
min = min(min, start);
max = max(max, end);
@@ -2118,7 +2117,9 @@ static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct drm_fb_helper *fb_helper = info->par;
- if (fb_helper->dev->driver->gem_prime_mmap)
+ if (drm_fbdev_use_shadow_fb(fb_helper))
+ return fb_deferred_io_mmap(info, vma);
+ else if (fb_helper->dev->driver->gem_prime_mmap)
return fb_helper->dev->driver->gem_prime_mmap(fb_helper->buffer->gem, vma);
else
return -ENODEV;
diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c
index 34b7ef443ad2..a3ccd8bc966f 100644
--- a/drivers/gpu/drm/drm_format_helper.c
+++ b/drivers/gpu/drm/drm_format_helper.c
@@ -40,6 +40,94 @@ unsigned int drm_fb_clip_offset(unsigned int pitch, const struct drm_format_info
}
EXPORT_SYMBOL(drm_fb_clip_offset);
+/* TODO: Make this functon work with multi-plane formats. */
+static int drm_fb_xfrm(void *dst, unsigned long dst_pitch, unsigned long dst_pixsize,
+ const void *vaddr, const struct drm_framebuffer *fb,
+ const struct drm_rect *clip, bool vaddr_cached_hint,
+ void (*xfrm_line)(void *dbuf, const void *sbuf, unsigned int npixels))
+{
+ unsigned long linepixels = drm_rect_width(clip);
+ unsigned long lines = drm_rect_height(clip);
+ size_t sbuf_len = linepixels * fb->format->cpp[0];
+ void *stmp = NULL;
+ unsigned long i;
+ const void *sbuf;
+
+ /*
+ * Some source buffers, such as CMA memory, use write-combine
+ * caching, so reads are uncached. Speed up access by fetching
+ * one line at a time.
+ */
+ if (!vaddr_cached_hint) {
+ stmp = kmalloc(sbuf_len, GFP_KERNEL);
+ if (!stmp)
+ return -ENOMEM;
+ }
+
+ if (!dst_pitch)
+ dst_pitch = drm_rect_width(clip) * dst_pixsize;
+ vaddr += clip_offset(clip, fb->pitches[0], fb->format->cpp[0]);
+
+ for (i = 0; i < lines; ++i) {
+ if (stmp)
+ sbuf = memcpy(stmp, vaddr, sbuf_len);
+ else
+ sbuf = vaddr;
+ xfrm_line(dst, sbuf, linepixels);
+ vaddr += fb->pitches[0];
+ dst += dst_pitch;
+ }
+
+ kfree(stmp);
+
+ return 0;
+}
+
+/* TODO: Make this functon work with multi-plane formats. */
+static int drm_fb_xfrm_toio(void __iomem *dst, unsigned long dst_pitch, unsigned long dst_pixsize,
+ const void *vaddr, const struct drm_framebuffer *fb,
+ const struct drm_rect *clip, bool vaddr_cached_hint,
+ void (*xfrm_line)(void *dbuf, const void *sbuf, unsigned int npixels))
+{
+ unsigned long linepixels = drm_rect_width(clip);
+ unsigned long lines = drm_rect_height(clip);
+ size_t dbuf_len = linepixels * dst_pixsize;
+ size_t stmp_off = round_up(dbuf_len, ARCH_KMALLOC_MINALIGN); /* for sbuf alignment */
+ size_t sbuf_len = linepixels * fb->format->cpp[0];
+ void *stmp = NULL;
+ unsigned long i;
+ const void *sbuf;
+ void *dbuf;
+
+ if (vaddr_cached_hint) {
+ dbuf = kmalloc(dbuf_len, GFP_KERNEL);
+ } else {
+ dbuf = kmalloc(stmp_off + sbuf_len, GFP_KERNEL);
+ stmp = dbuf + stmp_off;
+ }
+ if (!dbuf)
+ return -ENOMEM;
+
+ if (!dst_pitch)
+ dst_pitch = linepixels * dst_pixsize;
+ vaddr += clip_offset(clip, fb->pitches[0], fb->format->cpp[0]);
+
+ for (i = 0; i < lines; ++i) {
+ if (stmp)
+ sbuf = memcpy(stmp, vaddr, sbuf_len);
+ else
+ sbuf = vaddr;
+ xfrm_line(dbuf, sbuf, linepixels);
+ memcpy_toio(dst, dbuf, dbuf_len);
+ vaddr += fb->pitches[0];
+ dst += dst_pitch;
+ }
+
+ kfree(dbuf);
+
+ return 0;
+}
+
/**
* drm_fb_memcpy - Copy clip buffer
* @dst: Destination buffer
@@ -100,6 +188,26 @@ void drm_fb_memcpy_toio(void __iomem *dst, unsigned int dst_pitch, const void *v
}
EXPORT_SYMBOL(drm_fb_memcpy_toio);
+static void drm_fb_swab16_line(void *dbuf, const void *sbuf, unsigned int pixels)
+{
+ u16 *dbuf16 = dbuf;
+ const u16 *sbuf16 = sbuf;
+ const u16 *send16 = sbuf16 + pixels;
+
+ while (sbuf16 < send16)
+ *dbuf16++ = swab16(*sbuf16++);
+}
+
+static void drm_fb_swab32_line(void *dbuf, const void *sbuf, unsigned int pixels)
+{
+ u32 *dbuf32 = dbuf;
+ const u32 *sbuf32 = sbuf;
+ const u32 *send32 = sbuf32 + pixels;
+
+ while (sbuf32 < send32)
+ *dbuf32++ = swab32(*sbuf32++);
+}
+
/**
* drm_fb_swab - Swap bytes into clip buffer
* @dst: Destination buffer
@@ -120,63 +228,34 @@ void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src,
bool cached)
{
u8 cpp = fb->format->cpp[0];
- size_t len = drm_rect_width(clip) * cpp;
- const u16 *src16;
- const u32 *src32;
- u16 *dst16;
- u32 *dst32;
- unsigned int x, y;
- void *buf = NULL;
-
- if (WARN_ON_ONCE(cpp != 2 && cpp != 4))
- return;
- if (!dst_pitch)
- dst_pitch = len;
-
- if (!cached)
- buf = kmalloc(len, GFP_KERNEL);
-
- src += clip_offset(clip, fb->pitches[0], cpp);
-
- for (y = clip->y1; y < clip->y2; y++) {
- if (buf) {
- memcpy(buf, src, len);
- src16 = buf;
- src32 = buf;
- } else {
- src16 = src;
- src32 = src;
- }
-
- dst16 = dst;
- dst32 = dst;
-
- for (x = clip->x1; x < clip->x2; x++) {
- if (cpp == 4)
- *dst32++ = swab32(*src32++);
- else
- *dst16++ = swab16(*src16++);
- }
-
- src += fb->pitches[0];
- dst += dst_pitch;
+ switch (cpp) {
+ case 4:
+ drm_fb_xfrm(dst, dst_pitch, cpp, src, fb, clip, cached, drm_fb_swab32_line);
+ break;
+ case 2:
+ drm_fb_xfrm(dst, dst_pitch, cpp, src, fb, clip, cached, drm_fb_swab16_line);
+ break;
+ default:
+ drm_warn_once(fb->dev, "Format %p4cc has unsupported pixel size.\n",
+ &fb->format->format);
+ break;
}
-
- kfree(buf);
}
EXPORT_SYMBOL(drm_fb_swab);
-static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, const __le32 *sbuf, unsigned int pixels)
+static void drm_fb_xrgb8888_to_rgb332_line(void *dbuf, const void *sbuf, unsigned int pixels)
{
+ u8 *dbuf8 = dbuf;
+ const __le32 *sbuf32 = sbuf;
unsigned int x;
u32 pix;
for (x = 0; x < pixels; x++) {
- pix = le32_to_cpu(sbuf[x]);
- dbuf[x] = ((pix & 0x00e00000) >> 16) |
- ((pix & 0x0000e000) >> 11) |
- ((pix & 0x000000c0) >> 6);
+ pix = le32_to_cpu(sbuf32[x]);
+ dbuf8[x] = ((pix & 0x00e00000) >> 16) |
+ ((pix & 0x0000e000) >> 11) |
+ ((pix & 0x000000c0) >> 6);
}
}
@@ -193,46 +272,38 @@ static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, const __le32 *sbuf, unsigne
void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *src,
const struct drm_framebuffer *fb, const struct drm_rect *clip)
{
- size_t width = drm_rect_width(clip);
- size_t src_len = width * sizeof(u32);
- unsigned int y;
- void *sbuf;
-
- if (!dst_pitch)
- dst_pitch = width;
+ drm_fb_xfrm(dst, dst_pitch, 1, src, fb, clip, false, drm_fb_xrgb8888_to_rgb332_line);
+}
+EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332);
- /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
- sbuf = kmalloc(src_len, GFP_KERNEL);
- if (!sbuf)
- return;
+static void drm_fb_xrgb8888_to_rgb565_line(void *dbuf, const void *sbuf, unsigned int pixels)
+{
+ u16 *dbuf16 = dbuf;
+ const u32 *sbuf32 = sbuf;
+ unsigned int x;
+ u16 val16;
- src += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = 0; y < drm_rect_height(clip); y++) {
- memcpy(sbuf, src, src_len);
- drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width);
- src += fb->pitches[0];
- dst += dst_pitch;
+ for (x = 0; x < pixels; x++) {
+ val16 = ((sbuf32[x] & 0x00F80000) >> 8) |
+ ((sbuf32[x] & 0x0000FC00) >> 5) |
+ ((sbuf32[x] & 0x000000F8) >> 3);
+ dbuf16[x] = val16;
}
-
- kfree(sbuf);
}
-EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332);
-static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, const u32 *sbuf,
- unsigned int pixels,
- bool swab)
+static void drm_fb_xrgb8888_to_rgb565_swab_line(void *dbuf, const void *sbuf,
+ unsigned int pixels)
{
+ u16 *dbuf16 = dbuf;
+ const u32 *sbuf32 = sbuf;
unsigned int x;
u16 val16;
for (x = 0; x < pixels; x++) {
- val16 = ((sbuf[x] & 0x00F80000) >> 8) |
- ((sbuf[x] & 0x0000FC00) >> 5) |
- ((sbuf[x] & 0x000000F8) >> 3);
- if (swab)
- dbuf[x] = swab16(val16);
- else
- dbuf[x] = val16;
+ val16 = ((sbuf32[x] & 0x00F80000) >> 8) |
+ ((sbuf32[x] & 0x0000FC00) >> 5) |
+ ((sbuf32[x] & 0x000000F8) >> 3);
+ dbuf16[x] = swab16(val16);
}
}
@@ -252,32 +323,12 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, unsigned int dst_pitch, const void *va
const struct drm_framebuffer *fb, const struct drm_rect *clip,
bool swab)
{
- size_t linepixels = clip->x2 - clip->x1;
- size_t src_len = linepixels * sizeof(u32);
- size_t dst_len = linepixels * sizeof(u16);
- unsigned y, lines = clip->y2 - clip->y1;
- void *sbuf;
-
- if (!dst_pitch)
- dst_pitch = dst_len;
-
- /*
- * The cma memory is write-combined so reads are uncached.
- * Speed up by fetching one line at a time.
- */
- sbuf = kmalloc(src_len, GFP_KERNEL);
- if (!sbuf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = 0; y < lines; y++) {
- memcpy(sbuf, vaddr, src_len);
- drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(sbuf);
+ if (swab)
+ drm_fb_xfrm(dst, dst_pitch, 2, vaddr, fb, clip, false,
+ drm_fb_xrgb8888_to_rgb565_swab_line);
+ else
+ drm_fb_xfrm(dst, dst_pitch, 2, vaddr, fb, clip, false,
+ drm_fb_xrgb8888_to_rgb565_line);
}
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565);
@@ -297,39 +348,25 @@ void drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch,
const void *vaddr, const struct drm_framebuffer *fb,
const struct drm_rect *clip, bool swab)
{
- size_t linepixels = clip->x2 - clip->x1;
- size_t dst_len = linepixels * sizeof(u16);
- unsigned y, lines = clip->y2 - clip->y1;
- void *dbuf;
-
- if (!dst_pitch)
- dst_pitch = dst_len;
-
- dbuf = kmalloc(dst_len, GFP_KERNEL);
- if (!dbuf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = 0; y < lines; y++) {
- drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab);
- memcpy_toio(dst, dbuf, dst_len);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(dbuf);
+ if (swab)
+ drm_fb_xfrm_toio(dst, dst_pitch, 2, vaddr, fb, clip, false,
+ drm_fb_xrgb8888_to_rgb565_swab_line);
+ else
+ drm_fb_xfrm_toio(dst, dst_pitch, 2, vaddr, fb, clip, false,
+ drm_fb_xrgb8888_to_rgb565_line);
}
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_toio);
-static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, const u32 *sbuf,
- unsigned int pixels)
+static void drm_fb_xrgb8888_to_rgb888_line(void *dbuf, const void *sbuf, unsigned int pixels)
{
+ u8 *dbuf8 = dbuf;
+ const u32 *sbuf32 = sbuf;
unsigned int x;
for (x = 0; x < pixels; x++) {
- *dbuf++ = (sbuf[x] & 0x000000FF) >> 0;
- *dbuf++ = (sbuf[x] & 0x0000FF00) >> 8;
- *dbuf++ = (sbuf[x] & 0x00FF0000) >> 16;
+ *dbuf8++ = (sbuf32[x] & 0x000000FF) >> 0;
+ *dbuf8++ = (sbuf32[x] & 0x0000FF00) >> 8;
+ *dbuf8++ = (sbuf32[x] & 0x00FF0000) >> 16;
}
}
@@ -347,28 +384,7 @@ static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, const u32 *sbuf,
void drm_fb_xrgb8888_to_rgb888(void *dst, unsigned int dst_pitch, const void *src,
const struct drm_framebuffer *fb, const struct drm_rect *clip)
{
- size_t width = drm_rect_width(clip);
- size_t src_len = width * sizeof(u32);
- unsigned int y;
- void *sbuf;
-
- if (!dst_pitch)
- dst_pitch = width * 3;
-
- /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
- sbuf = kmalloc(src_len, GFP_KERNEL);
- if (!sbuf)
- return;
-
- src += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = 0; y < drm_rect_height(clip); y++) {
- memcpy(sbuf, src, src_len);
- drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width);
- src += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(sbuf);
+ drm_fb_xfrm(dst, dst_pitch, 3, src, fb, clip, false, drm_fb_xrgb8888_to_rgb888_line);
}
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888);
@@ -387,27 +403,8 @@ void drm_fb_xrgb8888_to_rgb888_toio(void __iomem *dst, unsigned int dst_pitch,
const void *vaddr, const struct drm_framebuffer *fb,
const struct drm_rect *clip)
{
- size_t linepixels = clip->x2 - clip->x1;
- size_t dst_len = linepixels * 3;
- unsigned y, lines = clip->y2 - clip->y1;
- void *dbuf;
-
- if (!dst_pitch)
- dst_pitch = dst_len;
-
- dbuf = kmalloc(dst_len, GFP_KERNEL);
- if (!dbuf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = 0; y < lines; y++) {
- drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels);
- memcpy_toio(dst, dbuf, dst_len);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(dbuf);
+ drm_fb_xfrm_toio(dst, dst_pitch, 3, vaddr, fb, clip, false,
+ drm_fb_xrgb8888_to_rgb888_line);
}
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_toio);
@@ -431,27 +428,8 @@ static void drm_fb_rgb565_to_xrgb8888_toio(void __iomem *dst, unsigned int dst_p
const void *vaddr, const struct drm_framebuffer *fb,
const struct drm_rect *clip)
{
- size_t linepixels = drm_rect_width(clip);
- size_t dst_len = linepixels * 4;
- unsigned int y, lines = drm_rect_height(clip);
- void *dbuf;
-
- if (!dst_pitch)
- dst_pitch = dst_len;
-
- dbuf = kmalloc(dst_len, GFP_KERNEL);
- if (!dbuf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], 2);
- for (y = 0; y < lines; y++) {
- drm_fb_rgb565_to_xrgb8888_line(dbuf, vaddr, linepixels);
- memcpy_toio(dst, dbuf, dst_len);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(dbuf);
+ drm_fb_xfrm_toio(dst, dst_pitch, 4, vaddr, fb, clip, false,
+ drm_fb_rgb565_to_xrgb8888_line);
}
static void drm_fb_rgb888_to_xrgb8888_line(void *dbuf, const void *sbuf, unsigned int pixels)
@@ -472,40 +450,22 @@ static void drm_fb_rgb888_to_xrgb8888_toio(void __iomem *dst, unsigned int dst_p
const void *vaddr, const struct drm_framebuffer *fb,
const struct drm_rect *clip)
{
- size_t linepixels = drm_rect_width(clip);
- size_t dst_len = linepixels * 4;
- unsigned int y, lines = drm_rect_height(clip);
- void *dbuf;
-
- if (!dst_pitch)
- dst_pitch = dst_len;
-
- dbuf = kmalloc(dst_len, GFP_KERNEL);
- if (!dbuf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], 3);
- for (y = 0; y < lines; y++) {
- drm_fb_rgb888_to_xrgb8888_line(dbuf, vaddr, linepixels);
- memcpy_toio(dst, dbuf, dst_len);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(dbuf);
+ drm_fb_xfrm_toio(dst, dst_pitch, 4, vaddr, fb, clip, false,
+ drm_fb_rgb888_to_xrgb8888_line);
}
-static void drm_fb_xrgb8888_to_xrgb2101010_line(u32 *dbuf, const u32 *sbuf,
- unsigned int pixels)
+static void drm_fb_xrgb8888_to_xrgb2101010_line(void *dbuf, const void *sbuf, unsigned int pixels)
{
+ u32 *dbuf32 = dbuf;
+ const u32 *sbuf32 = sbuf;
unsigned int x;
u32 val32;
for (x = 0; x < pixels; x++) {
- val32 = ((sbuf[x] & 0x000000FF) << 2) |
- ((sbuf[x] & 0x0000FF00) << 4) |
- ((sbuf[x] & 0x00FF0000) << 6);
- *dbuf++ = val32 | ((val32 >> 8) & 0x00300C03);
+ val32 = ((sbuf32[x] & 0x000000FF) << 2) |
+ ((sbuf32[x] & 0x0000FF00) << 4) |
+ ((sbuf32[x] & 0x00FF0000) << 6);
+ *dbuf32++ = val32 | ((val32 >> 8) & 0x00300C03);
}
}
@@ -526,42 +486,25 @@ void drm_fb_xrgb8888_to_xrgb2101010_toio(void __iomem *dst,
const struct drm_framebuffer *fb,
const struct drm_rect *clip)
{
- size_t linepixels = clip->x2 - clip->x1;
- size_t dst_len = linepixels * sizeof(u32);
- unsigned int y, lines = clip->y2 - clip->y1;
- void *dbuf;
-
- if (!dst_pitch)
- dst_pitch = dst_len;
-
- dbuf = kmalloc(dst_len, GFP_KERNEL);
- if (!dbuf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = 0; y < lines; y++) {
- drm_fb_xrgb8888_to_xrgb2101010_line(dbuf, vaddr, linepixels);
- memcpy_toio(dst, dbuf, dst_len);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(dbuf);
+ drm_fb_xfrm_toio(dst, dst_pitch, 4, vaddr, fb, clip, false,
+ drm_fb_xrgb8888_to_xrgb2101010_line);
}
EXPORT_SYMBOL(drm_fb_xrgb8888_to_xrgb2101010_toio);
-static void drm_fb_xrgb8888_to_gray8_line(u8 *dst, const u32 *src, unsigned int pixels)
+static void drm_fb_xrgb8888_to_gray8_line(void *dbuf, const void *sbuf, unsigned int pixels)
{
+ u8 *dbuf8 = dbuf;
+ const u32 *sbuf32 = sbuf;
unsigned int x;
for (x = 0; x < pixels; x++) {
- u8 r = (*src & 0x00ff0000) >> 16;
- u8 g = (*src & 0x0000ff00) >> 8;
- u8 b = *src & 0x000000ff;
+ u8 r = (*sbuf32 & 0x00ff0000) >> 16;
+ u8 g = (*sbuf32 & 0x0000ff00) >> 8;
+ u8 b = *sbuf32 & 0x000000ff;
/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
- *dst++ = (3 * r + 6 * g + b) / 10;
- src++;
+ *dbuf8++ = (3 * r + 6 * g + b) / 10;
+ sbuf32++;
}
}
@@ -585,37 +528,7 @@ static void drm_fb_xrgb8888_to_gray8_line(u8 *dst, const u32 *src, unsigned int
void drm_fb_xrgb8888_to_gray8(void *dst, unsigned int dst_pitch, const void *vaddr,
const struct drm_framebuffer *fb, const struct drm_rect *clip)
{
- unsigned int linepixels = clip->x2 - clip->x1;
- unsigned int len = linepixels * sizeof(u32);
- unsigned int y;
- void *buf;
- u8 *dst8;
- u32 *src32;
-
- if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888))
- return;
-
- if (!dst_pitch)
- dst_pitch = drm_rect_width(clip);
-
- /*
- * The cma memory is write-combined so reads are uncached.
- * Speed up by fetching one line at a time.
- */
- buf = kmalloc(len, GFP_KERNEL);
- if (!buf)
- return;
-
- vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
- for (y = clip->y1; y < clip->y2; y++) {
- dst8 = dst;
- src32 = memcpy(buf, vaddr, len);
- drm_fb_xrgb8888_to_gray8_line(dst8, src32, linepixels);
- vaddr += fb->pitches[0];
- dst += dst_pitch;
- }
-
- kfree(buf);
+ drm_fb_xfrm(dst, dst_pitch, 1, vaddr, fb, clip, false, drm_fb_xrgb8888_to_gray8_line);
}
EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8);
@@ -690,17 +603,20 @@ int drm_fb_blit_toio(void __iomem *dst, unsigned int dst_pitch, uint32_t dst_for
EXPORT_SYMBOL(drm_fb_blit_toio);
-static void drm_fb_gray8_to_mono_line(u8 *dst, const u8 *src, unsigned int pixels)
+static void drm_fb_gray8_to_mono_line(void *dbuf, const void *sbuf, unsigned int pixels)
{
+ u8 *dbuf8 = dbuf;
+ const u8 *sbuf8 = sbuf;
+
while (pixels) {
unsigned int i, bits = min(pixels, 8U);
u8 byte = 0;
for (i = 0; i < bits; i++, pixels--) {
- if (*src++ >= 128)
+ if (*sbuf8++ >= 128)
byte |= BIT(i);
}
- *dst++ = byte;
+ *dbuf8++ = byte;
}
}
diff --git a/drivers/gpu/drm/drm_gem_atomic_helper.c b/drivers/gpu/drm/drm_gem_atomic_helper.c
index a6d89aed0bda..a5026f617739 100644
--- a/drivers/gpu/drm/drm_gem_atomic_helper.c
+++ b/drivers/gpu/drm/drm_gem_atomic_helper.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/dma-resv.h>
+#include <linux/dma-fence-chain.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_atomic_uapi.h>
@@ -137,29 +138,67 @@
*
* This function is the default implementation for GEM drivers of
* &drm_plane_helper_funcs.prepare_fb if no callback is provided.
- *
- * See drm_atomic_set_fence_for_plane() for a discussion of implicit and
- * explicit fencing in atomic modeset updates.
*/
-int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state)
+int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *state)
{
- struct drm_gem_object *obj;
- struct dma_fence *fence;
+ struct dma_fence *fence = dma_fence_get(state->fence);
+ enum dma_resv_usage usage;
+ size_t i;
int ret;
if (!state->fb)
return 0;
- obj = drm_gem_fb_get_obj(state->fb, 0);
- ret = dma_resv_get_singleton(obj->resv, DMA_RESV_USAGE_WRITE, &fence);
- if (ret)
- return ret;
-
- /* TODO: drm_atomic_set_fence_for_plane() should be changed to be able
- * to handle more fences in general for multiple BOs per fb.
+ /*
+ * Only add the kernel fences here if there is already a fence set via
+ * explicit fencing interfaces on the atomic ioctl.
+ *
+ * This way explicit fencing can be used to overrule implicit fencing,
+ * which is important to make explicit fencing use-cases work: One
+ * example is using one buffer for 2 screens with different refresh
+ * rates. Implicit fencing will clamp rendering to the refresh rate of
+ * the slower screen, whereas explicit fence allows 2 independent
+ * render and display loops on a single buffer. If a driver allows
+ * obeys both implicit and explicit fences for plane updates, then it
+ * will break all the benefits of explicit fencing.
*/
- drm_atomic_set_fence_for_plane(state, fence);
+ usage = fence ? DMA_RESV_USAGE_KERNEL : DMA_RESV_USAGE_WRITE;
+
+ for (i = 0; i < state->fb->format->num_planes; ++i) {
+ struct drm_gem_object *obj = drm_gem_fb_get_obj(state->fb, i);
+ struct dma_fence *new;
+
+ if (WARN_ON_ONCE(!obj))
+ continue;
+
+ ret = dma_resv_get_singleton(obj->resv, usage, &new);
+ if (ret)
+ goto error;
+
+ if (new && fence) {
+ struct dma_fence_chain *chain = dma_fence_chain_alloc();
+
+ if (!chain) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ dma_fence_chain_init(chain, fence, new, 1);
+ fence = &chain->base;
+
+ } else if (new) {
+ fence = new;
+ }
+ }
+
+ dma_fence_put(state->fence);
+ state->fence = fence;
return 0;
+
+error:
+ dma_fence_put(fence);
+ return ret;
}
EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb);
@@ -168,13 +207,13 @@ EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb);
* @pipe: Simple display pipe
* @plane_state: Plane state
*
- * This function uses drm_gem_plane_helper_prepare_fb() to extract the exclusive fence
- * from &drm_gem_object.resv and attaches it to plane state for the atomic
+ * This function uses drm_gem_plane_helper_prepare_fb() to extract the fences
+ * from &drm_gem_object.resv and attaches them to the plane state for the atomic
* helper to wait on. This is necessary to correctly implement implicit
* synchronization for any buffers shared as a struct &dma_buf. Drivers can use
* this as their &drm_simple_display_pipe_funcs.prepare_fb callback.
*
- * See drm_atomic_set_fence_for_plane() for a discussion of implicit and
+ * See drm_gem_plane_helper_prepare_fb() for a discussion of implicit and
* explicit fencing in atomic modeset updates.
*/
int drm_gem_simple_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
diff --git a/drivers/gpu/drm/drm_managed.c b/drivers/gpu/drm/drm_managed.c
index 37d7db6223be..4cf214de50c4 100644
--- a/drivers/gpu/drm/drm_managed.c
+++ b/drivers/gpu/drm/drm_managed.c
@@ -8,6 +8,7 @@
#include <drm/drm_managed.h>
#include <linux/list.h>
+#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@@ -262,3 +263,29 @@ void drmm_kfree(struct drm_device *dev, void *data)
free_dr(dr_match);
}
EXPORT_SYMBOL(drmm_kfree);
+
+static void drmm_mutex_release(struct drm_device *dev, void *res)
+{
+ struct mutex *lock = res;
+
+ mutex_destroy(lock);
+}
+
+/**
+ * drmm_mutex_init - &drm_device-managed mutex_init()
+ * @dev: DRM device
+ * @lock: lock to be initialized
+ *
+ * Returns:
+ * 0 on success, or a negative errno code otherwise.
+ *
+ * This is a &drm_device-managed version of mutex_init(). The initialized
+ * lock is automatically destroyed on the final drm_dev_put().
+ */
+int drmm_mutex_init(struct drm_device *dev, struct mutex *lock)
+{
+ mutex_init(lock);
+
+ return drmm_add_action_or_reset(dev, drmm_mutex_release, lock);
+}
+EXPORT_SYMBOL(drmm_mutex_init);
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index bf0daa8d9bbd..726f2f163c26 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -247,6 +247,13 @@ static int __drm_universal_plane_init(struct drm_device *dev,
if (WARN_ON(config->num_total_plane >= 32))
return -EINVAL;
+ /*
+ * First driver to need more than 64 formats needs to fix this. Each
+ * format is encoded as a bit and the current code only supports a u64.
+ */
+ if (WARN_ON(format_count > 64))
+ return -EINVAL;
+
WARN_ON(drm_drv_uses_atomic_modeset(dev) &&
(!funcs->atomic_destroy_state ||
!funcs->atomic_duplicate_state));
@@ -268,13 +275,6 @@ static int __drm_universal_plane_init(struct drm_device *dev,
return -ENOMEM;
}
- /*
- * First driver to need more than 64 formats needs to fix this. Each
- * format is encoded as a bit and the current code only supports a u64.
- */
- if (WARN_ON(format_count > 64))
- return -EINVAL;
-
if (format_modifiers) {
const uint64_t *temp_modifiers = format_modifiers;
diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
index 98bb5c9239de..1ac916b24891 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
@@ -598,7 +598,12 @@ int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data,
sync_file = sync_file_create(submit->out_fence);
if (!sync_file) {
ret = -ENOMEM;
- goto err_submit_job;
+ /*
+ * When this late error is hit, the submit has already
+ * been handed over to the scheduler. At this point
+ * the sched_job must not be cleaned up.
+ */
+ goto err_submit_put;
}
fd_install(out_fence_fd, sync_file->file);
}
@@ -607,7 +612,8 @@ int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data,
args->fence = submit->out_fence_id;
err_submit_job:
- drm_sched_job_cleanup(&submit->sched_job);
+ if (ret)
+ drm_sched_job_cleanup(&submit->sched_job);
err_submit_put:
etnaviv_submit_put(submit);
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
index f067c86b0b12..ec673223d6b7 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
@@ -25,6 +25,7 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
@@ -1451,9 +1452,18 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
struct device *dev = dsi->dev;
struct drm_encoder *encoder = &dsi->encoder;
struct drm_device *drm = encoder->dev;
+ struct drm_panel *panel;
int ret;
- dsi->out_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);
+ panel = of_drm_find_panel(device->dev.of_node);
+ if (!IS_ERR(panel)) {
+ dsi->out_bridge = devm_drm_panel_bridge_add(dev, panel);
+ } else {
+ dsi->out_bridge = of_drm_find_bridge(device->dev.of_node);
+ if (!dsi->out_bridge)
+ dsi->out_bridge = ERR_PTR(-EINVAL);
+ }
+
if (IS_ERR(dsi->out_bridge)) {
ret = PTR_ERR(dsi->out_bridge);
DRM_DEV_ERROR(dev, "failed to find the bridge: %d\n", ret);
diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
index 960b49ea2ee5..5651734ce977 100644
--- a/drivers/gpu/drm/mcde/mcde_dsi.c
+++ b/drivers/gpu/drm/mcde/mcde_dsi.c
@@ -19,6 +19,7 @@
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
@@ -38,6 +39,7 @@ struct mcde_dsi {
struct device *dev;
struct mcde *mcde;
struct drm_bridge bridge;
+ struct drm_panel *panel;
struct drm_bridge *bridge_out;
struct mipi_dsi_host dsi_host;
struct mipi_dsi_device *mdsi;
@@ -1071,7 +1073,9 @@ static int mcde_dsi_bind(struct device *dev, struct device *master,
struct drm_device *drm = data;
struct mcde *mcde = to_mcde(drm);
struct mcde_dsi *d = dev_get_drvdata(dev);
- struct drm_bridge *bridge;
+ struct device_node *child;
+ struct drm_panel *panel = NULL;
+ struct drm_bridge *bridge = NULL;
if (!of_get_available_child_count(dev->of_node)) {
dev_info(dev, "unused DSI interface\n");
@@ -1096,10 +1100,37 @@ static int mcde_dsi_bind(struct device *dev, struct device *master,
return PTR_ERR(d->lp_clk);
}
- bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
- if (IS_ERR(bridge)) {
- dev_err(dev, "error to get bridge\n");
- return PTR_ERR(bridge);
+ /* Look for a panel as a child to this node */
+ for_each_available_child_of_node(dev->of_node, child) {
+ panel = of_drm_find_panel(child);
+ if (IS_ERR(panel)) {
+ dev_err(dev, "failed to find panel try bridge (%ld)\n",
+ PTR_ERR(panel));
+ panel = NULL;
+
+ bridge = of_drm_find_bridge(child);
+ if (!bridge) {
+ dev_err(dev, "failed to find bridge\n");
+ return -EINVAL;
+ }
+ }
+ }
+ if (panel) {
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_DSI);
+ if (IS_ERR(bridge)) {
+ dev_err(dev, "error adding panel bridge\n");
+ return PTR_ERR(bridge);
+ }
+ dev_info(dev, "connected to panel\n");
+ d->panel = panel;
+ } else if (bridge) {
+ /* TODO: AV8100 HDMI encoder goes here for example */
+ dev_info(dev, "connected to non-panel bridge (unsupported)\n");
+ return -ENODEV;
+ } else {
+ dev_err(dev, "no panel or bridge\n");
+ return -ENODEV;
}
d->bridge_out = bridge;
@@ -1122,6 +1153,8 @@ static void mcde_dsi_unbind(struct device *dev, struct device *master,
{
struct mcde_dsi *d = dev_get_drvdata(dev);
+ if (d->panel)
+ drm_panel_bridge_remove(d->bridge_out);
regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET,
PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0);
}
diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.c b/drivers/gpu/drm/mgag200/mgag200_drv.c
index 217844d71ab5..08839460606f 100644
--- a/drivers/gpu/drm/mgag200/mgag200_drv.c
+++ b/drivers/gpu/drm/mgag200/mgag200_drv.c
@@ -14,6 +14,7 @@
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_ioctl.h>
+#include <drm/drm_managed.h>
#include <drm/drm_module.h>
#include <drm/drm_pciids.h>
@@ -65,6 +66,11 @@ static int mgag200_regs_init(struct mga_device *mdev)
struct pci_dev *pdev = to_pci_dev(dev->dev);
u32 option, option2;
u8 crtcext3;
+ int ret;
+
+ ret = drmm_mutex_init(dev, &mdev->rmmio_lock);
+ if (ret)
+ return ret;
switch (mdev->type) {
case G200_PCI:
diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.h b/drivers/gpu/drm/mgag200/mgag200_drv.h
index 4368112023f7..a18384c41fc4 100644
--- a/drivers/gpu/drm/mgag200/mgag200_drv.h
+++ b/drivers/gpu/drm/mgag200/mgag200_drv.h
@@ -213,6 +213,7 @@ struct mga_device {
struct drm_device base;
unsigned long flags;
+ struct mutex rmmio_lock; /* Protects access to rmmio */
resource_size_t rmmio_base;
resource_size_t rmmio_size;
void __iomem *rmmio;
diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c
index 6e18d3bbd720..abde7655477d 100644
--- a/drivers/gpu/drm/mgag200/mgag200_mode.c
+++ b/drivers/gpu/drm/mgag200/mgag200_mode.c
@@ -881,6 +881,14 @@ mgag200_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe,
.y2 = fb->height,
};
+ /*
+ * Concurrent operations could possibly trigger a call to
+ * drm_connector_helper_funcs.get_modes by trying to read the
+ * display modes. Protect access to I/O registers by acquiring
+ * the I/O-register lock.
+ */
+ mutex_lock(&mdev->rmmio_lock);
+
if (mdev->type == G200_WB || mdev->type == G200_EW3)
mgag200_g200wb_hold_bmc(mdev);
@@ -904,6 +912,8 @@ mgag200_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe,
mgag200_enable_display(mdev);
mgag200_handle_damage(mdev, fb, &fullscreen, &shadow_plane_state->data[0]);
+
+ mutex_unlock(&mdev->rmmio_lock);
}
static void
@@ -963,8 +973,12 @@ mgag200_simple_display_pipe_update(struct drm_simple_display_pipe *pipe,
if (!fb)
return;
+ mutex_lock(&mdev->rmmio_lock);
+
if (drm_atomic_helper_damage_merged(old_state, state, &damage))
mgag200_handle_damage(mdev, fb, &damage, &shadow_plane_state->data[0]);
+
+ mutex_unlock(&mdev->rmmio_lock);
}
static struct drm_crtc_state *
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
index 9d71c55a31c0..55aad92e08ba 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -52,6 +52,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = {
.hs_wdth_shift = 24,
.has_overlay = false,
.has_ctrl2 = false,
+ .has_crc32 = false,
},
[MXSFB_V4] = {
.transfer_count = LCDC_V4_TRANSFER_COUNT,
@@ -61,6 +62,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = {
.hs_wdth_shift = 18,
.has_overlay = false,
.has_ctrl2 = true,
+ .has_crc32 = true,
},
[MXSFB_V6] = {
.transfer_count = LCDC_V4_TRANSFER_COUNT,
@@ -70,6 +72,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = {
.hs_wdth_shift = 18,
.has_overlay = true,
.has_ctrl2 = true,
+ .has_crc32 = true,
},
};
@@ -157,12 +160,19 @@ static irqreturn_t mxsfb_irq_handler(int irq, void *data)
{
struct drm_device *drm = data;
struct mxsfb_drm_private *mxsfb = drm->dev_private;
- u32 reg;
+ u32 reg, crc;
+ u64 vbc;
reg = readl(mxsfb->base + LCDC_CTRL1);
- if (reg & CTRL1_CUR_FRAME_DONE_IRQ)
+ if (reg & CTRL1_CUR_FRAME_DONE_IRQ) {
drm_crtc_handle_vblank(&mxsfb->crtc);
+ if (mxsfb->crc_active) {
+ crc = readl(mxsfb->base + LCDC_V4_CRC_STAT);
+ vbc = drm_crtc_accurate_vblank_count(&mxsfb->crtc);
+ drm_crtc_add_crc_entry(&mxsfb->crtc, true, vbc, &crc);
+ }
+ }
writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
index ddb5b0417a82..d160d921b25f 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
@@ -23,6 +23,7 @@ struct mxsfb_devdata {
unsigned int hs_wdth_shift;
bool has_overlay;
bool has_ctrl2;
+ bool has_crc32;
};
struct mxsfb_drm_private {
@@ -44,6 +45,8 @@ struct mxsfb_drm_private {
struct drm_encoder encoder;
struct drm_connector *connector;
struct drm_bridge *bridge;
+
+ bool crc_active;
};
static inline struct mxsfb_drm_private *
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_kms.c b/drivers/gpu/drm/mxsfb/mxsfb_kms.c
index cd2a59e110c3..f021ab2c4520 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_kms.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_kms.c
@@ -439,6 +439,41 @@ static void mxsfb_crtc_disable_vblank(struct drm_crtc *crtc)
writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
}
+static int mxsfb_crtc_set_crc_source(struct drm_crtc *crtc, const char *source)
+{
+ struct mxsfb_drm_private *mxsfb;
+
+ if (!crtc)
+ return -ENODEV;
+
+ mxsfb = to_mxsfb_drm_private(crtc->dev);
+
+ if (source && strcmp(source, "auto") == 0)
+ mxsfb->crc_active = true;
+ else if (!source)
+ mxsfb->crc_active = false;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int mxsfb_crtc_verify_crc_source(struct drm_crtc *crtc,
+ const char *source, size_t *values_cnt)
+{
+ if (!crtc)
+ return -ENODEV;
+
+ if (source && strcmp(source, "auto") != 0) {
+ DRM_DEBUG_DRIVER("Unknown CRC source %s for %s\n",
+ source, crtc->name);
+ return -EINVAL;
+ }
+
+ *values_cnt = 1;
+ return 0;
+}
+
static const struct drm_crtc_helper_funcs mxsfb_crtc_helper_funcs = {
.atomic_check = mxsfb_crtc_atomic_check,
.atomic_flush = mxsfb_crtc_atomic_flush,
@@ -457,6 +492,19 @@ static const struct drm_crtc_funcs mxsfb_crtc_funcs = {
.disable_vblank = mxsfb_crtc_disable_vblank,
};
+static const struct drm_crtc_funcs mxsfb_crtc_with_crc_funcs = {
+ .reset = drm_atomic_helper_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .enable_vblank = mxsfb_crtc_enable_vblank,
+ .disable_vblank = mxsfb_crtc_disable_vblank,
+ .set_crc_source = mxsfb_crtc_set_crc_source,
+ .verify_crc_source = mxsfb_crtc_verify_crc_source,
+};
+
/* -----------------------------------------------------------------------------
* Encoder
*/
@@ -645,9 +693,16 @@ int mxsfb_kms_init(struct mxsfb_drm_private *mxsfb)
}
drm_crtc_helper_add(crtc, &mxsfb_crtc_helper_funcs);
- ret = drm_crtc_init_with_planes(mxsfb->drm, crtc,
- &mxsfb->planes.primary, NULL,
- &mxsfb_crtc_funcs, NULL);
+ if (mxsfb->devdata->has_crc32) {
+ ret = drm_crtc_init_with_planes(mxsfb->drm, crtc,
+ &mxsfb->planes.primary, NULL,
+ &mxsfb_crtc_with_crc_funcs,
+ NULL);
+ } else {
+ ret = drm_crtc_init_with_planes(mxsfb->drm, crtc,
+ &mxsfb->planes.primary, NULL,
+ &mxsfb_crtc_funcs, NULL);
+ }
if (ret)
return ret;
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_regs.h b/drivers/gpu/drm/mxsfb/mxsfb_regs.h
index 694fea13e893..cf813a1da1d7 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_regs.h
+++ b/drivers/gpu/drm/mxsfb/mxsfb_regs.h
@@ -26,6 +26,7 @@
#define LCDC_VDCTRL2 0x90
#define LCDC_VDCTRL3 0xa0
#define LCDC_VDCTRL4 0xb0
+#define LCDC_V4_CRC_STAT 0x1a0
#define LCDC_V4_DEBUG0 0x1d0
#define LCDC_V3_DEBUG0 0x1f0
#define LCDC_AS_CTRL 0x210
diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.c b/drivers/gpu/drm/nouveau/dispnv50/wndw.c
index 8642b84ea20c..bb8a4601e0d9 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c
@@ -32,6 +32,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_fourcc.h>
#include "nouveau_bo.h"
@@ -558,9 +559,7 @@ nv50_wndw_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state)
asyw->image.handle[0] = ctxdma->object.handle;
}
- ret = dma_resv_get_singleton(nvbo->bo.base.resv,
- DMA_RESV_USAGE_WRITE,
- &asyw->state.fence);
+ ret = drm_gem_plane_helper_prepare_fb(plane, state);
if (ret)
return ret;
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h b/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h
index 1665738948fb..96113c8bee8c 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h
@@ -62,4 +62,6 @@ void nvkm_subdev_intr(struct nvkm_subdev *);
#define nvkm_debug(s,f,a...) nvkm_printk((s), DEBUG, info, f, ##a)
#define nvkm_trace(s,f,a...) nvkm_printk((s), TRACE, info, f, ##a)
#define nvkm_spam(s,f,a...) nvkm_printk((s), SPAM, dbg, f, ##a)
+
+#define nvkm_error_ratelimited(s,f,a...) nvkm_printk((s), ERROR, err_ratelimited, f, ##a)
#endif
diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.c b/drivers/gpu/drm/nouveau/nouveau_debugfs.c
index 1cbe01048b93..2a36d1ca8fda 100644
--- a/drivers/gpu/drm/nouveau/nouveau_debugfs.c
+++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.c
@@ -255,19 +255,13 @@ nouveau_drm_debugfs_init(struct drm_minor *minor)
int
nouveau_debugfs_init(struct nouveau_drm *drm)
{
- int ret;
-
drm->debugfs = kzalloc(sizeof(*drm->debugfs), GFP_KERNEL);
if (!drm->debugfs)
return -ENOMEM;
- ret = nvif_object_ctor(&drm->client.device.object, "debugfsCtrl", 0,
- NVIF_CLASS_CONTROL, NULL, 0,
- &drm->debugfs->ctrl);
- if (ret)
- return ret;
-
- return 0;
+ return nvif_object_ctor(&drm->client.device.object, "debugfsCtrl", 0,
+ NVIF_CLASS_CONTROL, NULL, 0,
+ &drm->debugfs->ctrl);
}
void
diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c
index 7f01dcf81fab..abcac7db4347 100644
--- a/drivers/gpu/drm/nouveau/nouveau_fence.c
+++ b/drivers/gpu/drm/nouveau/nouveau_fence.c
@@ -224,7 +224,6 @@ nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan)
&fctx->lock, fctx->context, ++fctx->sequence);
kref_get(&fctx->fence_ref);
- trace_dma_fence_emit(&fence->base);
ret = fctx->emit(fence);
if (!ret) {
dma_fence_get(&fence->base);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c
index 53a6651ac225..80b5aaceeaad 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c
@@ -35,13 +35,13 @@ gf100_bus_intr(struct nvkm_bus *bus)
u32 addr = nvkm_rd32(device, 0x009084);
u32 data = nvkm_rd32(device, 0x009088);
- nvkm_error(subdev,
- "MMIO %s of %08x FAULT at %06x [ %s%s%s]\n",
- (addr & 0x00000002) ? "write" : "read", data,
- (addr & 0x00fffffc),
- (stat & 0x00000002) ? "!ENGINE " : "",
- (stat & 0x00000004) ? "PRIVRING " : "",
- (stat & 0x00000008) ? "TIMEOUT " : "");
+ nvkm_error_ratelimited(subdev,
+ "MMIO %s of %08x FAULT at %06x [ %s%s%s]\n",
+ (addr & 0x00000002) ? "write" : "read", data,
+ (addr & 0x00fffffc),
+ (stat & 0x00000002) ? "!ENGINE " : "",
+ (stat & 0x00000004) ? "PRIVRING " : "",
+ (stat & 0x00000008) ? "TIMEOUT " : "");
nvkm_wr32(device, 0x009084, 0x00000000);
nvkm_wr32(device, 0x001100, (stat & 0x0000000e));
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c
index ad8da523bb22..c75e463f3501 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c
@@ -45,9 +45,9 @@ nv31_bus_intr(struct nvkm_bus *bus)
u32 addr = nvkm_rd32(device, 0x009084);
u32 data = nvkm_rd32(device, 0x009088);
- nvkm_error(subdev, "MMIO %s of %08x FAULT at %06x\n",
- (addr & 0x00000002) ? "write" : "read", data,
- (addr & 0x00fffffc));
+ nvkm_error_ratelimited(subdev, "MMIO %s of %08x FAULT at %06x\n",
+ (addr & 0x00000002) ? "write" : "read", data,
+ (addr & 0x00fffffc));
stat &= ~0x00000008;
nvkm_wr32(device, 0x001100, 0x00000008);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c
index 3a1e45adeedc..2055d0b100d3 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c
@@ -60,9 +60,9 @@ nv50_bus_intr(struct nvkm_bus *bus)
u32 addr = nvkm_rd32(device, 0x009084);
u32 data = nvkm_rd32(device, 0x009088);
- nvkm_error(subdev, "MMIO %s of %08x FAULT at %06x\n",
- (addr & 0x00000002) ? "write" : "read", data,
- (addr & 0x00fffffc));
+ nvkm_error_ratelimited(subdev, "MMIO %s of %08x FAULT at %06x\n",
+ (addr & 0x00000002) ? "write" : "read", data,
+ (addr & 0x00fffffc));
stat &= ~0x00000008;
nvkm_wr32(device, 0x001100, 0x00000008);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c
index 05729ca19e9a..8977483a9f42 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c
@@ -32,7 +32,6 @@ g98_devinit_disable(struct nvkm_devinit *init)
struct nvkm_device *device = init->subdev.device;
u32 r001540 = nvkm_rd32(device, 0x001540);
u32 r00154c = nvkm_rd32(device, 0x00154c);
- u64 disable = 0ULL;
if (!(r001540 & 0x40000000)) {
nvkm_subdev_disable(device, NVKM_ENGINE_MSPDEC, 0);
@@ -47,7 +46,7 @@ g98_devinit_disable(struct nvkm_devinit *init)
if (!(r00154c & 0x00000040))
nvkm_subdev_disable(device, NVKM_ENGINE_SEC, 0);
- return disable;
+ return 0ULL;
}
static const struct nvkm_devinit_func
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c
index 051cfd6a5caf..5b7cb1fe7897 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c
@@ -68,7 +68,6 @@ gf100_devinit_disable(struct nvkm_devinit *init)
{
struct nvkm_device *device = init->subdev.device;
u32 r022500 = nvkm_rd32(device, 0x022500);
- u64 disable = 0ULL;
if (r022500 & 0x00000001)
nvkm_subdev_disable(device, NVKM_ENGINE_DISP, 0);
@@ -87,7 +86,7 @@ gf100_devinit_disable(struct nvkm_devinit *init)
if (r022500 & 0x00000200)
nvkm_subdev_disable(device, NVKM_ENGINE_CE, 1);
- return disable;
+ return 0ULL;
}
void
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c
index 4323732a3cb2..8955af2704c7 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c
@@ -32,7 +32,6 @@ gm107_devinit_disable(struct nvkm_devinit *init)
struct nvkm_device *device = init->subdev.device;
u32 r021c00 = nvkm_rd32(device, 0x021c00);
u32 r021c04 = nvkm_rd32(device, 0x021c04);
- u64 disable = 0ULL;
if (r021c00 & 0x00000001)
nvkm_subdev_disable(device, NVKM_ENGINE_CE, 0);
@@ -41,7 +40,7 @@ gm107_devinit_disable(struct nvkm_devinit *init)
if (r021c04 & 0x00000001)
nvkm_subdev_disable(device, NVKM_ENGINE_DISP, 0);
- return disable;
+ return 0ULL;
}
static const struct nvkm_devinit_func
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 595396f57632..4a2e580a2f7b 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -720,7 +720,7 @@ static const struct drm_display_mode ampire_am_1280800n3tzqw_t00h_mode = {
static const struct panel_desc ampire_am_1280800n3tzqw_t00h = {
.modes = &ampire_am_1280800n3tzqw_t00h_mode,
.num_modes = 1,
- .bpc = 6,
+ .bpc = 8,
.size = {
.width = 217,
.height = 136,
@@ -3310,6 +3310,41 @@ static const struct panel_desc starry_kr070pe2t = {
.connector_type = DRM_MODE_CONNECTOR_DPI,
};
+static const struct display_timing startek_kd070wvfpa_mode = {
+ .pixelclock = { 25200000, 27200000, 30500000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 19, 44, 115 },
+ .hback_porch = { 5, 16, 101 },
+ .hsync_len = { 1, 2, 100 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 5, 43, 67 },
+ .vback_porch = { 5, 5, 67 },
+ .vsync_len = { 1, 2, 66 },
+ .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW |
+ DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE |
+ DISPLAY_FLAGS_SYNC_POSEDGE,
+};
+
+static const struct panel_desc startek_kd070wvfpa = {
+ .timings = &startek_kd070wvfpa_mode,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 152,
+ .height = 91,
+ },
+ .delay = {
+ .prepare = 20,
+ .enable = 200,
+ .disable = 200,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .connector_type = DRM_MODE_CONNECTOR_DPI,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE,
+};
+
static const struct display_timing tsd_tst043015cmhx_timing = {
.pixelclock = { 5000000, 9000000, 12000000 },
.hactive = { 480, 480, 480 },
@@ -4020,6 +4055,9 @@ static const struct of_device_id platform_of_match[] = {
.compatible = "starry,kr070pe2t",
.data = &starry_kr070pe2t,
}, {
+ .compatible = "startek,kd070wvfpa",
+ .data = &startek_kd070wvfpa,
+ }, {
.compatible = "team-source-display,tst043015cmhx",
.data = &tsd_tst043015cmhx,
}, {
diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
index 9a9c29b1d3e1..9a64fa4c7530 100644
--- a/drivers/gpu/drm/qxl/qxl_display.c
+++ b/drivers/gpu/drm/qxl/qxl_display.c
@@ -34,6 +34,7 @@
#include <drm/drm_plane_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_gem_atomic_helper.h>
#include "qxl_drv.h"
#include "qxl_object.h"
@@ -829,6 +830,7 @@ static int qxl_plane_prepare_fb(struct drm_plane *plane,
struct qxl_device *qdev = to_qxl(plane->dev);
struct drm_gem_object *obj;
struct qxl_bo *user_bo;
+ int ret;
if (!new_state->fb)
return 0;
@@ -852,7 +854,11 @@ static int qxl_plane_prepare_fb(struct drm_plane *plane,
qxl_free_cursor(old_cursor_bo);
}
- return qxl_bo_pin(user_bo);
+ ret = qxl_bo_pin(user_bo);
+ if (ret)
+ return ret;
+
+ return drm_gem_plane_helper_prepare_fb(plane, new_state);
}
static void qxl_plane_cleanup_fb(struct drm_plane *plane,
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index fb81040a733d..5afab49dc4f2 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -22,8 +22,22 @@ config DRM_ROCKCHIP
if DRM_ROCKCHIP
+config ROCKCHIP_VOP
+ bool "Rockchip VOP driver"
+ default y
+ help
+ This selects support for the VOP driver. You should enable it
+ on older SoCs.
+
+config ROCKCHIP_VOP2
+ bool "Rockchip VOP2 driver"
+ help
+ This selects support for the VOP2 driver. The VOP2 hardware is
+ first found on the RK3568.
+
config ROCKCHIP_ANALOGIX_DP
bool "Rockchip specific extensions for Analogix DP driver"
+ depends on ROCKCHIP_VOP
select DRM_DISPLAY_DP_HELPER
help
This selects support for Rockchip SoC specific extensions
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 1a56f696558c..3ff7b21c0414 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -4,8 +4,10 @@
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
- rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o
+ rockchip_drm_gem.o
+rockchipdrm-$(CONFIG_ROCKCHIP_VOP2) += rockchip_drm_vop2.o rockchip_vop2_reg.o
+rockchipdrm-$(CONFIG_ROCKCHIP_VOP) += rockchip_drm_vop.o rockchip_vop_reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
index f7e3fb94ed04..70be64ca0a00 100644
--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -40,8 +40,6 @@
#define PSR_WAIT_LINE_FLAG_TIMEOUT_MS 100
-#define to_dp(nm) container_of(nm, struct rockchip_dp_device, nm)
-
/**
* struct rockchip_dp_chip_data - splite the grf setting of kind of chips
* @lcdsel_grf_reg: grf register offset of lcdc select
@@ -59,7 +57,7 @@ struct rockchip_dp_chip_data {
struct rockchip_dp_device {
struct drm_device *drm_dev;
struct device *dev;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
struct drm_display_mode mode;
struct clk *pclk;
@@ -73,6 +71,18 @@ struct rockchip_dp_device {
struct analogix_dp_plat_data plat_data;
};
+static struct rockchip_dp_device *encoder_to_dp(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct rockchip_dp_device, encoder);
+}
+
+static struct rockchip_dp_device *pdata_encoder_to_dp(struct analogix_dp_plat_data *plat_data)
+{
+ return container_of(plat_data, struct rockchip_dp_device, plat_data);
+}
+
static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
{
reset_control_assert(dp->rst);
@@ -84,7 +94,7 @@ static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
{
- struct rockchip_dp_device *dp = to_dp(plat_data);
+ struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
int ret;
ret = clk_prepare_enable(dp->pclk);
@@ -105,7 +115,7 @@ static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
{
- struct rockchip_dp_device *dp = to_dp(plat_data);
+ struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
clk_disable_unprepare(dp->pclk);
@@ -166,7 +176,7 @@ struct drm_crtc *rockchip_dp_drm_get_new_crtc(struct drm_encoder *encoder,
static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
- struct rockchip_dp_device *dp = to_dp(encoder);
+ struct rockchip_dp_device *dp = encoder_to_dp(encoder);
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state;
int ret;
@@ -208,7 +218,7 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder,
static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
- struct rockchip_dp_device *dp = to_dp(encoder);
+ struct rockchip_dp_device *dp = encoder_to_dp(encoder);
struct drm_crtc *crtc;
struct drm_crtc_state *new_crtc_state = NULL;
int ret;
@@ -297,7 +307,7 @@ static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp)
{
- struct drm_encoder *encoder = &dp->encoder;
+ struct drm_encoder *encoder = &dp->encoder.encoder;
struct drm_device *drm_dev = dp->drm_dev;
struct device *dev = dp->dev;
int ret;
@@ -333,7 +343,7 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
return ret;
}
- dp->plat_data.encoder = &dp->encoder;
+ dp->plat_data.encoder = &dp->encoder.encoder;
ret = analogix_dp_bind(dp->adp, drm_dev);
if (ret)
@@ -341,7 +351,7 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
return 0;
err_cleanup_encoder:
- dp->encoder.funcs->destroy(&dp->encoder);
+ dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder);
return ret;
}
@@ -351,7 +361,7 @@ static void rockchip_dp_unbind(struct device *dev, struct device *master,
struct rockchip_dp_device *dp = dev_get_drvdata(dev);
analogix_dp_unbind(dp->adp);
- dp->encoder.funcs->destroy(&dp->encoder);
+ dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder);
}
static const struct component_ops rockchip_dp_component_ops = {
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c
index 2857d3f68730..c204e9b95c1f 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.c
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c
@@ -26,11 +26,17 @@
#include "cdn-dp-reg.h"
#include "rockchip_drm_vop.h"
-#define connector_to_dp(c) \
- container_of(c, struct cdn_dp_device, connector)
+static inline struct cdn_dp_device *connector_to_dp(struct drm_connector *connector)
+{
+ return container_of(connector, struct cdn_dp_device, connector);
+}
-#define encoder_to_dp(c) \
- container_of(c, struct cdn_dp_device, encoder)
+static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct cdn_dp_device, encoder);
+}
#define GRF_SOC_CON9 0x6224
#define DP_SEL_VOP_LIT BIT(12)
@@ -48,7 +54,7 @@ struct cdn_dp_data {
u8 max_phy;
};
-struct cdn_dp_data rk3399_cdn_dp = {
+static struct cdn_dp_data rk3399_cdn_dp = {
.max_phy = 2,
};
@@ -1050,7 +1056,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
INIT_WORK(&dp->event_work, cdn_dp_pd_event_work);
- encoder = &dp->encoder;
+ encoder = &dp->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
@@ -1115,7 +1121,7 @@ err_free_encoder:
static void cdn_dp_unbind(struct device *dev, struct device *master, void *data)
{
struct cdn_dp_device *dp = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dp->encoder;
+ struct drm_encoder *encoder = &dp->encoder.encoder;
struct drm_connector *connector = &dp->connector;
cancel_work_sync(&dp->event_work);
diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h
index cd6c3089e35c..5b2fed1f5f55 100644
--- a/drivers/gpu/drm/rockchip/cdn-dp-core.h
+++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h
@@ -66,7 +66,7 @@ struct cdn_dp_device {
struct device *dev;
struct drm_device *drm_dev;
struct drm_connector connector;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
struct drm_display_mode mode;
struct platform_device *audio_pdev;
struct work_struct event_work;
diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
index 4ed7a6868197..110e83aad9bb 100644
--- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
@@ -181,8 +181,6 @@
#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)
-#define to_dsi(nm) container_of(nm, struct dw_mipi_dsi_rockchip, nm)
-
enum {
DW_DSI_USAGE_IDLE,
DW_DSI_USAGE_DSI,
@@ -236,7 +234,7 @@ struct rockchip_dw_dsi_chip_data {
struct dw_mipi_dsi_rockchip {
struct device *dev;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
void __iomem *base;
struct regmap *grf_regmap;
@@ -271,6 +269,13 @@ struct dw_mipi_dsi_rockchip {
bool dsi_bound;
};
+static struct dw_mipi_dsi_rockchip *to_dsi(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct dw_mipi_dsi_rockchip, encoder);
+}
+
struct dphy_pll_parameter_map {
unsigned int max_mbps;
u8 hsfreqrange;
@@ -770,7 +775,7 @@ static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
int ret, mux;
mux = drm_of_encoder_active_endpoint_id(dsi->dev->of_node,
- &dsi->encoder);
+ &dsi->encoder.encoder);
if (mux < 0)
return;
@@ -801,7 +806,7 @@ dw_mipi_dsi_encoder_helper_funcs = {
static int rockchip_dsi_drm_create_encoder(struct dw_mipi_dsi_rockchip *dsi,
struct drm_device *drm_dev)
{
- struct drm_encoder *encoder = &dsi->encoder;
+ struct drm_encoder *encoder = &dsi->encoder.encoder;
int ret;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
@@ -959,7 +964,7 @@ static int dw_mipi_dsi_rockchip_bind(struct device *dev,
goto out_pll_clk;
}
- ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder);
+ ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder.encoder);
if (ret) {
DRM_DEV_ERROR(dev, "Failed to bind: %d\n", ret);
goto out_pll_clk;
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 8677c8271678..c14f88893868 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -9,6 +9,7 @@
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <drm/bridge/dw_hdmi.h>
#include <drm/drm_edid.h>
@@ -50,6 +51,10 @@
#define RK3399_GRF_SOC_CON20 0x6250
#define RK3399_HDMI_LCDC_SEL BIT(6)
+#define RK3568_GRF_VO_CON1 0x0364
+#define RK3568_HDMI_SDAIN_MSK BIT(15)
+#define RK3568_HDMI_SCLIN_MSK BIT(14)
+
#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)
/**
@@ -67,15 +72,22 @@ struct rockchip_hdmi_chip_data {
struct rockchip_hdmi {
struct device *dev;
struct regmap *regmap;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
const struct rockchip_hdmi_chip_data *chip_data;
- struct clk *vpll_clk;
+ struct clk *ref_clk;
struct clk *grf_clk;
struct dw_hdmi *hdmi;
+ struct regulator *avdd_0v9;
+ struct regulator *avdd_1v8;
struct phy *phy;
};
-#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x)
+static struct rockchip_hdmi *to_rockchip_hdmi(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct rockchip_hdmi, encoder);
+}
static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
{
@@ -196,14 +208,15 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
return PTR_ERR(hdmi->regmap);
}
- hdmi->vpll_clk = devm_clk_get(hdmi->dev, "vpll");
- if (PTR_ERR(hdmi->vpll_clk) == -ENOENT) {
- hdmi->vpll_clk = NULL;
- } else if (PTR_ERR(hdmi->vpll_clk) == -EPROBE_DEFER) {
+ hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "ref");
+ if (!hdmi->ref_clk)
+ hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "vpll");
+
+ if (PTR_ERR(hdmi->ref_clk) == -EPROBE_DEFER) {
return -EPROBE_DEFER;
- } else if (IS_ERR(hdmi->vpll_clk)) {
- DRM_DEV_ERROR(hdmi->dev, "failed to get vpll clock\n");
- return PTR_ERR(hdmi->vpll_clk);
+ } else if (IS_ERR(hdmi->ref_clk)) {
+ DRM_DEV_ERROR(hdmi->dev, "failed to get reference clock\n");
+ return PTR_ERR(hdmi->ref_clk);
}
hdmi->grf_clk = devm_clk_get(hdmi->dev, "grf");
@@ -216,6 +229,14 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
return PTR_ERR(hdmi->grf_clk);
}
+ hdmi->avdd_0v9 = devm_regulator_get(hdmi->dev, "avdd-0v9");
+ if (IS_ERR(hdmi->avdd_0v9))
+ return PTR_ERR(hdmi->avdd_0v9);
+
+ hdmi->avdd_1v8 = devm_regulator_get(hdmi->dev, "avdd-1v8");
+ if (IS_ERR(hdmi->avdd_1v8))
+ return PTR_ERR(hdmi->avdd_1v8);
+
return 0;
}
@@ -257,7 +278,7 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
{
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
- clk_set_rate(hdmi->vpll_clk, adj_mode->clock * 1000);
+ clk_set_rate(hdmi->ref_clk, adj_mode->clock * 1000);
}
static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
@@ -467,6 +488,19 @@ static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
.use_drm_infoframe = true,
};
+static struct rockchip_hdmi_chip_data rk3568_chip_data = {
+ .lcdsel_grf_reg = -1,
+};
+
+static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = {
+ .mode_valid = dw_hdmi_rockchip_mode_valid,
+ .mpll_cfg = rockchip_mpll_cfg,
+ .cur_ctr = rockchip_cur_ctr,
+ .phy_config = rockchip_phy_config,
+ .phy_data = &rk3568_chip_data,
+ .use_drm_infoframe = true,
+};
+
static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
{ .compatible = "rockchip,rk3228-dw-hdmi",
.data = &rk3228_hdmi_drv_data
@@ -480,6 +514,9 @@ static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
{ .compatible = "rockchip,rk3399-dw-hdmi",
.data = &rk3399_hdmi_drv_data
},
+ { .compatible = "rockchip,rk3568-dw-hdmi",
+ .data = &rk3568_hdmi_drv_data
+ },
{},
};
MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids);
@@ -511,9 +548,12 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
hdmi->dev = &pdev->dev;
hdmi->chip_data = plat_data->phy_data;
plat_data->phy_data = hdmi;
- encoder = &hdmi->encoder;
+ encoder = &hdmi->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+ rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder,
+ dev->of_node, 0, 0);
+
/*
* If we failed to find the CRTC(s) which this encoder is
* supposed to be connected to, it's because the CRTC has
@@ -537,11 +577,31 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
return ret;
}
- ret = clk_prepare_enable(hdmi->vpll_clk);
+ ret = regulator_enable(hdmi->avdd_0v9);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret);
+ goto err_avdd_0v9;
+ }
+
+ ret = regulator_enable(hdmi->avdd_1v8);
if (ret) {
- DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI vpll: %d\n",
+ DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret);
+ goto err_avdd_1v8;
+ }
+
+ ret = clk_prepare_enable(hdmi->ref_clk);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n",
ret);
- return ret;
+ goto err_clk;
+ }
+
+ if (hdmi->chip_data == &rk3568_chip_data) {
+ regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
+ HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK |
+ RK3568_HDMI_SCLIN_MSK,
+ RK3568_HDMI_SDAIN_MSK |
+ RK3568_HDMI_SCLIN_MSK));
}
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
@@ -557,10 +617,19 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
*/
if (IS_ERR(hdmi->hdmi)) {
ret = PTR_ERR(hdmi->hdmi);
- drm_encoder_cleanup(encoder);
- clk_disable_unprepare(hdmi->vpll_clk);
+ goto err_bind;
}
+ return 0;
+
+err_bind:
+ drm_encoder_cleanup(encoder);
+ clk_disable_unprepare(hdmi->ref_clk);
+err_clk:
+ regulator_disable(hdmi->avdd_1v8);
+err_avdd_1v8:
+ regulator_disable(hdmi->avdd_0v9);
+err_avdd_0v9:
return ret;
}
@@ -570,7 +639,10 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
dw_hdmi_unbind(hdmi->hdmi);
- clk_disable_unprepare(hdmi->vpll_clk);
+ clk_disable_unprepare(hdmi->ref_clk);
+
+ regulator_disable(hdmi->avdd_1v8);
+ regulator_disable(hdmi->avdd_0v9);
}
static const struct component_ops dw_hdmi_rockchip_ops = {
diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/rockchip/inno_hdmi.c
index 046e8ec2a71c..87b2243ea23e 100644
--- a/drivers/gpu/drm/rockchip/inno_hdmi.c
+++ b/drivers/gpu/drm/rockchip/inno_hdmi.c
@@ -26,11 +26,8 @@
#include "inno_hdmi.h"
-#define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x)
-
struct hdmi_data_info {
int vic;
- bool sink_is_hdmi;
bool sink_has_audio;
unsigned int enc_in_format;
unsigned int enc_out_format;
@@ -56,7 +53,7 @@ struct inno_hdmi {
void __iomem *regs;
struct drm_connector connector;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
struct inno_hdmi_i2c *i2c;
struct i2c_adapter *ddc;
@@ -67,6 +64,18 @@ struct inno_hdmi {
struct drm_display_mode previous_mode;
};
+static struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct inno_hdmi, encoder);
+}
+
+static struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector)
+{
+ return container_of(connector, struct inno_hdmi, connector);
+}
+
enum {
CSC_ITU601_16_235_TO_RGB_0_255_8BIT,
CSC_ITU601_0_255_TO_RGB_0_255_8BIT,
@@ -433,6 +442,8 @@ static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi,
static int inno_hdmi_setup(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
+ struct drm_display_info *display = &hdmi->connector.display_info;
+
hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB;
@@ -452,13 +463,13 @@ static int inno_hdmi_setup(struct inno_hdmi *hdmi,
/* Set HDMI Mode */
hdmi_writeb(hdmi, HDMI_HDCP_CTRL,
- v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi));
+ v_HDMI_DVI(display->is_hdmi));
inno_hdmi_config_video_timing(hdmi, mode);
inno_hdmi_config_video_csc(hdmi);
- if (hdmi->hdmi_data.sink_is_hdmi) {
+ if (display->is_hdmi) {
inno_hdmi_config_video_avi(hdmi, mode);
inno_hdmi_config_video_vsi(hdmi, mode);
}
@@ -483,7 +494,7 @@ static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
- struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+ struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder);
inno_hdmi_setup(hdmi, adj_mode);
@@ -493,14 +504,14 @@ static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder,
static void inno_hdmi_encoder_enable(struct drm_encoder *encoder)
{
- struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+ struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder);
inno_hdmi_set_pwr_mode(hdmi, NORMAL);
}
static void inno_hdmi_encoder_disable(struct drm_encoder *encoder)
{
- struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+ struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder);
inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR);
}
@@ -536,7 +547,7 @@ static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = {
static enum drm_connector_status
inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
- struct inno_hdmi *hdmi = to_inno_hdmi(connector);
+ struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
connector_status_connected : connector_status_disconnected;
@@ -544,7 +555,7 @@ inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
{
- struct inno_hdmi *hdmi = to_inno_hdmi(connector);
+ struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
struct edid *edid;
int ret = 0;
@@ -553,7 +564,6 @@ static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
edid = drm_get_edid(connector, hdmi->ddc);
if (edid) {
- hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid);
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
@@ -599,7 +609,7 @@ static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi)
{
- struct drm_encoder *encoder = &hdmi->encoder;
+ struct drm_encoder *encoder = &hdmi->encoder.encoder;
struct device *dev = hdmi->dev;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
@@ -879,7 +889,7 @@ static int inno_hdmi_bind(struct device *dev, struct device *master,
return 0;
err_cleanup_hdmi:
hdmi->connector.funcs->destroy(&hdmi->connector);
- hdmi->encoder.funcs->destroy(&hdmi->encoder);
+ hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
err_put_adapter:
i2c_put_adapter(hdmi->ddc);
err_disable_clk:
@@ -893,7 +903,7 @@ static void inno_hdmi_unbind(struct device *dev, struct device *master,
struct inno_hdmi *hdmi = dev_get_drvdata(dev);
hdmi->connector.funcs->destroy(&hdmi->connector);
- hdmi->encoder.funcs->destroy(&hdmi->encoder);
+ hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
i2c_put_adapter(hdmi->ddc);
clk_disable_unprepare(hdmi->pclk);
diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c b/drivers/gpu/drm/rockchip/rk3066_hdmi.c
index 1c546c3a8998..c8c3612a4fe2 100644
--- a/drivers/gpu/drm/rockchip/rk3066_hdmi.c
+++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c
@@ -22,7 +22,6 @@
struct hdmi_data_info {
int vic; /* The CEA Video ID (VIC) of the current drm display mode. */
- bool sink_is_hdmi;
unsigned int enc_out_format;
unsigned int colorimetry;
};
@@ -47,7 +46,7 @@ struct rk3066_hdmi {
void __iomem *regs;
struct drm_connector connector;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
struct rk3066_hdmi_i2c *i2c;
struct i2c_adapter *ddc;
@@ -58,7 +57,17 @@ struct rk3066_hdmi {
struct drm_display_mode previous_mode;
};
-#define to_rk3066_hdmi(x) container_of(x, struct rk3066_hdmi, x)
+static struct rk3066_hdmi *encoder_to_rk3066_hdmi(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct rk3066_hdmi, encoder);
+}
+
+static struct rk3066_hdmi *connector_to_rk3066_hdmi(struct drm_connector *connector)
+{
+ return container_of(connector, struct rk3066_hdmi, connector);
+}
static inline u8 hdmi_readb(struct rk3066_hdmi *hdmi, u16 offset)
{
@@ -317,6 +326,8 @@ static void rk3066_hdmi_config_phy(struct rk3066_hdmi *hdmi)
static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
struct drm_display_mode *mode)
{
+ struct drm_display_info *display = &hdmi->connector.display_info;
+
hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
@@ -349,7 +360,7 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
rk3066_hdmi_config_video_timing(hdmi, mode);
- if (hdmi->hdmi_data.sink_is_hdmi) {
+ if (display->is_hdmi) {
hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK,
HDMI_VIDEO_MODE_HDMI);
rk3066_hdmi_config_avi(hdmi, mode);
@@ -380,7 +391,7 @@ rk3066_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
- struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder);
+ struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder);
/* Store the display mode for plugin/DPMS poweron events. */
memcpy(&hdmi->previous_mode, adj_mode, sizeof(hdmi->previous_mode));
@@ -388,7 +399,7 @@ rk3066_hdmi_encoder_mode_set(struct drm_encoder *encoder,
static void rk3066_hdmi_encoder_enable(struct drm_encoder *encoder)
{
- struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder);
+ struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder);
int mux, val;
mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder);
@@ -407,7 +418,7 @@ static void rk3066_hdmi_encoder_enable(struct drm_encoder *encoder)
static void rk3066_hdmi_encoder_disable(struct drm_encoder *encoder)
{
- struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder);
+ struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder);
DRM_DEV_DEBUG(hdmi->dev, "hdmi encoder disable\n");
@@ -455,7 +466,7 @@ struct drm_encoder_helper_funcs rk3066_hdmi_encoder_helper_funcs = {
static enum drm_connector_status
rk3066_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
- struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector);
+ struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector);
return (hdmi_readb(hdmi, HDMI_HPG_MENS_STA) & HDMI_HPG_IN_STATUS_HIGH) ?
connector_status_connected : connector_status_disconnected;
@@ -463,7 +474,7 @@ rk3066_hdmi_connector_detect(struct drm_connector *connector, bool force)
static int rk3066_hdmi_connector_get_modes(struct drm_connector *connector)
{
- struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector);
+ struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector);
struct edid *edid;
int ret = 0;
@@ -472,7 +483,6 @@ static int rk3066_hdmi_connector_get_modes(struct drm_connector *connector)
edid = drm_get_edid(connector, hdmi->ddc);
if (edid) {
- hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
@@ -496,9 +506,9 @@ rk3066_hdmi_connector_mode_valid(struct drm_connector *connector,
static struct drm_encoder *
rk3066_hdmi_connector_best_encoder(struct drm_connector *connector)
{
- struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector);
+ struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector);
- return &hdmi->encoder;
+ return &hdmi->encoder.encoder;
}
static int
@@ -538,7 +548,7 @@ struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = {
static int
rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
{
- struct drm_encoder *encoder = &hdmi->encoder;
+ struct drm_encoder *encoder = &hdmi->encoder.encoder;
struct device *dev = hdmi->dev;
encoder->possible_crtcs =
@@ -816,7 +826,7 @@ static int rk3066_hdmi_bind(struct device *dev, struct device *master,
err_cleanup_hdmi:
hdmi->connector.funcs->destroy(&hdmi->connector);
- hdmi->encoder.funcs->destroy(&hdmi->encoder);
+ hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
err_disable_i2c:
i2c_put_adapter(hdmi->ddc);
err_disable_hclk:
@@ -831,7 +841,7 @@ static void rk3066_hdmi_unbind(struct device *dev, struct device *master,
struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
hdmi->connector.funcs->destroy(&hdmi->connector);
- hdmi->encoder.funcs->destroy(&hdmi->encoder);
+ hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder);
i2c_put_adapter(hdmi->ddc);
clk_disable_unprepare(hdmi->hclk);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
index 4eaeb430c83a..67d38f53d3e5 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -7,7 +7,6 @@
*/
#include <linux/dma-mapping.h>
-#include <linux/dma-iommu.h>
#include <linux/pm_runtime.h>
#include <linux/module.h>
#include <linux/of_graph.h>
@@ -34,7 +33,6 @@
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0
-static bool is_support_iommu = true;
static const struct drm_driver rockchip_drm_driver;
/*
@@ -48,7 +46,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
struct rockchip_drm_private *private = drm_dev->dev_private;
int ret;
- if (!is_support_iommu)
+ if (!private->domain)
return 0;
ret = iommu_attach_device(private->domain, dev);
@@ -64,12 +62,22 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
struct device *dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
- struct iommu_domain *domain = private->domain;
- if (!is_support_iommu)
+ if (!private->domain)
return;
- iommu_detach_device(domain, dev);
+ iommu_detach_device(private->domain, dev);
+}
+
+void rockchip_drm_dma_init_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ struct rockchip_drm_private *private = drm_dev->dev_private;
+
+ if (!device_iommu_mapped(dev))
+ private->iommu_dev = ERR_PTR(-ENODEV);
+ else if (!private->iommu_dev)
+ private->iommu_dev = dev;
}
static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
@@ -78,10 +86,10 @@ static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
struct iommu_domain_geometry *geometry;
u64 start, end;
- if (!is_support_iommu)
+ if (IS_ERR_OR_NULL(private->iommu_dev))
return 0;
- private->domain = iommu_domain_alloc(&platform_bus_type);
+ private->domain = iommu_domain_alloc(private->iommu_dev->bus);
if (!private->domain)
return -ENOMEM;
@@ -101,7 +109,7 @@ static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
- if (!is_support_iommu)
+ if (!private->domain)
return;
drm_mm_takedown(&private->mm);
@@ -137,25 +145,25 @@ static int rockchip_drm_bind(struct device *dev)
drm_dev->dev_private = private;
- ret = rockchip_drm_init_iommu(drm_dev);
- if (ret)
- goto err_free;
-
ret = drmm_mode_config_init(drm_dev);
if (ret)
- goto err_iommu_cleanup;
+ goto err_free;
rockchip_drm_mode_config_init(drm_dev);
/* Try to bind all sub drivers. */
ret = component_bind_all(dev, drm_dev);
if (ret)
- goto err_iommu_cleanup;
+ goto err_free;
- ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
+ ret = rockchip_drm_init_iommu(drm_dev);
if (ret)
goto err_unbind_all;
+ ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
+ if (ret)
+ goto err_iommu_cleanup;
+
drm_mode_config_reset(drm_dev);
/* init kms poll for handling hpd */
@@ -170,10 +178,10 @@ static int rockchip_drm_bind(struct device *dev)
return 0;
err_kms_helper_poll_fini:
drm_kms_helper_poll_fini(drm_dev);
-err_unbind_all:
- component_unbind_all(dev, drm_dev);
err_iommu_cleanup:
rockchip_iommu_cleanup(drm_dev);
+err_unbind_all:
+ component_unbind_all(dev, drm_dev);
err_free:
drm_dev_put(drm_dev);
return ret;
@@ -237,6 +245,39 @@ static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
static int num_rockchip_sub_drivers;
/*
+ * Get the endpoint id of the remote endpoint of the given encoder. This
+ * information is used by the VOP2 driver to identify the encoder.
+ *
+ * @rkencoder: The encoder to get the remote endpoint id from
+ * @np: The encoder device node
+ * @port: The number of the port leading to the VOP2
+ * @reg: The endpoint number leading to the VOP2
+ */
+int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rkencoder,
+ struct device_node *np, int port, int reg)
+{
+ struct of_endpoint ep;
+ struct device_node *en, *ren;
+ int ret;
+
+ en = of_graph_get_endpoint_by_regs(np, port, reg);
+ if (!en)
+ return -ENOENT;
+
+ ren = of_graph_get_remote_endpoint(en);
+ if (!ren)
+ return -ENOENT;
+
+ ret = of_graph_parse_endpoint(ren, &ep);
+ if (ret)
+ return ret;
+
+ rkencoder->crtc_endpoint_id = ep.id;
+
+ return 0;
+}
+
+/*
* Check if a vop endpoint is leading to a rockchip subdriver or bridge.
* Should be called from the component bind stage of the drivers
* to ensure that all subdrivers are probed.
@@ -342,8 +383,6 @@ static int rockchip_drm_platform_of_probe(struct device *dev)
return -ENODEV;
for (i = 0;; i++) {
- struct device_node *iommu;
-
port = of_parse_phandle(np, "ports", i);
if (!port)
break;
@@ -353,21 +392,7 @@ static int rockchip_drm_platform_of_probe(struct device *dev)
continue;
}
- iommu = of_parse_phandle(port->parent, "iommus", 0);
- if (!iommu || !of_device_is_available(iommu)) {
- DRM_DEV_DEBUG(dev,
- "no iommu attached for %pOF, using non-iommu buffers\n",
- port->parent);
- /*
- * if there is a crtc not support iommu, force set all
- * crtc use non-iommu buffer.
- */
- is_support_iommu = false;
- }
-
found = true;
-
- of_node_put(iommu);
of_node_put(port);
}
@@ -456,7 +481,8 @@ static int __init rockchip_drm_init(void)
return -ENODEV;
num_rockchip_sub_drivers = 0;
- ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP);
+ ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
+ ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2);
ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
CONFIG_ROCKCHIP_LVDS);
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
index 143a48330f84..1641440837af 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -18,7 +18,7 @@
#define ROCKCHIP_MAX_FB_BUFFER 3
#define ROCKCHIP_MAX_CONNECTOR 2
-#define ROCKCHIP_MAX_CRTC 2
+#define ROCKCHIP_MAX_CRTC 4
struct drm_device;
struct drm_connector;
@@ -31,6 +31,9 @@ struct rockchip_crtc_state {
int output_bpc;
int output_flags;
bool enable_afbc;
+ u32 bus_format;
+ u32 bus_flags;
+ int color_space;
};
#define to_rockchip_crtc_state(s) \
container_of(s, struct rockchip_crtc_state, base)
@@ -44,16 +47,25 @@ struct rockchip_crtc_state {
*/
struct rockchip_drm_private {
struct iommu_domain *domain;
+ struct device *iommu_dev;
struct mutex mm_lock;
struct drm_mm mm;
};
+struct rockchip_encoder {
+ int crtc_endpoint_id;
+ struct drm_encoder encoder;
+};
+
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
struct device *dev);
void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
struct device *dev);
+void rockchip_drm_dma_init_device(struct drm_device *drm_dev,
+ struct device *dev);
int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout);
-
+int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder,
+ struct device_node *np, int port, int reg);
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
extern struct platform_driver cdn_dp_driver;
extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
@@ -63,4 +75,11 @@ extern struct platform_driver rockchip_dp_driver;
extern struct platform_driver rockchip_lvds_driver;
extern struct platform_driver vop_platform_driver;
extern struct platform_driver rk3066_hdmi_driver;
+extern struct platform_driver vop2_platform_driver;
+
+static inline struct rockchip_encoder *to_rockchip_encoder(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct rockchip_encoder, encoder);
+}
+
#endif /* _ROCKCHIP_DRM_DRV_H_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 3aa37e177667..0d2cb4f3922b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -134,4 +134,6 @@ void rockchip_drm_mode_config_init(struct drm_device *dev)
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
dev->mode_config.helper_private = &rockchip_mode_config_helpers;
+
+ dev->mode_config.normalize_zpos = true;
}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 3e8d9e2d1b67..74562d40f639 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -262,6 +262,18 @@ static bool has_rb_swapped(uint32_t format)
}
}
+static bool has_uv_swapped(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_NV42:
+ return true;
+ default:
+ return false;
+ }
+}
+
static enum vop_data_format vop_convert_format(uint32_t format)
{
switch (format) {
@@ -277,10 +289,13 @@ static enum vop_data_format vop_convert_format(uint32_t format)
case DRM_FORMAT_BGR565:
return VOP_FMT_RGB565;
case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
return VOP_FMT_YUV420SP;
case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
return VOP_FMT_YUV422SP;
case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
return VOP_FMT_YUV444SP;
default:
DRM_ERROR("unsupported format[%08x]\n", format);
@@ -899,7 +914,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane,
unsigned long offset;
dma_addr_t dma_addr;
uint32_t val;
- bool rb_swap;
+ bool rb_swap, uv_swap;
int win_index = VOP_WIN_TO_INDEX(vop_win);
int format;
int is_yuv = fb->format->is_yuv;
@@ -988,6 +1003,9 @@ static void vop_plane_atomic_update(struct drm_plane *plane,
y2r_coefficients[i],
bt601_yuv2rgb[i]);
}
+
+ uv_swap = has_uv_swapped(fb->format->format);
+ VOP_WIN_SET(vop, win, uv_swap, uv_swap);
}
if (win->phy->scl)
@@ -2118,10 +2136,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
vop_win_init(vop);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- vop->len = resource_size(res);
vop->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(vop->regs))
return PTR_ERR(vop->regs);
+ vop->len = resource_size(res);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res) {
@@ -2175,6 +2193,8 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
}
}
+ rockchip_drm_dma_init_device(drm_dev, dev);
+
return 0;
err_disable_pm_runtime:
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index 857d97cdc67c..ba88addc1a75 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -54,9 +54,23 @@ struct vop_afbc {
struct vop_reg enable;
struct vop_reg win_sel;
struct vop_reg format;
+ struct vop_reg rb_swap;
+ struct vop_reg uv_swap;
+ struct vop_reg auto_gating_en;
+ struct vop_reg block_split_en;
+ struct vop_reg pic_vir_width;
+ struct vop_reg tile_num;
struct vop_reg hreg_block_split;
+ struct vop_reg pic_offset;
struct vop_reg pic_size;
+ struct vop_reg dsp_offset;
+ struct vop_reg transform_offset;
struct vop_reg hdr_ptr;
+ struct vop_reg half_block_en;
+ struct vop_reg xmirror;
+ struct vop_reg ymirror;
+ struct vop_reg rotate_270;
+ struct vop_reg rotate_90;
struct vop_reg rstn;
};
@@ -166,6 +180,7 @@ struct vop_win_phy {
struct vop_reg gate;
struct vop_reg format;
struct vop_reg rb_swap;
+ struct vop_reg uv_swap;
struct vop_reg act_info;
struct vop_reg dsp_info;
struct vop_reg dsp_st;
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
new file mode 100644
index 000000000000..0b49fed16535
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -0,0 +1,2706 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2020 Rockchip Electronics Co., Ltd.
+ * Author: Andy Yan <[email protected]>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/swab.h>
+
+#include <drm/drm.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_flip_work.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include <uapi/linux/videodev2.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_vop2.h"
+
+/*
+ * VOP2 architecture
+ *
+ +----------+ +-------------+ +-----------+
+ | Cluster | | Sel 1 from 6| | 1 from 3 |
+ | window0 | | Layer0 | | RGB |
+ +----------+ +-------------+ +---------------+ +-------------+ +-----------+
+ +----------+ +-------------+ |N from 6 layers| | |
+ | Cluster | | Sel 1 from 6| | Overlay0 +--->| Video Port0 | +-----------+
+ | window1 | | Layer1 | | | | | | 1 from 3 |
+ +----------+ +-------------+ +---------------+ +-------------+ | LVDS |
+ +----------+ +-------------+ +-----------+
+ | Esmart | | Sel 1 from 6|
+ | window0 | | Layer2 | +---------------+ +-------------+ +-----------+
+ +----------+ +-------------+ |N from 6 Layers| | | +--> | 1 from 3 |
+ +----------+ +-------------+ --------> | Overlay1 +--->| Video Port1 | | MIPI |
+ | Esmart | | Sel 1 from 6| --------> | | | | +-----------+
+ | Window1 | | Layer3 | +---------------+ +-------------+
+ +----------+ +-------------+ +-----------+
+ +----------+ +-------------+ | 1 from 3 |
+ | Smart | | Sel 1 from 6| +---------------+ +-------------+ | HDMI |
+ | Window0 | | Layer4 | |N from 6 Layers| | | +-----------+
+ +----------+ +-------------+ | Overlay2 +--->| Video Port2 |
+ +----------+ +-------------+ | | | | +-----------+
+ | Smart | | Sel 1 from 6| +---------------+ +-------------+ | 1 from 3 |
+ | Window1 | | Layer5 | | eDP |
+ +----------+ +-------------+ +-----------+
+ *
+ */
+
+enum vop2_data_format {
+ VOP2_FMT_ARGB8888 = 0,
+ VOP2_FMT_RGB888,
+ VOP2_FMT_RGB565,
+ VOP2_FMT_XRGB101010,
+ VOP2_FMT_YUV420SP,
+ VOP2_FMT_YUV422SP,
+ VOP2_FMT_YUV444SP,
+ VOP2_FMT_YUYV422 = 8,
+ VOP2_FMT_YUYV420,
+ VOP2_FMT_VYUY422,
+ VOP2_FMT_VYUY420,
+ VOP2_FMT_YUV420SP_TILE_8x4 = 0x10,
+ VOP2_FMT_YUV420SP_TILE_16x2,
+ VOP2_FMT_YUV422SP_TILE_8x4,
+ VOP2_FMT_YUV422SP_TILE_16x2,
+ VOP2_FMT_YUV420SP_10,
+ VOP2_FMT_YUV422SP_10,
+ VOP2_FMT_YUV444SP_10,
+};
+
+enum vop2_afbc_format {
+ VOP2_AFBC_FMT_RGB565,
+ VOP2_AFBC_FMT_ARGB2101010 = 2,
+ VOP2_AFBC_FMT_YUV420_10BIT,
+ VOP2_AFBC_FMT_RGB888,
+ VOP2_AFBC_FMT_ARGB8888,
+ VOP2_AFBC_FMT_YUV420 = 9,
+ VOP2_AFBC_FMT_YUV422 = 0xb,
+ VOP2_AFBC_FMT_YUV422_10BIT = 0xe,
+ VOP2_AFBC_FMT_INVALID = -1,
+};
+
+union vop2_alpha_ctrl {
+ u32 val;
+ struct {
+ /* [0:1] */
+ u32 color_mode:1;
+ u32 alpha_mode:1;
+ /* [2:3] */
+ u32 blend_mode:2;
+ u32 alpha_cal_mode:1;
+ /* [5:7] */
+ u32 factor_mode:3;
+ /* [8:9] */
+ u32 alpha_en:1;
+ u32 src_dst_swap:1;
+ u32 reserved:6;
+ /* [16:23] */
+ u32 glb_alpha:8;
+ } bits;
+};
+
+struct vop2_alpha {
+ union vop2_alpha_ctrl src_color_ctrl;
+ union vop2_alpha_ctrl dst_color_ctrl;
+ union vop2_alpha_ctrl src_alpha_ctrl;
+ union vop2_alpha_ctrl dst_alpha_ctrl;
+};
+
+struct vop2_alpha_config {
+ bool src_premulti_en;
+ bool dst_premulti_en;
+ bool src_pixel_alpha_en;
+ bool dst_pixel_alpha_en;
+ u16 src_glb_alpha_value;
+ u16 dst_glb_alpha_value;
+};
+
+struct vop2_win {
+ struct vop2 *vop2;
+ struct drm_plane base;
+ const struct vop2_win_data *data;
+ struct regmap_field *reg[VOP2_WIN_MAX_REG];
+
+ /**
+ * @win_id: graphic window id, a cluster may be split into two
+ * graphics windows.
+ */
+ u8 win_id;
+ u8 delay;
+ u32 offset;
+
+ enum drm_plane_type type;
+};
+
+struct vop2_video_port {
+ struct drm_crtc crtc;
+ struct vop2 *vop2;
+ struct clk *dclk;
+ unsigned int id;
+ const struct vop2_video_port_regs *regs;
+ const struct vop2_video_port_data *data;
+
+ struct completion dsp_hold_completion;
+
+ /**
+ * @win_mask: Bitmask of windows attached to the video port;
+ */
+ u32 win_mask;
+
+ struct vop2_win *primary_plane;
+ struct drm_pending_vblank_event *event;
+
+ unsigned int nlayers;
+};
+
+struct vop2 {
+ struct device *dev;
+ struct drm_device *drm;
+ struct vop2_video_port vps[ROCKCHIP_MAX_CRTC];
+
+ const struct vop2_data *data;
+ /*
+ * Number of windows that are registered as plane, may be less than the
+ * total number of hardware windows.
+ */
+ u32 registered_num_wins;
+
+ void __iomem *regs;
+ struct regmap *map;
+
+ struct regmap *grf;
+
+ /* physical map length of vop2 register */
+ u32 len;
+
+ void __iomem *lut_regs;
+
+ /* protects crtc enable/disable */
+ struct mutex vop2_lock;
+
+ int irq;
+
+ /*
+ * Some global resources are shared between all video ports(crtcs), so
+ * we need a ref counter here.
+ */
+ unsigned int enable_count;
+ struct clk *hclk;
+ struct clk *aclk;
+
+ /* must be put at the end of the struct */
+ struct vop2_win win[];
+};
+
+static struct vop2_video_port *to_vop2_video_port(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct vop2_video_port, crtc);
+}
+
+static struct vop2_win *to_vop2_win(struct drm_plane *p)
+{
+ return container_of(p, struct vop2_win, base);
+}
+
+static void vop2_lock(struct vop2 *vop2)
+{
+ mutex_lock(&vop2->vop2_lock);
+}
+
+static void vop2_unlock(struct vop2 *vop2)
+{
+ mutex_unlock(&vop2->vop2_lock);
+}
+
+static void vop2_writel(struct vop2 *vop2, u32 offset, u32 v)
+{
+ regmap_write(vop2->map, offset, v);
+}
+
+static void vop2_vp_write(struct vop2_video_port *vp, u32 offset, u32 v)
+{
+ regmap_write(vp->vop2->map, vp->data->offset + offset, v);
+}
+
+static u32 vop2_readl(struct vop2 *vop2, u32 offset)
+{
+ u32 val;
+
+ regmap_read(vop2->map, offset, &val);
+
+ return val;
+}
+
+static void vop2_win_write(const struct vop2_win *win, unsigned int reg, u32 v)
+{
+ regmap_field_write(win->reg[reg], v);
+}
+
+static bool vop2_cluster_window(const struct vop2_win *win)
+{
+ return win->data->feature & WIN_FEATURE_CLUSTER;
+}
+
+static void vop2_cfg_done(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE,
+ BIT(vp->id) | RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN);
+}
+
+static void vop2_win_disable(struct vop2_win *win)
+{
+ vop2_win_write(win, VOP2_WIN_ENABLE, 0);
+
+ if (vop2_cluster_window(win))
+ vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 0);
+}
+
+static enum vop2_data_format vop2_convert_format(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ return VOP2_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return VOP2_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ return VOP2_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP2_FMT_YUV420SP;
+ case DRM_FORMAT_NV16:
+ return VOP2_FMT_YUV422SP;
+ case DRM_FORMAT_NV24:
+ return VOP2_FMT_YUV444SP;
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_YVYU:
+ return VOP2_FMT_VYUY422;
+ case DRM_FORMAT_VYUY:
+ case DRM_FORMAT_UYVY:
+ return VOP2_FMT_YUYV422;
+ default:
+ DRM_ERROR("unsupported format[%08x]\n", format);
+ return -EINVAL;
+ }
+}
+
+static enum vop2_afbc_format vop2_convert_afbc_format(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ return VOP2_AFBC_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return VOP2_AFBC_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ return VOP2_AFBC_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP2_AFBC_FMT_YUV420;
+ case DRM_FORMAT_NV16:
+ return VOP2_AFBC_FMT_YUV422;
+ default:
+ return VOP2_AFBC_FMT_INVALID;
+ }
+
+ return VOP2_AFBC_FMT_INVALID;
+}
+
+static bool vop2_win_rb_swap(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_BGR888:
+ case DRM_FORMAT_BGR565:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_afbc_rb_swap(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV24:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_afbc_uv_swap(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV16:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_win_uv_swap(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV24:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_win_dither_up(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_RGB565:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_output_uv_swap(u32 bus_format, u32 output_mode)
+{
+ /*
+ * FIXME:
+ *
+ * There is no media type for YUV444 output,
+ * so when out_mode is AAAA or P888, assume output is YUV444 on
+ * yuv format.
+ *
+ * From H/W testing, YUV444 mode need a rb swap.
+ */
+ if (bus_format == MEDIA_BUS_FMT_YVYU8_1X16 ||
+ bus_format == MEDIA_BUS_FMT_VYUY8_1X16 ||
+ bus_format == MEDIA_BUS_FMT_YVYU8_2X8 ||
+ bus_format == MEDIA_BUS_FMT_VYUY8_2X8 ||
+ ((bus_format == MEDIA_BUS_FMT_YUV8_1X24 ||
+ bus_format == MEDIA_BUS_FMT_YUV10_1X30) &&
+ (output_mode == ROCKCHIP_OUT_MODE_AAAA ||
+ output_mode == ROCKCHIP_OUT_MODE_P888)))
+ return true;
+ else
+ return false;
+}
+
+static bool is_yuv_output(u32 bus_format)
+{
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rockchip_afbc(struct drm_plane *plane, u64 modifier)
+{
+ int i;
+
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ return false;
+
+ for (i = 0 ; i < plane->modifier_count; i++)
+ if (plane->modifiers[i] == modifier)
+ return true;
+
+ return false;
+}
+
+static bool rockchip_vop2_mod_supported(struct drm_plane *plane, u32 format,
+ u64 modifier)
+{
+ struct vop2_win *win = to_vop2_win(plane);
+ struct vop2 *vop2 = win->vop2;
+
+ if (modifier == DRM_FORMAT_MOD_INVALID)
+ return false;
+
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ return true;
+
+ if (!rockchip_afbc(plane, modifier)) {
+ drm_err(vop2->drm, "Unsupported format modifier 0x%llx\n",
+ modifier);
+
+ return false;
+ }
+
+ return vop2_convert_afbc_format(format) >= 0;
+}
+
+static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate,
+ bool afbc_half_block_en)
+{
+ struct drm_rect *src = &pstate->src;
+ struct drm_framebuffer *fb = pstate->fb;
+ u32 bpp = fb->format->cpp[0] * 8;
+ u32 vir_width = (fb->pitches[0] << 3) / bpp;
+ u32 width = drm_rect_width(src) >> 16;
+ u32 height = drm_rect_height(src) >> 16;
+ u32 act_xoffset = src->x1 >> 16;
+ u32 act_yoffset = src->y1 >> 16;
+ u32 align16_crop = 0;
+ u32 align64_crop = 0;
+ u32 height_tmp;
+ u8 tx, ty;
+ u8 bottom_crop_line_num = 0;
+
+ /* 16 pixel align */
+ if (height & 0xf)
+ align16_crop = 16 - (height & 0xf);
+
+ height_tmp = height + align16_crop;
+
+ /* 64 pixel align */
+ if (height_tmp & 0x3f)
+ align64_crop = 64 - (height_tmp & 0x3f);
+
+ bottom_crop_line_num = align16_crop + align64_crop;
+
+ switch (pstate->rotation &
+ (DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y |
+ DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270)) {
+ case DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y:
+ tx = 16 - ((act_xoffset + width) & 0xf);
+ ty = bottom_crop_line_num - act_yoffset;
+ break;
+ case DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90:
+ tx = bottom_crop_line_num - act_yoffset;
+ ty = vir_width - width - act_xoffset;
+ break;
+ case DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_270:
+ tx = act_yoffset;
+ ty = act_xoffset;
+ break;
+ case DRM_MODE_REFLECT_X:
+ tx = 16 - ((act_xoffset + width) & 0xf);
+ ty = act_yoffset;
+ break;
+ case DRM_MODE_REFLECT_Y:
+ tx = act_xoffset;
+ ty = bottom_crop_line_num - act_yoffset;
+ break;
+ case DRM_MODE_ROTATE_90:
+ tx = bottom_crop_line_num - act_yoffset;
+ ty = act_xoffset;
+ break;
+ case DRM_MODE_ROTATE_270:
+ tx = act_yoffset;
+ ty = vir_width - width - act_xoffset;
+ break;
+ case 0:
+ tx = act_xoffset;
+ ty = act_yoffset;
+ break;
+ }
+
+ if (afbc_half_block_en)
+ ty &= 0x7f;
+
+#define TRANSFORM_XOFFSET GENMASK(7, 0)
+#define TRANSFORM_YOFFSET GENMASK(23, 16)
+ return FIELD_PREP(TRANSFORM_XOFFSET, tx) |
+ FIELD_PREP(TRANSFORM_YOFFSET, ty);
+}
+
+/*
+ * A Cluster window has 2048 x 16 line buffer, which can
+ * works at 2048 x 16(Full) or 4096 x 8 (Half) mode.
+ * for Cluster_lb_mode register:
+ * 0: half mode, for plane input width range 2048 ~ 4096
+ * 1: half mode, for cluster work at 2 * 2048 plane mode
+ * 2: half mode, for rotate_90/270 mode
+ *
+ */
+static int vop2_get_cluster_lb_mode(struct vop2_win *win,
+ struct drm_plane_state *pstate)
+{
+ if ((pstate->rotation & DRM_MODE_ROTATE_270) ||
+ (pstate->rotation & DRM_MODE_ROTATE_90))
+ return 2;
+ else
+ return 0;
+}
+
+static u16 vop2_scale_factor(u32 src, u32 dst)
+{
+ u32 fac;
+ int shift;
+
+ if (src == dst)
+ return 0;
+
+ if (dst < 2)
+ return U16_MAX;
+
+ if (src < 2)
+ return 0;
+
+ if (src > dst)
+ shift = 12;
+ else
+ shift = 16;
+
+ src--;
+ dst--;
+
+ fac = DIV_ROUND_UP(src << shift, dst) - 1;
+
+ if (fac > U16_MAX)
+ return U16_MAX;
+
+ return fac;
+}
+
+static void vop2_setup_scale(struct vop2 *vop2, const struct vop2_win *win,
+ u32 src_w, u32 src_h, u32 dst_w,
+ u32 dst_h, u32 pixel_format)
+{
+ const struct drm_format_info *info;
+ u16 hor_scl_mode, ver_scl_mode;
+ u16 hscl_filter_mode, vscl_filter_mode;
+ u8 gt2 = 0;
+ u8 gt4 = 0;
+ u32 val;
+
+ info = drm_format_info(pixel_format);
+
+ if (src_h >= (4 * dst_h)) {
+ gt4 = 1;
+ src_h >>= 2;
+ } else if (src_h >= (2 * dst_h)) {
+ gt2 = 1;
+ src_h >>= 1;
+ }
+
+ hor_scl_mode = scl_get_scl_mode(src_w, dst_w);
+ ver_scl_mode = scl_get_scl_mode(src_h, dst_h);
+
+ if (hor_scl_mode == SCALE_UP)
+ hscl_filter_mode = VOP2_SCALE_UP_BIC;
+ else
+ hscl_filter_mode = VOP2_SCALE_DOWN_BIL;
+
+ if (ver_scl_mode == SCALE_UP)
+ vscl_filter_mode = VOP2_SCALE_UP_BIL;
+ else
+ vscl_filter_mode = VOP2_SCALE_DOWN_BIL;
+
+ /*
+ * RK3568 VOP Esmart/Smart dsp_w should be even pixel
+ * at scale down mode
+ */
+ if (!(win->data->feature & WIN_FEATURE_AFBDC)) {
+ if ((hor_scl_mode == SCALE_DOWN) && (dst_w & 0x1)) {
+ drm_dbg(vop2->drm, "%s dst_w[%d] should align as 2 pixel\n",
+ win->data->name, dst_w);
+ dst_w++;
+ }
+ }
+
+ val = vop2_scale_factor(src_w, dst_w);
+ vop2_win_write(win, VOP2_WIN_SCALE_YRGB_X, val);
+ val = vop2_scale_factor(src_h, dst_h);
+ vop2_win_write(win, VOP2_WIN_SCALE_YRGB_Y, val);
+
+ vop2_win_write(win, VOP2_WIN_VSD_YRGB_GT4, gt4);
+ vop2_win_write(win, VOP2_WIN_VSD_YRGB_GT2, gt2);
+
+ vop2_win_write(win, VOP2_WIN_YRGB_HOR_SCL_MODE, hor_scl_mode);
+ vop2_win_write(win, VOP2_WIN_YRGB_VER_SCL_MODE, ver_scl_mode);
+
+ if (vop2_cluster_window(win))
+ return;
+
+ vop2_win_write(win, VOP2_WIN_YRGB_HSCL_FILTER_MODE, hscl_filter_mode);
+ vop2_win_write(win, VOP2_WIN_YRGB_VSCL_FILTER_MODE, vscl_filter_mode);
+
+ if (info->is_yuv) {
+ src_w /= info->hsub;
+ src_h /= info->vsub;
+
+ gt4 = 0;
+ gt2 = 0;
+
+ if (src_h >= (4 * dst_h)) {
+ gt4 = 1;
+ src_h >>= 2;
+ } else if (src_h >= (2 * dst_h)) {
+ gt2 = 1;
+ src_h >>= 1;
+ }
+
+ hor_scl_mode = scl_get_scl_mode(src_w, dst_w);
+ ver_scl_mode = scl_get_scl_mode(src_h, dst_h);
+
+ val = vop2_scale_factor(src_w, dst_w);
+ vop2_win_write(win, VOP2_WIN_SCALE_CBCR_X, val);
+
+ val = vop2_scale_factor(src_h, dst_h);
+ vop2_win_write(win, VOP2_WIN_SCALE_CBCR_Y, val);
+
+ vop2_win_write(win, VOP2_WIN_VSD_CBCR_GT4, gt4);
+ vop2_win_write(win, VOP2_WIN_VSD_CBCR_GT2, gt2);
+ vop2_win_write(win, VOP2_WIN_CBCR_HOR_SCL_MODE, hor_scl_mode);
+ vop2_win_write(win, VOP2_WIN_CBCR_VER_SCL_MODE, ver_scl_mode);
+ vop2_win_write(win, VOP2_WIN_CBCR_HSCL_FILTER_MODE, hscl_filter_mode);
+ vop2_win_write(win, VOP2_WIN_CBCR_VSCL_FILTER_MODE, vscl_filter_mode);
+ }
+}
+
+static int vop2_convert_csc_mode(int csc_mode)
+{
+ switch (csc_mode) {
+ case V4L2_COLORSPACE_SMPTE170M:
+ case V4L2_COLORSPACE_470_SYSTEM_M:
+ case V4L2_COLORSPACE_470_SYSTEM_BG:
+ return CSC_BT601L;
+ case V4L2_COLORSPACE_REC709:
+ case V4L2_COLORSPACE_SMPTE240M:
+ case V4L2_COLORSPACE_DEFAULT:
+ return CSC_BT709L;
+ case V4L2_COLORSPACE_JPEG:
+ return CSC_BT601F;
+ case V4L2_COLORSPACE_BT2020:
+ return CSC_BT2020;
+ default:
+ return CSC_BT709L;
+ }
+}
+
+/*
+ * colorspace path:
+ * Input Win csc Output
+ * 1. YUV(2020) --> Y2R->2020To709->R2Y --> YUV_OUTPUT(601/709)
+ * RGB --> R2Y __/
+ *
+ * 2. YUV(2020) --> bypasss --> YUV_OUTPUT(2020)
+ * RGB --> 709To2020->R2Y __/
+ *
+ * 3. YUV(2020) --> Y2R->2020To709 --> RGB_OUTPUT(709)
+ * RGB --> R2Y __/
+ *
+ * 4. YUV(601/709)-> Y2R->709To2020->R2Y --> YUV_OUTPUT(2020)
+ * RGB --> 709To2020->R2Y __/
+ *
+ * 5. YUV(601/709)-> bypass --> YUV_OUTPUT(709)
+ * RGB --> R2Y __/
+ *
+ * 6. YUV(601/709)-> bypass --> YUV_OUTPUT(601)
+ * RGB --> R2Y(601) __/
+ *
+ * 7. YUV --> Y2R(709) --> RGB_OUTPUT(709)
+ * RGB --> bypass __/
+ *
+ * 8. RGB --> 709To2020->R2Y --> YUV_OUTPUT(2020)
+ *
+ * 9. RGB --> R2Y(709) --> YUV_OUTPUT(709)
+ *
+ * 10. RGB --> R2Y(601) --> YUV_OUTPUT(601)
+ *
+ * 11. RGB --> bypass --> RGB_OUTPUT(709)
+ */
+
+static void vop2_setup_csc_mode(struct vop2_video_port *vp,
+ struct vop2_win *win,
+ struct drm_plane_state *pstate)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state);
+ int is_input_yuv = pstate->fb->format->is_yuv;
+ int is_output_yuv = is_yuv_output(vcstate->bus_format);
+ int input_csc = V4L2_COLORSPACE_DEFAULT;
+ int output_csc = vcstate->color_space;
+ bool r2y_en, y2r_en;
+ int csc_mode;
+
+ if (is_input_yuv && !is_output_yuv) {
+ y2r_en = true;
+ r2y_en = false;
+ csc_mode = vop2_convert_csc_mode(input_csc);
+ } else if (!is_input_yuv && is_output_yuv) {
+ y2r_en = false;
+ r2y_en = true;
+ csc_mode = vop2_convert_csc_mode(output_csc);
+ } else {
+ y2r_en = false;
+ r2y_en = false;
+ csc_mode = false;
+ }
+
+ vop2_win_write(win, VOP2_WIN_Y2R_EN, y2r_en);
+ vop2_win_write(win, VOP2_WIN_R2Y_EN, r2y_en);
+ vop2_win_write(win, VOP2_WIN_CSC_MODE, csc_mode);
+}
+
+static void vop2_crtc_enable_irq(struct vop2_video_port *vp, u32 irq)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ vop2_writel(vop2, RK3568_VP_INT_CLR(vp->id), irq << 16 | irq);
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16 | irq);
+}
+
+static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
+}
+
+static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
+{
+ int ret;
+
+ ret = clk_prepare_enable(vop2->hclk);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to enable hclk - %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(vop2->aclk);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to enable aclk - %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ clk_disable_unprepare(vop2->hclk);
+
+ return ret;
+}
+
+static void vop2_enable(struct vop2 *vop2)
+{
+ int ret;
+
+ ret = pm_runtime_get_sync(vop2->dev);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to get pm runtime: %d\n", ret);
+ return;
+ }
+
+ ret = vop2_core_clks_prepare_enable(vop2);
+ if (ret) {
+ pm_runtime_put_sync(vop2->dev);
+ return;
+ }
+
+ ret = rockchip_drm_dma_attach_device(vop2->drm, vop2->dev);
+ if (ret) {
+ drm_err(vop2->drm, "failed to attach dma mapping, %d\n", ret);
+ return;
+ }
+
+ if (vop2->data->soc_id == 3566)
+ vop2_writel(vop2, RK3568_OTP_WIN_EN, 1);
+
+ vop2_writel(vop2, RK3568_REG_CFG_DONE, RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN);
+
+ /*
+ * Disable auto gating, this is a workaround to
+ * avoid display image shift when a window enabled.
+ */
+ regmap_clear_bits(vop2->map, RK3568_SYS_AUTO_GATING_CTRL,
+ RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN);
+
+ vop2_writel(vop2, RK3568_SYS0_INT_CLR,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+ vop2_writel(vop2, RK3568_SYS0_INT_EN,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+ vop2_writel(vop2, RK3568_SYS1_INT_CLR,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+ vop2_writel(vop2, RK3568_SYS1_INT_EN,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+}
+
+static void vop2_disable(struct vop2 *vop2)
+{
+ rockchip_drm_dma_detach_device(vop2->drm, vop2->dev);
+
+ pm_runtime_put_sync(vop2->dev);
+
+ clk_disable_unprepare(vop2->aclk);
+ clk_disable_unprepare(vop2->hclk);
+}
+
+static void vop2_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct vop2 *vop2 = vp->vop2;
+ int ret;
+
+ vop2_lock(vop2);
+
+ drm_crtc_vblank_off(crtc);
+
+ /*
+ * Vop standby will take effect at end of current frame,
+ * if dsp hold valid irq happen, it means standby complete.
+ *
+ * we must wait standby complete when we want to disable aclk,
+ * if not, memory bus maybe dead.
+ */
+ reinit_completion(&vp->dsp_hold_completion);
+
+ vop2_crtc_enable_irq(vp, VP_INT_DSP_HOLD_VALID);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, RK3568_VP_DSP_CTRL__STANDBY);
+
+ ret = wait_for_completion_timeout(&vp->dsp_hold_completion,
+ msecs_to_jiffies(50));
+ if (!ret)
+ drm_info(vop2->drm, "wait for vp%d dsp_hold timeout\n", vp->id);
+
+ vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID);
+
+ clk_disable_unprepare(vp->dclk);
+
+ vop2->enable_count--;
+
+ if (!vop2->enable_count)
+ vop2_disable(vop2);
+
+ vop2_unlock(vop2);
+
+ if (crtc->state->event && !crtc->state->active) {
+ spin_lock_irq(&crtc->dev->event_lock);
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ crtc->state->event = NULL;
+ }
+}
+
+static int vop2_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *astate)
+{
+ struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(astate, plane);
+ struct drm_framebuffer *fb = pstate->fb;
+ struct drm_crtc *crtc = pstate->crtc;
+ struct drm_crtc_state *cstate;
+ struct vop2_video_port *vp;
+ struct vop2 *vop2;
+ const struct vop2_data *vop2_data;
+ struct drm_rect *dest = &pstate->dst;
+ struct drm_rect *src = &pstate->src;
+ int min_scale = FRAC_16_16(1, 8);
+ int max_scale = FRAC_16_16(8, 1);
+ int format;
+ int ret;
+
+ if (!crtc)
+ return 0;
+
+ vp = to_vop2_video_port(crtc);
+ vop2 = vp->vop2;
+ vop2_data = vop2->data;
+
+ cstate = drm_atomic_get_existing_crtc_state(pstate->state, crtc);
+ if (WARN_ON(!cstate))
+ return -EINVAL;
+
+ ret = drm_atomic_helper_check_plane_state(pstate, cstate,
+ min_scale, max_scale,
+ true, true);
+ if (ret)
+ return ret;
+
+ if (!pstate->visible)
+ return 0;
+
+ format = vop2_convert_format(fb->format->format);
+ if (format < 0)
+ return format;
+
+ if (drm_rect_width(src) >> 16 < 4 || drm_rect_height(src) >> 16 < 4 ||
+ drm_rect_width(dest) < 4 || drm_rect_width(dest) < 4) {
+ drm_err(vop2->drm, "Invalid size: %dx%d->%dx%d, min size is 4x4\n",
+ drm_rect_width(src) >> 16, drm_rect_height(src) >> 16,
+ drm_rect_width(dest), drm_rect_height(dest));
+ pstate->visible = false;
+ return 0;
+ }
+
+ if (drm_rect_width(src) >> 16 > vop2_data->max_input.width ||
+ drm_rect_height(src) >> 16 > vop2_data->max_input.height) {
+ drm_err(vop2->drm, "Invalid source: %dx%d. max input: %dx%d\n",
+ drm_rect_width(src) >> 16,
+ drm_rect_height(src) >> 16,
+ vop2_data->max_input.width,
+ vop2_data->max_input.height);
+ return -EINVAL;
+ }
+
+ /*
+ * Src.x1 can be odd when do clip, but yuv plane start point
+ * need align with 2 pixel.
+ */
+ if (fb->format->is_yuv && ((pstate->src.x1 >> 16) % 2)) {
+ drm_err(vop2->drm, "Invalid Source: Yuv format not support odd xpos\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vop2_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *old_pstate = drm_atomic_get_old_plane_state(state, plane);
+ struct vop2_win *win = to_vop2_win(plane);
+ struct vop2 *vop2 = win->vop2;
+
+ drm_dbg(vop2->drm, "%s disable\n", win->data->name);
+
+ if (!old_pstate->crtc)
+ return;
+
+ vop2_win_disable(win);
+ vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0);
+}
+
+/*
+ * The color key is 10 bit, so all format should
+ * convert to 10 bit here.
+ */
+static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key)
+{
+ struct drm_plane_state *pstate = plane->state;
+ struct drm_framebuffer *fb = pstate->fb;
+ struct vop2_win *win = to_vop2_win(plane);
+ u32 color_key_en = 0;
+ u32 r = 0;
+ u32 g = 0;
+ u32 b = 0;
+
+ if (!(color_key & VOP2_COLOR_KEY_MASK) || fb->format->is_yuv) {
+ vop2_win_write(win, VOP2_WIN_COLOR_KEY_EN, 0);
+ return;
+ }
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ r = (color_key & 0xf800) >> 11;
+ g = (color_key & 0x7e0) >> 5;
+ b = (color_key & 0x1f);
+ r <<= 5;
+ g <<= 4;
+ b <<= 5;
+ color_key_en = 1;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ r = (color_key & 0xff0000) >> 16;
+ g = (color_key & 0xff00) >> 8;
+ b = (color_key & 0xff);
+ r <<= 2;
+ g <<= 2;
+ b <<= 2;
+ color_key_en = 1;
+ break;
+ }
+
+ vop2_win_write(win, VOP2_WIN_COLOR_KEY_EN, color_key_en);
+ vop2_win_write(win, VOP2_WIN_COLOR_KEY, (r << 20) | (g << 10) | b);
+}
+
+static void vop2_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *pstate = plane->state;
+ struct drm_crtc *crtc = pstate->crtc;
+ struct vop2_win *win = to_vop2_win(plane);
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
+ struct vop2 *vop2 = win->vop2;
+ struct drm_framebuffer *fb = pstate->fb;
+ u32 bpp = fb->format->cpp[0] * 8;
+ u32 actual_w, actual_h, dsp_w, dsp_h;
+ u32 act_info, dsp_info;
+ u32 format;
+ u32 afbc_format;
+ u32 rb_swap;
+ u32 uv_swap;
+ struct drm_rect *src = &pstate->src;
+ struct drm_rect *dest = &pstate->dst;
+ u32 afbc_tile_num;
+ u32 transform_offset;
+ bool dither_up;
+ bool xmirror = pstate->rotation & DRM_MODE_REFLECT_X ? true : false;
+ bool ymirror = pstate->rotation & DRM_MODE_REFLECT_Y ? true : false;
+ bool rotate_270 = pstate->rotation & DRM_MODE_ROTATE_270;
+ bool rotate_90 = pstate->rotation & DRM_MODE_ROTATE_90;
+ struct rockchip_gem_object *rk_obj;
+ unsigned long offset;
+ bool afbc_en;
+ dma_addr_t yrgb_mst;
+ dma_addr_t uv_mst;
+
+ /*
+ * can't update plane when vop2 is disabled.
+ */
+ if (WARN_ON(!crtc))
+ return;
+
+ if (!pstate->visible) {
+ vop2_plane_atomic_disable(plane, state);
+ return;
+ }
+
+ afbc_en = rockchip_afbc(plane, fb->modifier);
+
+ offset = (src->x1 >> 16) * fb->format->cpp[0];
+
+ /*
+ * AFBC HDR_PTR must set to the zero offset of the framebuffer.
+ */
+ if (afbc_en)
+ offset = 0;
+ else if (pstate->rotation & DRM_MODE_REFLECT_Y)
+ offset += ((src->y2 >> 16) - 1) * fb->pitches[0];
+ else
+ offset += (src->y1 >> 16) * fb->pitches[0];
+
+ rk_obj = to_rockchip_obj(fb->obj[0]);
+
+ yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0];
+ if (fb->format->is_yuv) {
+ int hsub = fb->format->hsub;
+ int vsub = fb->format->vsub;
+
+ offset = (src->x1 >> 16) * fb->format->cpp[1] / hsub;
+ offset += (src->y1 >> 16) * fb->pitches[1] / vsub;
+
+ if ((pstate->rotation & DRM_MODE_REFLECT_Y) && !afbc_en)
+ offset += fb->pitches[1] * ((pstate->src_h >> 16) - 2) / vsub;
+
+ rk_obj = to_rockchip_obj(fb->obj[0]);
+ uv_mst = rk_obj->dma_addr + offset + fb->offsets[1];
+ }
+
+ actual_w = drm_rect_width(src) >> 16;
+ actual_h = drm_rect_height(src) >> 16;
+ dsp_w = drm_rect_width(dest);
+
+ if (dest->x1 + dsp_w > adjusted_mode->hdisplay) {
+ drm_err(vop2->drm, "vp%d %s dest->x1[%d] + dsp_w[%d] exceed mode hdisplay[%d]\n",
+ vp->id, win->data->name, dest->x1, dsp_w, adjusted_mode->hdisplay);
+ dsp_w = adjusted_mode->hdisplay - dest->x1;
+ if (dsp_w < 4)
+ dsp_w = 4;
+ actual_w = dsp_w * actual_w / drm_rect_width(dest);
+ }
+
+ dsp_h = drm_rect_height(dest);
+
+ if (dest->y1 + dsp_h > adjusted_mode->vdisplay) {
+ drm_err(vop2->drm, "vp%d %s dest->y1[%d] + dsp_h[%d] exceed mode vdisplay[%d]\n",
+ vp->id, win->data->name, dest->y1, dsp_h, adjusted_mode->vdisplay);
+ dsp_h = adjusted_mode->vdisplay - dest->y1;
+ if (dsp_h < 4)
+ dsp_h = 4;
+ actual_h = dsp_h * actual_h / drm_rect_height(dest);
+ }
+
+ /*
+ * This is workaround solution for IC design:
+ * esmart can't support scale down when actual_w % 16 == 1.
+ */
+ if (!(win->data->feature & WIN_FEATURE_AFBDC)) {
+ if (actual_w > dsp_w && (actual_w & 0xf) == 1) {
+ drm_err(vop2->drm, "vp%d %s act_w[%d] MODE 16 == 1\n",
+ vp->id, win->data->name, actual_w);
+ actual_w -= 1;
+ }
+ }
+
+ if (afbc_en && actual_w % 4) {
+ drm_err(vop2->drm, "vp%d %s actual_w[%d] not 4 pixel aligned\n",
+ vp->id, win->data->name, actual_w);
+ actual_w = ALIGN_DOWN(actual_w, 4);
+ }
+
+ act_info = (actual_h - 1) << 16 | ((actual_w - 1) & 0xffff);
+ dsp_info = (dsp_h - 1) << 16 | ((dsp_w - 1) & 0xffff);
+
+ format = vop2_convert_format(fb->format->format);
+
+ drm_dbg(vop2->drm, "vp%d update %s[%dx%d->%dx%d@%dx%d] fmt[%p4cc_%s] addr[%pad]\n",
+ vp->id, win->data->name, actual_w, actual_h, dsp_w, dsp_h,
+ dest->x1, dest->y1,
+ &fb->format->format,
+ afbc_en ? "AFBC" : "", &yrgb_mst);
+
+ if (afbc_en) {
+ u32 stride;
+
+ /* the afbc superblock is 16 x 16 */
+ afbc_format = vop2_convert_afbc_format(fb->format->format);
+
+ /* Enable color transform for YTR */
+ if (fb->modifier & AFBC_FORMAT_MOD_YTR)
+ afbc_format |= (1 << 4);
+
+ afbc_tile_num = ALIGN(actual_w, 16) >> 4;
+
+ /*
+ * AFBC pic_vir_width is count by pixel, this is different
+ * with WIN_VIR_STRIDE.
+ */
+ stride = (fb->pitches[0] << 3) / bpp;
+ if ((stride & 0x3f) && (xmirror || rotate_90 || rotate_270))
+ drm_err(vop2->drm, "vp%d %s stride[%d] not 64 pixel aligened\n",
+ vp->id, win->data->name, stride);
+
+ rb_swap = vop2_afbc_rb_swap(fb->format->format);
+ uv_swap = vop2_afbc_uv_swap(fb->format->format);
+ /*
+ * This is a workaround for crazy IC design, Cluster
+ * and Esmart/Smart use different format configuration map:
+ * YUV420_10BIT: 0x10 for Cluster, 0x14 for Esmart/Smart.
+ *
+ * This is one thing we can make the convert simple:
+ * AFBCD decode all the YUV data to YUV444. So we just
+ * set all the yuv 10 bit to YUV444_10.
+ */
+ if (fb->format->is_yuv && bpp == 10)
+ format = VOP2_CLUSTER_YUV444_10;
+
+ if (vop2_cluster_window(win))
+ vop2_win_write(win, VOP2_WIN_AFBC_ENABLE, 1);
+ vop2_win_write(win, VOP2_WIN_AFBC_FORMAT, afbc_format);
+ vop2_win_write(win, VOP2_WIN_AFBC_RB_SWAP, rb_swap);
+ vop2_win_write(win, VOP2_WIN_AFBC_UV_SWAP, uv_swap);
+ vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 0);
+ vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 0);
+ if (pstate->rotation & (DRM_MODE_ROTATE_270 | DRM_MODE_ROTATE_90)) {
+ vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 0);
+ transform_offset = vop2_afbc_transform_offset(pstate, false);
+ } else {
+ vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 1);
+ transform_offset = vop2_afbc_transform_offset(pstate, true);
+ }
+ vop2_win_write(win, VOP2_WIN_AFBC_HDR_PTR, yrgb_mst);
+ vop2_win_write(win, VOP2_WIN_AFBC_PIC_SIZE, act_info);
+ vop2_win_write(win, VOP2_WIN_AFBC_TRANSFORM_OFFSET, transform_offset);
+ vop2_win_write(win, VOP2_WIN_AFBC_PIC_OFFSET, ((src->x1 >> 16) | src->y1));
+ vop2_win_write(win, VOP2_WIN_AFBC_DSP_OFFSET, (dest->x1 | (dest->y1 << 16)));
+ vop2_win_write(win, VOP2_WIN_AFBC_PIC_VIR_WIDTH, stride);
+ vop2_win_write(win, VOP2_WIN_AFBC_TILE_NUM, afbc_tile_num);
+ vop2_win_write(win, VOP2_WIN_XMIRROR, xmirror);
+ vop2_win_write(win, VOP2_WIN_AFBC_ROTATE_270, rotate_270);
+ vop2_win_write(win, VOP2_WIN_AFBC_ROTATE_90, rotate_90);
+ } else {
+ vop2_win_write(win, VOP2_WIN_YRGB_VIR, DIV_ROUND_UP(fb->pitches[0], 4));
+ }
+
+ vop2_win_write(win, VOP2_WIN_YMIRROR, ymirror);
+
+ if (rotate_90 || rotate_270) {
+ act_info = swahw32(act_info);
+ actual_w = drm_rect_height(src) >> 16;
+ actual_h = drm_rect_width(src) >> 16;
+ }
+
+ vop2_win_write(win, VOP2_WIN_FORMAT, format);
+ vop2_win_write(win, VOP2_WIN_YRGB_MST, yrgb_mst);
+
+ rb_swap = vop2_win_rb_swap(fb->format->format);
+ vop2_win_write(win, VOP2_WIN_RB_SWAP, rb_swap);
+ if (!vop2_cluster_window(win)) {
+ uv_swap = vop2_win_uv_swap(fb->format->format);
+ vop2_win_write(win, VOP2_WIN_UV_SWAP, uv_swap);
+ }
+
+ if (fb->format->is_yuv) {
+ vop2_win_write(win, VOP2_WIN_UV_VIR, DIV_ROUND_UP(fb->pitches[1], 4));
+ vop2_win_write(win, VOP2_WIN_UV_MST, uv_mst);
+ }
+
+ vop2_setup_scale(vop2, win, actual_w, actual_h, dsp_w, dsp_h, fb->format->format);
+ if (!vop2_cluster_window(win))
+ vop2_plane_setup_color_key(plane, 0);
+ vop2_win_write(win, VOP2_WIN_ACT_INFO, act_info);
+ vop2_win_write(win, VOP2_WIN_DSP_INFO, dsp_info);
+ vop2_win_write(win, VOP2_WIN_DSP_ST, dest->y1 << 16 | (dest->x1 & 0xffff));
+
+ vop2_setup_csc_mode(vp, win, pstate);
+
+ dither_up = vop2_win_dither_up(fb->format->format);
+ vop2_win_write(win, VOP2_WIN_DITHER_UP, dither_up);
+
+ vop2_win_write(win, VOP2_WIN_ENABLE, 1);
+
+ if (vop2_cluster_window(win)) {
+ int lb_mode = vop2_get_cluster_lb_mode(win, pstate);
+
+ vop2_win_write(win, VOP2_WIN_CLUSTER_LB_MODE, lb_mode);
+ vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 1);
+ }
+}
+
+static const struct drm_plane_helper_funcs vop2_plane_helper_funcs = {
+ .atomic_check = vop2_plane_atomic_check,
+ .atomic_update = vop2_plane_atomic_update,
+ .atomic_disable = vop2_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs vop2_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+ .format_mod_supported = rockchip_vop2_mod_supported,
+};
+
+static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+
+ vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
+
+ return 0;
+}
+
+static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+}
+
+static bool vop2_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ drm_mode_set_crtcinfo(adj_mode, CRTC_INTERLACE_HALVE_V |
+ CRTC_STEREO_DOUBLE);
+
+ return true;
+}
+
+static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+
+ switch (vcstate->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__DITHER_DOWN_EN;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__DITHER_DOWN_EN;
+ *dsp_ctrl |= RGB888_TO_RGB666;
+ break;
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN;
+ break;
+ default:
+ break;
+ }
+
+ if (vcstate->output_mode != ROCKCHIP_OUT_MODE_AAAA)
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN;
+
+ *dsp_ctrl |= FIELD_PREP(RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL,
+ DITHER_DOWN_ALLEGRO);
+}
+
+static void vop2_post_config(struct drm_crtc *crtc)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ u16 vtotal = mode->crtc_vtotal;
+ u16 hdisplay = mode->crtc_hdisplay;
+ u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start;
+ u16 vdisplay = mode->crtc_vdisplay;
+ u16 vact_st = mode->crtc_vtotal - mode->crtc_vsync_start;
+ u32 left_margin = 100, right_margin = 100;
+ u32 top_margin = 100, bottom_margin = 100;
+ u16 hsize = hdisplay * (left_margin + right_margin) / 200;
+ u16 vsize = vdisplay * (top_margin + bottom_margin) / 200;
+ u16 hact_end, vact_end;
+ u32 val;
+
+ vsize = rounddown(vsize, 2);
+ hsize = rounddown(hsize, 2);
+ hact_st += hdisplay * (100 - left_margin) / 200;
+ hact_end = hact_st + hsize;
+ val = hact_st << 16;
+ val |= hact_end;
+ vop2_vp_write(vp, RK3568_VP_POST_DSP_HACT_INFO, val);
+ vact_st += vdisplay * (100 - top_margin) / 200;
+ vact_end = vact_st + vsize;
+ val = vact_st << 16;
+ val |= vact_end;
+ vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO, val);
+ val = scl_cal_scale2(vdisplay, vsize) << 16;
+ val |= scl_cal_scale2(hdisplay, hsize);
+ vop2_vp_write(vp, RK3568_VP_POST_SCL_FACTOR_YRGB, val);
+
+ val = 0;
+ if (hdisplay != hsize)
+ val |= RK3568_VP_POST_SCL_CTRL__HSCALEDOWN;
+ if (vdisplay != vsize)
+ val |= RK3568_VP_POST_SCL_CTRL__VSCALEDOWN;
+ vop2_vp_write(vp, RK3568_VP_POST_SCL_CTRL, val);
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+ u16 vact_st_f1 = vtotal + vact_st + 1;
+ u16 vact_end_f1 = vact_st_f1 + vsize;
+
+ val = vact_st_f1 << 16 | vact_end_f1;
+ vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val);
+ }
+
+ vop2_vp_write(vp, RK3568_VP_DSP_BG, 0);
+}
+
+static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id,
+ u32 polflags)
+{
+ struct vop2 *vop2 = vp->vop2;
+ u32 die, dip;
+
+ die = vop2_readl(vop2, RK3568_DSP_IF_EN);
+ dip = vop2_readl(vop2, RK3568_DSP_IF_POL);
+
+ switch (id) {
+ case ROCKCHIP_VOP2_EP_RGB0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_RGB_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_RGB |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_RGB_MUX, vp->id);
+ if (polflags & POLFLAG_DCLK_INV)
+ regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16) | BIT(3));
+ else
+ regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16));
+ break;
+ case ROCKCHIP_VOP2_EP_HDMI0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_HDMI_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_HDMI |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_HDMI_MUX, vp->id);
+ break;
+ case ROCKCHIP_VOP2_EP_EDP0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_EDP_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_EDP |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_EDP_MUX, vp->id);
+ break;
+ case ROCKCHIP_VOP2_EP_MIPI0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_MIPI0 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__MIPI_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__MIPI_PIN_POL, polflags);
+ break;
+ case ROCKCHIP_VOP2_EP_MIPI1:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_MIPI1 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__MIPI_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__MIPI_PIN_POL, polflags);
+ break;
+ case ROCKCHIP_VOP2_EP_LVDS0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_LVDS0 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags);
+ break;
+ case ROCKCHIP_VOP2_EP_LVDS1:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_LVDS1 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags);
+ break;
+ default:
+ drm_err(vop2->drm, "Invalid interface id %d on vp%d\n", id, vp->id);
+ return;
+ };
+
+ dip |= RK3568_DSP_IF_POL__CFG_DONE_IMD;
+
+ vop2_writel(vop2, RK3568_DSP_IF_EN, die);
+ vop2_writel(vop2, RK3568_DSP_IF_POL, dip);
+}
+
+static int us_to_vertical_line(struct drm_display_mode *mode, int us)
+{
+ return us * mode->clock / mode->htotal / 1000;
+}
+
+static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct vop2 *vop2 = vp->vop2;
+ const struct vop2_data *vop2_data = vop2->data;
+ const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id];
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ unsigned long clock = mode->crtc_clock * 1000;
+ u16 hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+ u16 hdisplay = mode->crtc_hdisplay;
+ u16 htotal = mode->crtc_htotal;
+ u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start;
+ u16 hact_end = hact_st + hdisplay;
+ u16 vdisplay = mode->crtc_vdisplay;
+ u16 vtotal = mode->crtc_vtotal;
+ u16 vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+ u16 vact_st = mode->crtc_vtotal - mode->crtc_vsync_start;
+ u16 vact_end = vact_st + vdisplay;
+ u8 out_mode;
+ u32 dsp_ctrl = 0;
+ int act_end;
+ u32 val, polflags;
+ int ret;
+ struct drm_encoder *encoder;
+
+ drm_dbg(vop2->drm, "Update mode to %dx%d%s%d, type: %d for vp%d\n",
+ hdisplay, vdisplay, mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "p",
+ drm_mode_vrefresh(mode), vcstate->output_type, vp->id);
+
+ vop2_lock(vop2);
+
+ ret = clk_prepare_enable(vp->dclk);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to enable dclk for video port%d - %d\n",
+ vp->id, ret);
+ return;
+ }
+
+ if (!vop2->enable_count)
+ vop2_enable(vop2);
+
+ vop2->enable_count++;
+
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+
+ polflags = 0;
+ if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+ polflags |= POLFLAG_DCLK_INV;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ polflags |= BIT(HSYNC_POSITIVE);
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ polflags |= BIT(VSYNC_POSITIVE);
+
+ drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) {
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ rk3568_set_intf_mux(vp, rkencoder->crtc_endpoint_id, polflags);
+ }
+
+ if (vcstate->output_mode == ROCKCHIP_OUT_MODE_AAAA &&
+ !(vp_data->feature & VOP_FEATURE_OUTPUT_10BIT))
+ out_mode = ROCKCHIP_OUT_MODE_P888;
+ else
+ out_mode = vcstate->output_mode;
+
+ dsp_ctrl |= FIELD_PREP(RK3568_VP_DSP_CTRL__OUT_MODE, out_mode);
+
+ if (vop2_output_uv_swap(vcstate->bus_format, vcstate->output_mode))
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_RB_SWAP;
+
+ if (is_yuv_output(vcstate->bus_format))
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y;
+
+ vop2_dither_setup(crtc, &dsp_ctrl);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_HTOTAL_HS_END, (htotal << 16) | hsync_len);
+ val = hact_st << 16;
+ val |= hact_end;
+ vop2_vp_write(vp, RK3568_VP_DSP_HACT_ST_END, val);
+
+ val = vact_st << 16;
+ val |= vact_end;
+ vop2_vp_write(vp, RK3568_VP_DSP_VACT_ST_END, val);
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+ u16 vact_st_f1 = vtotal + vact_st + 1;
+ u16 vact_end_f1 = vact_st_f1 + vdisplay;
+
+ val = vact_st_f1 << 16 | vact_end_f1;
+ vop2_vp_write(vp, RK3568_VP_DSP_VACT_ST_END_F1, val);
+
+ val = vtotal << 16 | (vtotal + vsync_len);
+ vop2_vp_write(vp, RK3568_VP_DSP_VS_ST_END_F1, val);
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_INTERLACE;
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_FILED_POL;
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__P2I_EN;
+ vtotal += vtotal + 1;
+ act_end = vact_end_f1;
+ } else {
+ act_end = vact_end;
+ }
+
+ vop2_writel(vop2, RK3568_VP_LINE_FLAG(vp->id),
+ (act_end - us_to_vertical_line(mode, 0)) << 16 | act_end);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_VTOTAL_VS_END, vtotal << 16 | vsync_len);
+
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__CORE_DCLK_DIV;
+ clock *= 2;
+ }
+
+ vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0);
+
+ clk_set_rate(vp->dclk, clock);
+
+ vop2_post_config(crtc);
+
+ vop2_cfg_done(vp);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
+
+ drm_crtc_vblank_on(crtc);
+
+ vop2_unlock(vop2);
+}
+
+static int vop2_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_plane *plane;
+ int nplanes = 0;
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state)
+ nplanes++;
+
+ if (nplanes > vp->nlayers)
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool is_opaque(u16 alpha)
+{
+ return (alpha >> 8) == 0xff;
+}
+
+static void vop2_parse_alpha(struct vop2_alpha_config *alpha_config,
+ struct vop2_alpha *alpha)
+{
+ int src_glb_alpha_en = is_opaque(alpha_config->src_glb_alpha_value) ? 0 : 1;
+ int dst_glb_alpha_en = is_opaque(alpha_config->dst_glb_alpha_value) ? 0 : 1;
+ int src_color_mode = alpha_config->src_premulti_en ?
+ ALPHA_SRC_PRE_MUL : ALPHA_SRC_NO_PRE_MUL;
+ int dst_color_mode = alpha_config->dst_premulti_en ?
+ ALPHA_SRC_PRE_MUL : ALPHA_SRC_NO_PRE_MUL;
+
+ alpha->src_color_ctrl.val = 0;
+ alpha->dst_color_ctrl.val = 0;
+ alpha->src_alpha_ctrl.val = 0;
+ alpha->dst_alpha_ctrl.val = 0;
+
+ if (!alpha_config->src_pixel_alpha_en)
+ alpha->src_color_ctrl.bits.blend_mode = ALPHA_GLOBAL;
+ else if (alpha_config->src_pixel_alpha_en && !src_glb_alpha_en)
+ alpha->src_color_ctrl.bits.blend_mode = ALPHA_PER_PIX;
+ else
+ alpha->src_color_ctrl.bits.blend_mode = ALPHA_PER_PIX_GLOBAL;
+
+ alpha->src_color_ctrl.bits.alpha_en = 1;
+
+ if (alpha->src_color_ctrl.bits.blend_mode == ALPHA_GLOBAL) {
+ alpha->src_color_ctrl.bits.color_mode = src_color_mode;
+ alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_SRC_GLOBAL;
+ } else if (alpha->src_color_ctrl.bits.blend_mode == ALPHA_PER_PIX) {
+ alpha->src_color_ctrl.bits.color_mode = src_color_mode;
+ alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_ONE;
+ } else {
+ alpha->src_color_ctrl.bits.color_mode = ALPHA_SRC_PRE_MUL;
+ alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_SRC_GLOBAL;
+ }
+ alpha->src_color_ctrl.bits.glb_alpha = alpha_config->src_glb_alpha_value >> 8;
+ alpha->src_color_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ alpha->src_color_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION;
+
+ alpha->dst_color_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ alpha->dst_color_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION;
+ alpha->dst_color_ctrl.bits.blend_mode = ALPHA_GLOBAL;
+ alpha->dst_color_ctrl.bits.glb_alpha = alpha_config->dst_glb_alpha_value >> 8;
+ alpha->dst_color_ctrl.bits.color_mode = dst_color_mode;
+ alpha->dst_color_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE;
+
+ alpha->src_alpha_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ alpha->src_alpha_ctrl.bits.blend_mode = alpha->src_color_ctrl.bits.blend_mode;
+ alpha->src_alpha_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION;
+ alpha->src_alpha_ctrl.bits.factor_mode = ALPHA_ONE;
+
+ alpha->dst_alpha_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ if (alpha_config->dst_pixel_alpha_en && !dst_glb_alpha_en)
+ alpha->dst_alpha_ctrl.bits.blend_mode = ALPHA_PER_PIX;
+ else
+ alpha->dst_alpha_ctrl.bits.blend_mode = ALPHA_PER_PIX_GLOBAL;
+ alpha->dst_alpha_ctrl.bits.alpha_cal_mode = ALPHA_NO_SATURATION;
+ alpha->dst_alpha_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE;
+}
+
+static int vop2_find_start_mixer_id_for_vp(struct vop2 *vop2, u8 port_id)
+{
+ struct vop2_video_port *vp;
+ int used_layer = 0;
+ int i;
+
+ for (i = 0; i < port_id; i++) {
+ vp = &vop2->vps[i];
+ used_layer += hweight32(vp->win_mask);
+ }
+
+ return used_layer;
+}
+
+static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win)
+{
+ u32 offset = (main_win->data->phys_id * 0x10);
+ struct vop2_alpha_config alpha_config;
+ struct vop2_alpha alpha;
+ struct drm_plane_state *bottom_win_pstate;
+ bool src_pixel_alpha_en = false;
+ u16 src_glb_alpha_val, dst_glb_alpha_val;
+ bool premulti_en = false;
+ bool swap = false;
+
+ /* At one win mode, win0 is dst/bottom win, and win1 is a all zero src/top win */
+ bottom_win_pstate = main_win->base.state;
+ src_glb_alpha_val = 0;
+ dst_glb_alpha_val = main_win->base.state->alpha;
+
+ if (!bottom_win_pstate->fb)
+ return;
+
+ alpha_config.src_premulti_en = premulti_en;
+ alpha_config.dst_premulti_en = false;
+ alpha_config.src_pixel_alpha_en = src_pixel_alpha_en;
+ alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */
+ alpha_config.src_glb_alpha_value = src_glb_alpha_val;
+ alpha_config.dst_glb_alpha_value = dst_glb_alpha_val;
+ vop2_parse_alpha(&alpha_config, &alpha);
+
+ alpha.src_color_ctrl.bits.src_dst_swap = swap;
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL + offset,
+ alpha.src_color_ctrl.val);
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_DST_COLOR_CTRL + offset,
+ alpha.dst_color_ctrl.val);
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL + offset,
+ alpha.src_alpha_ctrl.val);
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL + offset,
+ alpha.dst_alpha_ctrl.val);
+}
+
+static void vop2_setup_alpha(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+ struct drm_framebuffer *fb;
+ struct vop2_alpha_config alpha_config;
+ struct vop2_alpha alpha;
+ struct drm_plane *plane;
+ int pixel_alpha_en;
+ int premulti_en, gpremulti_en = 0;
+ int mixer_id;
+ u32 offset;
+ bool bottom_layer_alpha_en = false;
+ u32 dst_global_alpha = DRM_BLEND_ALPHA_OPAQUE;
+
+ mixer_id = vop2_find_start_mixer_id_for_vp(vop2, vp->id);
+ alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */
+
+ drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+
+ if (plane->state->normalized_zpos == 0 &&
+ !is_opaque(plane->state->alpha) &&
+ !vop2_cluster_window(win)) {
+ /*
+ * If bottom layer have global alpha effect [except cluster layer,
+ * because cluster have deal with bottom layer global alpha value
+ * at cluster mix], bottom layer mix need deal with global alpha.
+ */
+ bottom_layer_alpha_en = true;
+ dst_global_alpha = plane->state->alpha;
+ }
+ }
+
+ drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+ int zpos = plane->state->normalized_zpos;
+
+ if (plane->state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI)
+ premulti_en = 1;
+ else
+ premulti_en = 0;
+
+ plane = &win->base;
+ fb = plane->state->fb;
+
+ pixel_alpha_en = fb->format->has_alpha;
+
+ alpha_config.src_premulti_en = premulti_en;
+
+ if (bottom_layer_alpha_en && zpos == 1) {
+ gpremulti_en = premulti_en;
+ /* Cd = Cs + (1 - As) * Cd * Agd */
+ alpha_config.dst_premulti_en = false;
+ alpha_config.src_pixel_alpha_en = pixel_alpha_en;
+ alpha_config.src_glb_alpha_value = plane->state->alpha;
+ alpha_config.dst_glb_alpha_value = dst_global_alpha;
+ } else if (vop2_cluster_window(win)) {
+ /* Mix output data only have pixel alpha */
+ alpha_config.dst_premulti_en = true;
+ alpha_config.src_pixel_alpha_en = true;
+ alpha_config.src_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ } else {
+ /* Cd = Cs + (1 - As) * Cd */
+ alpha_config.dst_premulti_en = true;
+ alpha_config.src_pixel_alpha_en = pixel_alpha_en;
+ alpha_config.src_glb_alpha_value = plane->state->alpha;
+ alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ }
+
+ vop2_parse_alpha(&alpha_config, &alpha);
+
+ offset = (mixer_id + zpos - 1) * 0x10;
+ vop2_writel(vop2, RK3568_MIX0_SRC_COLOR_CTRL + offset,
+ alpha.src_color_ctrl.val);
+ vop2_writel(vop2, RK3568_MIX0_DST_COLOR_CTRL + offset,
+ alpha.dst_color_ctrl.val);
+ vop2_writel(vop2, RK3568_MIX0_SRC_ALPHA_CTRL + offset,
+ alpha.src_alpha_ctrl.val);
+ vop2_writel(vop2, RK3568_MIX0_DST_ALPHA_CTRL + offset,
+ alpha.dst_alpha_ctrl.val);
+ }
+
+ if (vp->id == 0) {
+ if (bottom_layer_alpha_en) {
+ /* Transfer pixel alpha to hdr mix */
+ alpha_config.src_premulti_en = gpremulti_en;
+ alpha_config.dst_premulti_en = true;
+ alpha_config.src_pixel_alpha_en = true;
+ alpha_config.src_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ vop2_parse_alpha(&alpha_config, &alpha);
+
+ vop2_writel(vop2, RK3568_HDR0_SRC_COLOR_CTRL,
+ alpha.src_color_ctrl.val);
+ vop2_writel(vop2, RK3568_HDR0_DST_COLOR_CTRL,
+ alpha.dst_color_ctrl.val);
+ vop2_writel(vop2, RK3568_HDR0_SRC_ALPHA_CTRL,
+ alpha.src_alpha_ctrl.val);
+ vop2_writel(vop2, RK3568_HDR0_DST_ALPHA_CTRL,
+ alpha.dst_alpha_ctrl.val);
+ } else {
+ vop2_writel(vop2, RK3568_HDR0_SRC_COLOR_CTRL, 0);
+ }
+ }
+}
+
+static void vop2_setup_layer_mixer(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+ struct drm_plane *plane;
+ u32 layer_sel = 0;
+ u32 port_sel;
+ unsigned int nlayer, ofs;
+ struct drm_display_mode *adjusted_mode;
+ u16 hsync_len;
+ u16 hdisplay;
+ u32 bg_dly;
+ u32 pre_scan_dly;
+ int i;
+ struct vop2_video_port *vp0 = &vop2->vps[0];
+ struct vop2_video_port *vp1 = &vop2->vps[1];
+ struct vop2_video_port *vp2 = &vop2->vps[2];
+
+ adjusted_mode = &vp->crtc.state->adjusted_mode;
+ hsync_len = adjusted_mode->crtc_hsync_end - adjusted_mode->crtc_hsync_start;
+ hdisplay = adjusted_mode->crtc_hdisplay;
+
+ bg_dly = vp->data->pre_scan_max_dly[3];
+ vop2_writel(vop2, RK3568_VP_BG_MIX_CTRL(vp->id),
+ FIELD_PREP(RK3568_VP_BG_MIX_CTRL__BG_DLY, bg_dly));
+
+ pre_scan_dly = ((bg_dly + (hdisplay >> 1) - 1) << 16) | hsync_len;
+ vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly);
+
+ vop2_writel(vop2, RK3568_OVL_CTRL, 0);
+ port_sel = vop2_readl(vop2, RK3568_OVL_PORT_SEL);
+ port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT;
+
+ if (vp0->nlayers)
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT0_MUX,
+ vp0->nlayers - 1);
+ else
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT0_MUX, 8);
+
+ if (vp1->nlayers)
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX,
+ (vp0->nlayers + vp1->nlayers - 1));
+ else
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, 8);
+
+ if (vp2->nlayers)
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT2_MUX,
+ (vp2->nlayers + vp1->nlayers + vp0->nlayers - 1));
+ else
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, 8);
+
+ layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
+
+ ofs = 0;
+ for (i = 0; i < vp->id; i++)
+ ofs += vop2->vps[i].nlayers;
+
+ nlayer = 0;
+ drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+
+ switch (win->data->phys_id) {
+ case ROCKCHIP_VOP2_CLUSTER0:
+ port_sel &= ~RK3568_OVL_PORT_SEL__CLUSTER0;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__CLUSTER0, vp->id);
+ break;
+ case ROCKCHIP_VOP2_CLUSTER1:
+ port_sel &= ~RK3568_OVL_PORT_SEL__CLUSTER1;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__CLUSTER1, vp->id);
+ break;
+ case ROCKCHIP_VOP2_ESMART0:
+ port_sel &= ~RK3568_OVL_PORT_SEL__ESMART0;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__ESMART0, vp->id);
+ break;
+ case ROCKCHIP_VOP2_ESMART1:
+ port_sel &= ~RK3568_OVL_PORT_SEL__ESMART1;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__ESMART1, vp->id);
+ break;
+ case ROCKCHIP_VOP2_SMART0:
+ port_sel &= ~RK3568_OVL_PORT_SEL__SMART0;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__SMART0, vp->id);
+ break;
+ case ROCKCHIP_VOP2_SMART1:
+ port_sel &= ~RK3568_OVL_PORT_SEL__SMART1;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__SMART1, vp->id);
+ break;
+ }
+
+ layer_sel &= ~RK3568_OVL_LAYER_SEL__LAYER(plane->state->normalized_zpos + ofs,
+ 0x7);
+ layer_sel |= RK3568_OVL_LAYER_SEL__LAYER(plane->state->normalized_zpos + ofs,
+ win->data->layer_sel_id);
+ nlayer++;
+ }
+
+ /* configure unused layers to 0x5 (reserved) */
+ for (; nlayer < vp->nlayers; nlayer++) {
+ layer_sel &= ~RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 0x7);
+ layer_sel |= RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 5);
+ }
+
+ vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel);
+ vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
+ vop2_writel(vop2, RK3568_OVL_CTRL, RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD);
+}
+
+static void vop2_setup_dly_for_windows(struct vop2 *vop2)
+{
+ struct vop2_win *win;
+ int i = 0;
+ u32 cdly = 0, sdly = 0;
+
+ for (i = 0; i < vop2->data->win_size; i++) {
+ u32 dly;
+
+ win = &vop2->win[i];
+ dly = win->delay;
+
+ switch (win->data->phys_id) {
+ case ROCKCHIP_VOP2_CLUSTER0:
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER0_0, dly);
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER0_1, dly);
+ break;
+ case ROCKCHIP_VOP2_CLUSTER1:
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER1_0, dly);
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER1_1, dly);
+ break;
+ case ROCKCHIP_VOP2_ESMART0:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__ESMART0, dly);
+ break;
+ case ROCKCHIP_VOP2_ESMART1:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__ESMART1, dly);
+ break;
+ case ROCKCHIP_VOP2_SMART0:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__SMART0, dly);
+ break;
+ case ROCKCHIP_VOP2_SMART1:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__SMART1, dly);
+ break;
+ }
+ }
+
+ vop2_writel(vop2, RK3568_CLUSTER_DLY_NUM, cdly);
+ vop2_writel(vop2, RK3568_SMART_DLY_NUM, sdly);
+}
+
+static void vop2_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct vop2 *vop2 = vp->vop2;
+ struct drm_plane *plane;
+
+ vp->win_mask = 0;
+
+ drm_atomic_crtc_for_each_plane(plane, crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+
+ win->delay = win->data->dly[VOP2_DLY_MODE_DEFAULT];
+
+ vp->win_mask |= BIT(win->data->phys_id);
+
+ if (vop2_cluster_window(win))
+ vop2_setup_cluster_alpha(vop2, win);
+ }
+
+ if (!vp->win_mask)
+ return;
+
+ vop2_setup_layer_mixer(vp);
+ vop2_setup_alpha(vp);
+ vop2_setup_dly_for_windows(vop2);
+}
+
+static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+
+ vop2_post_config(crtc);
+
+ vop2_cfg_done(vp);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ vp->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static const struct drm_crtc_helper_funcs vop2_crtc_helper_funcs = {
+ .mode_fixup = vop2_crtc_mode_fixup,
+ .atomic_check = vop2_crtc_atomic_check,
+ .atomic_begin = vop2_crtc_atomic_begin,
+ .atomic_flush = vop2_crtc_atomic_flush,
+ .atomic_enable = vop2_crtc_atomic_enable,
+ .atomic_disable = vop2_crtc_atomic_disable,
+};
+
+static void vop2_crtc_reset(struct drm_crtc *crtc)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+ kfree(vcstate);
+ }
+
+ vcstate = kzalloc(sizeof(*vcstate), GFP_KERNEL);
+ if (!vcstate)
+ return;
+
+ crtc->state = &vcstate->base;
+ crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *vop2_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct rockchip_crtc_state *vcstate, *old_vcstate;
+
+ old_vcstate = to_rockchip_crtc_state(crtc->state);
+
+ vcstate = kmemdup(old_vcstate, sizeof(*old_vcstate), GFP_KERNEL);
+ if (!vcstate)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &vcstate->base);
+
+ return &vcstate->base;
+}
+
+static void vop2_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(state);
+
+ __drm_atomic_helper_crtc_destroy_state(&vcstate->base);
+ kfree(vcstate);
+}
+
+static const struct drm_crtc_funcs vop2_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .destroy = drm_crtc_cleanup,
+ .reset = vop2_crtc_reset,
+ .atomic_duplicate_state = vop2_crtc_duplicate_state,
+ .atomic_destroy_state = vop2_crtc_destroy_state,
+ .enable_vblank = vop2_crtc_enable_vblank,
+ .disable_vblank = vop2_crtc_disable_vblank,
+};
+
+static irqreturn_t vop2_isr(int irq, void *data)
+{
+ struct vop2 *vop2 = data;
+ const struct vop2_data *vop2_data = vop2->data;
+ u32 axi_irqs[VOP2_SYS_AXI_BUS_NUM];
+ int ret = IRQ_NONE;
+ int i;
+
+ /*
+ * The irq is shared with the iommu. If the runtime-pm state of the
+ * vop2-device is disabled the irq has to be targeted at the iommu.
+ */
+ if (!pm_runtime_get_if_in_use(vop2->dev))
+ return IRQ_NONE;
+
+ for (i = 0; i < vop2_data->nr_vps; i++) {
+ struct vop2_video_port *vp = &vop2->vps[i];
+ struct drm_crtc *crtc = &vp->crtc;
+ u32 irqs;
+
+ irqs = vop2_readl(vop2, RK3568_VP_INT_STATUS(vp->id));
+ vop2_writel(vop2, RK3568_VP_INT_CLR(vp->id), irqs << 16 | irqs);
+
+ if (irqs & VP_INT_DSP_HOLD_VALID) {
+ complete(&vp->dsp_hold_completion);
+ ret = IRQ_HANDLED;
+ }
+
+ if (irqs & VP_INT_FS_FIELD) {
+ drm_crtc_handle_vblank(crtc);
+ spin_lock(&crtc->dev->event_lock);
+ if (vp->event) {
+ u32 val = vop2_readl(vop2, RK3568_REG_CFG_DONE);
+
+ if (!(val & BIT(vp->id))) {
+ drm_crtc_send_vblank_event(crtc, vp->event);
+ vp->event = NULL;
+ drm_crtc_vblank_put(crtc);
+ }
+ }
+ spin_unlock(&crtc->dev->event_lock);
+
+ ret = IRQ_HANDLED;
+ }
+
+ if (irqs & VP_INT_POST_BUF_EMPTY) {
+ drm_err_ratelimited(vop2->drm,
+ "POST_BUF_EMPTY irq err at vp%d\n",
+ vp->id);
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ axi_irqs[0] = vop2_readl(vop2, RK3568_SYS0_INT_STATUS);
+ vop2_writel(vop2, RK3568_SYS0_INT_CLR, axi_irqs[0] << 16 | axi_irqs[0]);
+ axi_irqs[1] = vop2_readl(vop2, RK3568_SYS1_INT_STATUS);
+ vop2_writel(vop2, RK3568_SYS1_INT_CLR, axi_irqs[1] << 16 | axi_irqs[1]);
+
+ for (i = 0; i < ARRAY_SIZE(axi_irqs); i++) {
+ if (axi_irqs[i] & VOP2_INT_BUS_ERRPR) {
+ drm_err_ratelimited(vop2->drm, "BUS_ERROR irq err\n");
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ pm_runtime_put(vop2->dev);
+
+ return ret;
+}
+
+static int vop2_plane_init(struct vop2 *vop2, struct vop2_win *win,
+ unsigned long possible_crtcs)
+{
+ const struct vop2_win_data *win_data = win->data;
+ unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE);
+ int ret;
+
+ ret = drm_universal_plane_init(vop2->drm, &win->base, possible_crtcs,
+ &vop2_plane_funcs, win_data->formats,
+ win_data->nformats,
+ win_data->format_modifiers,
+ win->type, win_data->name);
+ if (ret) {
+ drm_err(vop2->drm, "failed to initialize plane %d\n", ret);
+ return ret;
+ }
+
+ drm_plane_helper_add(&win->base, &vop2_plane_helper_funcs);
+
+ if (win->data->supported_rotations)
+ drm_plane_create_rotation_property(&win->base, DRM_MODE_ROTATE_0,
+ DRM_MODE_ROTATE_0 |
+ win->data->supported_rotations);
+ drm_plane_create_alpha_property(&win->base);
+ drm_plane_create_blend_mode_property(&win->base, blend_caps);
+ drm_plane_create_zpos_property(&win->base, win->win_id, 0,
+ vop2->registered_num_wins - 1);
+
+ return 0;
+}
+
+static struct vop2_video_port *find_vp_without_primary(struct vop2 *vop2)
+{
+ int i;
+
+ for (i = 0; i < vop2->data->nr_vps; i++) {
+ struct vop2_video_port *vp = &vop2->vps[i];
+
+ if (!vp->crtc.port)
+ continue;
+ if (vp->primary_plane)
+ continue;
+
+ return vp;
+ }
+
+ return NULL;
+}
+
+#define NR_LAYERS 6
+
+static int vop2_create_crtc(struct vop2 *vop2)
+{
+ const struct vop2_data *vop2_data = vop2->data;
+ struct drm_device *drm = vop2->drm;
+ struct device *dev = vop2->dev;
+ struct drm_plane *plane;
+ struct device_node *port;
+ struct vop2_video_port *vp;
+ int i, nvp, nvps = 0;
+ int ret;
+
+ for (i = 0; i < vop2_data->nr_vps; i++) {
+ const struct vop2_video_port_data *vp_data;
+ struct device_node *np;
+ char dclk_name[9];
+
+ vp_data = &vop2_data->vp[i];
+ vp = &vop2->vps[i];
+ vp->vop2 = vop2;
+ vp->id = vp_data->id;
+ vp->regs = vp_data->regs;
+ vp->data = vp_data;
+
+ snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id);
+ vp->dclk = devm_clk_get(vop2->dev, dclk_name);
+ if (IS_ERR(vp->dclk)) {
+ drm_err(vop2->drm, "failed to get %s\n", dclk_name);
+ return PTR_ERR(vp->dclk);
+ }
+
+ np = of_graph_get_remote_node(dev->of_node, i, -1);
+ if (!np) {
+ drm_dbg(vop2->drm, "%s: No remote for vp%d\n", __func__, i);
+ continue;
+ }
+ of_node_put(np);
+
+ port = of_graph_get_port_by_id(dev->of_node, i);
+ if (!port) {
+ drm_err(vop2->drm, "no port node found for video_port%d\n", i);
+ return -ENOENT;
+ }
+
+ vp->crtc.port = port;
+ nvps++;
+ }
+
+ nvp = 0;
+ for (i = 0; i < vop2->registered_num_wins; i++) {
+ struct vop2_win *win = &vop2->win[i];
+ u32 possible_crtcs;
+
+ if (vop2->data->soc_id == 3566) {
+ /*
+ * On RK3566 these windows don't have an independent
+ * framebuffer. They share the framebuffer with smart0,
+ * esmart0 and cluster0 respectively.
+ */
+ switch (win->data->phys_id) {
+ case ROCKCHIP_VOP2_SMART1:
+ case ROCKCHIP_VOP2_ESMART1:
+ case ROCKCHIP_VOP2_CLUSTER1:
+ continue;
+ }
+ }
+
+ if (win->type == DRM_PLANE_TYPE_PRIMARY) {
+ vp = find_vp_without_primary(vop2);
+ if (vp) {
+ possible_crtcs = BIT(nvp);
+ vp->primary_plane = win;
+ nvp++;
+ } else {
+ /* change the unused primary window to overlay window */
+ win->type = DRM_PLANE_TYPE_OVERLAY;
+ }
+ }
+
+ if (win->type == DRM_PLANE_TYPE_OVERLAY)
+ possible_crtcs = (1 << nvps) - 1;
+
+ ret = vop2_plane_init(vop2, win, possible_crtcs);
+ if (ret) {
+ drm_err(vop2->drm, "failed to init plane %s: %d\n",
+ win->data->name, ret);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < vop2_data->nr_vps; i++) {
+ vp = &vop2->vps[i];
+
+ if (!vp->crtc.port)
+ continue;
+
+ plane = &vp->primary_plane->base;
+
+ ret = drm_crtc_init_with_planes(drm, &vp->crtc, plane, NULL,
+ &vop2_crtc_funcs,
+ "video_port%d", vp->id);
+ if (ret) {
+ drm_err(vop2->drm, "crtc init for video_port%d failed\n", i);
+ return ret;
+ }
+
+ drm_crtc_helper_add(&vp->crtc, &vop2_crtc_helper_funcs);
+
+ init_completion(&vp->dsp_hold_completion);
+ }
+
+ /*
+ * On the VOP2 it's very hard to change the number of layers on a VP
+ * during runtime, so we distribute the layers equally over the used
+ * VPs
+ */
+ for (i = 0; i < vop2->data->nr_vps; i++) {
+ struct vop2_video_port *vp = &vop2->vps[i];
+
+ if (vp->crtc.port)
+ vp->nlayers = NR_LAYERS / nvps;
+ }
+
+ return 0;
+}
+
+static void vop2_destroy_crtc(struct drm_crtc *crtc)
+{
+ of_node_put(crtc->port);
+
+ /*
+ * Destroy CRTC after vop2_plane_destroy() since vop2_disable_plane()
+ * references the CRTC.
+ */
+ drm_crtc_cleanup(crtc);
+}
+
+static struct reg_field vop2_cluster_regs[VOP2_WIN_MAX_REG] = {
+ [VOP2_WIN_ENABLE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 0, 0),
+ [VOP2_WIN_FORMAT] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 1, 5),
+ [VOP2_WIN_RB_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 14, 14),
+ [VOP2_WIN_DITHER_UP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 18, 18),
+ [VOP2_WIN_ACT_INFO] = REG_FIELD(RK3568_CLUSTER_WIN_ACT_INFO, 0, 31),
+ [VOP2_WIN_DSP_INFO] = REG_FIELD(RK3568_CLUSTER_WIN_DSP_INFO, 0, 31),
+ [VOP2_WIN_DSP_ST] = REG_FIELD(RK3568_CLUSTER_WIN_DSP_ST, 0, 31),
+ [VOP2_WIN_YRGB_MST] = REG_FIELD(RK3568_CLUSTER_WIN_YRGB_MST, 0, 31),
+ [VOP2_WIN_UV_MST] = REG_FIELD(RK3568_CLUSTER_WIN_CBR_MST, 0, 31),
+ [VOP2_WIN_YUV_CLIP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 19, 19),
+ [VOP2_WIN_YRGB_VIR] = REG_FIELD(RK3568_CLUSTER_WIN_VIR, 0, 15),
+ [VOP2_WIN_UV_VIR] = REG_FIELD(RK3568_CLUSTER_WIN_VIR, 16, 31),
+ [VOP2_WIN_Y2R_EN] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 8, 8),
+ [VOP2_WIN_R2Y_EN] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 9, 9),
+ [VOP2_WIN_CSC_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 10, 11),
+
+ /* Scale */
+ [VOP2_WIN_SCALE_YRGB_X] = REG_FIELD(RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB, 0, 15),
+ [VOP2_WIN_SCALE_YRGB_Y] = REG_FIELD(RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB, 16, 31),
+ [VOP2_WIN_YRGB_VER_SCL_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 14, 15),
+ [VOP2_WIN_YRGB_HOR_SCL_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 12, 13),
+ [VOP2_WIN_BIC_COE_SEL] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 2, 3),
+ [VOP2_WIN_VSD_YRGB_GT2] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 28, 28),
+ [VOP2_WIN_VSD_YRGB_GT4] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 29, 29),
+
+ /* cluster regs */
+ [VOP2_WIN_AFBC_ENABLE] = REG_FIELD(RK3568_CLUSTER_CTRL, 1, 1),
+ [VOP2_WIN_CLUSTER_ENABLE] = REG_FIELD(RK3568_CLUSTER_CTRL, 0, 0),
+ [VOP2_WIN_CLUSTER_LB_MODE] = REG_FIELD(RK3568_CLUSTER_CTRL, 4, 7),
+
+ /* afbc regs */
+ [VOP2_WIN_AFBC_FORMAT] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 2, 6),
+ [VOP2_WIN_AFBC_RB_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 9, 9),
+ [VOP2_WIN_AFBC_UV_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 10, 10),
+ [VOP2_WIN_AFBC_AUTO_GATING_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL, 4, 4),
+ [VOP2_WIN_AFBC_HALF_BLOCK_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 7, 7),
+ [VOP2_WIN_AFBC_BLOCK_SPLIT_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 8, 8),
+ [VOP2_WIN_AFBC_HDR_PTR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_HDR_PTR, 0, 31),
+ [VOP2_WIN_AFBC_PIC_SIZE] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE, 0, 31),
+ [VOP2_WIN_AFBC_PIC_VIR_WIDTH] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH, 0, 15),
+ [VOP2_WIN_AFBC_TILE_NUM] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH, 16, 31),
+ [VOP2_WIN_AFBC_PIC_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET, 0, 31),
+ [VOP2_WIN_AFBC_DSP_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET, 0, 31),
+ [VOP2_WIN_AFBC_TRANSFORM_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_TRANSFORM_OFFSET, 0, 31),
+ [VOP2_WIN_AFBC_ROTATE_90] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 0, 0),
+ [VOP2_WIN_AFBC_ROTATE_270] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 1, 1),
+ [VOP2_WIN_XMIRROR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 2, 2),
+ [VOP2_WIN_YMIRROR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 3, 3),
+ [VOP2_WIN_UV_SWAP] = { .reg = 0xffffffff },
+ [VOP2_WIN_COLOR_KEY] = { .reg = 0xffffffff },
+ [VOP2_WIN_COLOR_KEY_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_SCALE_CBCR_X] = { .reg = 0xffffffff },
+ [VOP2_WIN_SCALE_CBCR_Y] = { .reg = 0xffffffff },
+ [VOP2_WIN_YRGB_HSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_YRGB_VSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_VER_SCL_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_HSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_HOR_SCL_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_VSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_VSD_CBCR_GT2] = { .reg = 0xffffffff },
+ [VOP2_WIN_VSD_CBCR_GT4] = { .reg = 0xffffffff },
+};
+
+static int vop2_cluster_init(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ struct reg_field *cluster_regs;
+ int ret, i;
+
+ cluster_regs = kmemdup(vop2_cluster_regs, sizeof(vop2_cluster_regs),
+ GFP_KERNEL);
+ if (!cluster_regs)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(vop2_cluster_regs); i++)
+ if (cluster_regs[i].reg != 0xffffffff)
+ cluster_regs[i].reg += win->offset;
+
+ ret = devm_regmap_field_bulk_alloc(vop2->dev, vop2->map, win->reg,
+ cluster_regs,
+ ARRAY_SIZE(vop2_cluster_regs));
+
+ kfree(cluster_regs);
+
+ return ret;
+};
+
+static struct reg_field vop2_esmart_regs[VOP2_WIN_MAX_REG] = {
+ [VOP2_WIN_ENABLE] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 0, 0),
+ [VOP2_WIN_FORMAT] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 1, 5),
+ [VOP2_WIN_DITHER_UP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 12, 12),
+ [VOP2_WIN_RB_SWAP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 14, 14),
+ [VOP2_WIN_UV_SWAP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 16, 16),
+ [VOP2_WIN_ACT_INFO] = REG_FIELD(RK3568_SMART_REGION0_ACT_INFO, 0, 31),
+ [VOP2_WIN_DSP_INFO] = REG_FIELD(RK3568_SMART_REGION0_DSP_INFO, 0, 31),
+ [VOP2_WIN_DSP_ST] = REG_FIELD(RK3568_SMART_REGION0_DSP_ST, 0, 28),
+ [VOP2_WIN_YRGB_MST] = REG_FIELD(RK3568_SMART_REGION0_YRGB_MST, 0, 31),
+ [VOP2_WIN_UV_MST] = REG_FIELD(RK3568_SMART_REGION0_CBR_MST, 0, 31),
+ [VOP2_WIN_YUV_CLIP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 17, 17),
+ [VOP2_WIN_YRGB_VIR] = REG_FIELD(RK3568_SMART_REGION0_VIR, 0, 15),
+ [VOP2_WIN_UV_VIR] = REG_FIELD(RK3568_SMART_REGION0_VIR, 16, 31),
+ [VOP2_WIN_Y2R_EN] = REG_FIELD(RK3568_SMART_CTRL0, 0, 0),
+ [VOP2_WIN_R2Y_EN] = REG_FIELD(RK3568_SMART_CTRL0, 1, 1),
+ [VOP2_WIN_CSC_MODE] = REG_FIELD(RK3568_SMART_CTRL0, 2, 3),
+ [VOP2_WIN_YMIRROR] = REG_FIELD(RK3568_SMART_CTRL1, 31, 31),
+ [VOP2_WIN_COLOR_KEY] = REG_FIELD(RK3568_SMART_COLOR_KEY_CTRL, 0, 29),
+ [VOP2_WIN_COLOR_KEY_EN] = REG_FIELD(RK3568_SMART_COLOR_KEY_CTRL, 31, 31),
+
+ /* Scale */
+ [VOP2_WIN_SCALE_YRGB_X] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_YRGB, 0, 15),
+ [VOP2_WIN_SCALE_YRGB_Y] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_YRGB, 16, 31),
+ [VOP2_WIN_SCALE_CBCR_X] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_CBR, 0, 15),
+ [VOP2_WIN_SCALE_CBCR_Y] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_CBR, 16, 31),
+ [VOP2_WIN_YRGB_HOR_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 0, 1),
+ [VOP2_WIN_YRGB_HSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 2, 3),
+ [VOP2_WIN_YRGB_VER_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 4, 5),
+ [VOP2_WIN_YRGB_VSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 6, 7),
+ [VOP2_WIN_CBCR_HOR_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 8, 9),
+ [VOP2_WIN_CBCR_HSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 10, 11),
+ [VOP2_WIN_CBCR_VER_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 12, 13),
+ [VOP2_WIN_CBCR_VSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 14, 15),
+ [VOP2_WIN_BIC_COE_SEL] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 16, 17),
+ [VOP2_WIN_VSD_YRGB_GT2] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 8, 8),
+ [VOP2_WIN_VSD_YRGB_GT4] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 9, 9),
+ [VOP2_WIN_VSD_CBCR_GT2] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 10, 10),
+ [VOP2_WIN_VSD_CBCR_GT4] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 11, 11),
+ [VOP2_WIN_XMIRROR] = { .reg = 0xffffffff },
+ [VOP2_WIN_CLUSTER_ENABLE] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_ENABLE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CLUSTER_LB_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_FORMAT] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_RB_SWAP] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_UV_SWAP] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_AUTO_GATING_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_BLOCK_SPLIT_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_PIC_VIR_WIDTH] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_TILE_NUM] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_PIC_OFFSET] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_PIC_SIZE] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_DSP_OFFSET] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_TRANSFORM_OFFSET] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_HDR_PTR] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_HALF_BLOCK_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_ROTATE_270] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_ROTATE_90] = { .reg = 0xffffffff },
+};
+
+static int vop2_esmart_init(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ struct reg_field *esmart_regs;
+ int ret, i;
+
+ esmart_regs = kmemdup(vop2_esmart_regs, sizeof(vop2_esmart_regs),
+ GFP_KERNEL);
+ if (!esmart_regs)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(vop2_esmart_regs); i++)
+ if (esmart_regs[i].reg != 0xffffffff)
+ esmart_regs[i].reg += win->offset;
+
+ ret = devm_regmap_field_bulk_alloc(vop2->dev, vop2->map, win->reg,
+ esmart_regs,
+ ARRAY_SIZE(vop2_esmart_regs));
+
+ kfree(esmart_regs);
+
+ return ret;
+};
+
+static int vop2_win_init(struct vop2 *vop2)
+{
+ const struct vop2_data *vop2_data = vop2->data;
+ struct vop2_win *win;
+ int i, ret;
+
+ for (i = 0; i < vop2_data->win_size; i++) {
+ const struct vop2_win_data *win_data = &vop2_data->win[i];
+
+ win = &vop2->win[i];
+ win->data = win_data;
+ win->type = win_data->type;
+ win->offset = win_data->base;
+ win->win_id = i;
+ win->vop2 = vop2;
+ if (vop2_cluster_window(win))
+ ret = vop2_cluster_init(win);
+ else
+ ret = vop2_esmart_init(win);
+ if (ret)
+ return ret;
+ }
+
+ vop2->registered_num_wins = vop2_data->win_size;
+
+ return 0;
+}
+
+/*
+ * The window registers are only updated when config done is written.
+ * Until that they read back the old value. As we read-modify-write
+ * these registers mark them as non-volatile. This makes sure we read
+ * the new values from the regmap register cache.
+ */
+static const struct regmap_range vop2_nonvolatile_range[] = {
+ regmap_reg_range(0x1000, 0x23ff),
+};
+
+static const struct regmap_access_table vop2_volatile_table = {
+ .no_ranges = vop2_nonvolatile_range,
+ .n_no_ranges = ARRAY_SIZE(vop2_nonvolatile_range),
+};
+
+static const struct regmap_config vop2_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x3000,
+ .name = "vop2",
+ .volatile_table = &vop2_volatile_table,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int vop2_bind(struct device *dev, struct device *master, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ const struct vop2_data *vop2_data;
+ struct drm_device *drm = data;
+ struct vop2 *vop2;
+ struct resource *res;
+ size_t alloc_size;
+ int ret;
+
+ vop2_data = of_device_get_match_data(dev);
+ if (!vop2_data)
+ return -ENODEV;
+
+ /* Allocate vop2 struct and its vop2_win array */
+ alloc_size = sizeof(*vop2) + sizeof(*vop2->win) * vop2_data->win_size;
+ vop2 = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
+ if (!vop2)
+ return -ENOMEM;
+
+ vop2->dev = dev;
+ vop2->data = vop2_data;
+ vop2->drm = drm;
+
+ dev_set_drvdata(dev, vop2);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+ if (!res) {
+ drm_err(vop2->drm, "failed to get vop2 register byname\n");
+ return -EINVAL;
+ }
+
+ vop2->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop2->regs))
+ return PTR_ERR(vop2->regs);
+ vop2->len = resource_size(res);
+
+ vop2->map = devm_regmap_init_mmio(dev, vop2->regs, &vop2_regmap_config);
+
+ ret = vop2_win_init(vop2);
+ if (ret)
+ return ret;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gamma_lut");
+ if (res) {
+ vop2->lut_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop2->lut_regs))
+ return PTR_ERR(vop2->lut_regs);
+ }
+
+ vop2->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
+
+ vop2->hclk = devm_clk_get(vop2->dev, "hclk");
+ if (IS_ERR(vop2->hclk)) {
+ drm_err(vop2->drm, "failed to get hclk source\n");
+ return PTR_ERR(vop2->hclk);
+ }
+
+ vop2->aclk = devm_clk_get(vop2->dev, "aclk");
+ if (IS_ERR(vop2->aclk)) {
+ drm_err(vop2->drm, "failed to get aclk source\n");
+ return PTR_ERR(vop2->aclk);
+ }
+
+ vop2->irq = platform_get_irq(pdev, 0);
+ if (vop2->irq < 0) {
+ drm_err(vop2->drm, "cannot find irq for vop2\n");
+ return vop2->irq;
+ }
+
+ mutex_init(&vop2->vop2_lock);
+
+ ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
+ if (ret)
+ return ret;
+
+ ret = vop2_create_crtc(vop2);
+ if (ret)
+ return ret;
+
+ rockchip_drm_dma_init_device(vop2->drm, vop2->dev);
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static void vop2_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct vop2 *vop2 = dev_get_drvdata(dev);
+ struct drm_device *drm = vop2->drm;
+ struct list_head *plane_list = &drm->mode_config.plane_list;
+ struct list_head *crtc_list = &drm->mode_config.crtc_list;
+ struct drm_crtc *crtc, *tmpc;
+ struct drm_plane *plane, *tmpp;
+
+ pm_runtime_disable(dev);
+
+ list_for_each_entry_safe(plane, tmpp, plane_list, head)
+ drm_plane_cleanup(plane);
+
+ list_for_each_entry_safe(crtc, tmpc, crtc_list, head)
+ vop2_destroy_crtc(crtc);
+}
+
+const struct component_ops vop2_component_ops = {
+ .bind = vop2_bind,
+ .unbind = vop2_unbind,
+};
+EXPORT_SYMBOL_GPL(vop2_component_ops);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
new file mode 100644
index 000000000000..c727093a06d6
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -0,0 +1,477 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <[email protected]>
+ */
+
+#ifndef _ROCKCHIP_DRM_VOP2_H
+#define _ROCKCHIP_DRM_VOP2_H
+
+#include "rockchip_drm_vop.h"
+
+#include <linux/regmap.h>
+#include <drm/drm_modes.h>
+
+#define VOP_FEATURE_OUTPUT_10BIT BIT(0)
+
+#define WIN_FEATURE_AFBDC BIT(0)
+#define WIN_FEATURE_CLUSTER BIT(1)
+
+/*
+ * the delay number of a window in different mode.
+ */
+enum win_dly_mode {
+ VOP2_DLY_MODE_DEFAULT, /**< default mode */
+ VOP2_DLY_MODE_HISO_S, /** HDR in SDR out mode, as a SDR window */
+ VOP2_DLY_MODE_HIHO_H, /** HDR in HDR out mode, as a HDR window */
+ VOP2_DLY_MODE_MAX,
+};
+
+struct vop_rect {
+ int width;
+ int height;
+};
+
+enum vop2_scale_up_mode {
+ VOP2_SCALE_UP_NRST_NBOR,
+ VOP2_SCALE_UP_BIL,
+ VOP2_SCALE_UP_BIC,
+};
+
+enum vop2_scale_down_mode {
+ VOP2_SCALE_DOWN_NRST_NBOR,
+ VOP2_SCALE_DOWN_BIL,
+ VOP2_SCALE_DOWN_AVG,
+};
+
+enum vop2_win_regs {
+ VOP2_WIN_ENABLE,
+ VOP2_WIN_FORMAT,
+ VOP2_WIN_CSC_MODE,
+ VOP2_WIN_XMIRROR,
+ VOP2_WIN_YMIRROR,
+ VOP2_WIN_RB_SWAP,
+ VOP2_WIN_UV_SWAP,
+ VOP2_WIN_ACT_INFO,
+ VOP2_WIN_DSP_INFO,
+ VOP2_WIN_DSP_ST,
+ VOP2_WIN_YRGB_MST,
+ VOP2_WIN_UV_MST,
+ VOP2_WIN_YRGB_VIR,
+ VOP2_WIN_UV_VIR,
+ VOP2_WIN_YUV_CLIP,
+ VOP2_WIN_Y2R_EN,
+ VOP2_WIN_R2Y_EN,
+ VOP2_WIN_COLOR_KEY,
+ VOP2_WIN_COLOR_KEY_EN,
+ VOP2_WIN_DITHER_UP,
+
+ /* scale regs */
+ VOP2_WIN_SCALE_YRGB_X,
+ VOP2_WIN_SCALE_YRGB_Y,
+ VOP2_WIN_SCALE_CBCR_X,
+ VOP2_WIN_SCALE_CBCR_Y,
+ VOP2_WIN_YRGB_HOR_SCL_MODE,
+ VOP2_WIN_YRGB_HSCL_FILTER_MODE,
+ VOP2_WIN_YRGB_VER_SCL_MODE,
+ VOP2_WIN_YRGB_VSCL_FILTER_MODE,
+ VOP2_WIN_CBCR_VER_SCL_MODE,
+ VOP2_WIN_CBCR_HSCL_FILTER_MODE,
+ VOP2_WIN_CBCR_HOR_SCL_MODE,
+ VOP2_WIN_CBCR_VSCL_FILTER_MODE,
+ VOP2_WIN_VSD_CBCR_GT2,
+ VOP2_WIN_VSD_CBCR_GT4,
+ VOP2_WIN_VSD_YRGB_GT2,
+ VOP2_WIN_VSD_YRGB_GT4,
+ VOP2_WIN_BIC_COE_SEL,
+
+ /* cluster regs */
+ VOP2_WIN_CLUSTER_ENABLE,
+ VOP2_WIN_AFBC_ENABLE,
+ VOP2_WIN_CLUSTER_LB_MODE,
+
+ /* afbc regs */
+ VOP2_WIN_AFBC_FORMAT,
+ VOP2_WIN_AFBC_RB_SWAP,
+ VOP2_WIN_AFBC_UV_SWAP,
+ VOP2_WIN_AFBC_AUTO_GATING_EN,
+ VOP2_WIN_AFBC_BLOCK_SPLIT_EN,
+ VOP2_WIN_AFBC_PIC_VIR_WIDTH,
+ VOP2_WIN_AFBC_TILE_NUM,
+ VOP2_WIN_AFBC_PIC_OFFSET,
+ VOP2_WIN_AFBC_PIC_SIZE,
+ VOP2_WIN_AFBC_DSP_OFFSET,
+ VOP2_WIN_AFBC_TRANSFORM_OFFSET,
+ VOP2_WIN_AFBC_HDR_PTR,
+ VOP2_WIN_AFBC_HALF_BLOCK_EN,
+ VOP2_WIN_AFBC_ROTATE_270,
+ VOP2_WIN_AFBC_ROTATE_90,
+ VOP2_WIN_MAX_REG,
+};
+
+struct vop2_win_data {
+ const char *name;
+ unsigned int phys_id;
+
+ u32 base;
+ enum drm_plane_type type;
+
+ u32 nformats;
+ const u32 *formats;
+ const uint64_t *format_modifiers;
+ const unsigned int supported_rotations;
+
+ /**
+ * @layer_sel_id: defined by register OVERLAY_LAYER_SEL of VOP2
+ */
+ unsigned int layer_sel_id;
+ uint64_t feature;
+
+ unsigned int max_upscale_factor;
+ unsigned int max_downscale_factor;
+ const u8 dly[VOP2_DLY_MODE_MAX];
+};
+
+struct vop2_video_port_data {
+ unsigned int id;
+ u32 feature;
+ u16 gamma_lut_len;
+ u16 cubic_lut_len;
+ struct vop_rect max_output;
+ const u8 pre_scan_max_dly[4];
+ const struct vop2_video_port_regs *regs;
+ unsigned int offset;
+};
+
+struct vop2_data {
+ u8 nr_vps;
+ const struct vop2_ctrl *ctrl;
+ const struct vop2_win_data *win;
+ const struct vop2_video_port_data *vp;
+ const struct vop_csc_table *csc_table;
+ struct vop_rect max_input;
+ struct vop_rect max_output;
+
+ unsigned int win_size;
+ unsigned int soc_id;
+};
+
+/* interrupt define */
+#define FS_NEW_INTR BIT(4)
+#define ADDR_SAME_INTR BIT(5)
+#define LINE_FLAG1_INTR BIT(6)
+#define WIN0_EMPTY_INTR BIT(7)
+#define WIN1_EMPTY_INTR BIT(8)
+#define WIN2_EMPTY_INTR BIT(9)
+#define WIN3_EMPTY_INTR BIT(10)
+#define HWC_EMPTY_INTR BIT(11)
+#define POST_BUF_EMPTY_INTR BIT(12)
+#define PWM_GEN_INTR BIT(13)
+#define DMA_FINISH_INTR BIT(14)
+#define FS_FIELD_INTR BIT(15)
+#define FE_INTR BIT(16)
+#define WB_UV_FIFO_FULL_INTR BIT(17)
+#define WB_YRGB_FIFO_FULL_INTR BIT(18)
+#define WB_COMPLETE_INTR BIT(19)
+
+/*
+ * display output interface supported by rockchip lcdc
+ */
+#define ROCKCHIP_OUT_MODE_P888 0
+#define ROCKCHIP_OUT_MODE_BT1120 0
+#define ROCKCHIP_OUT_MODE_P666 1
+#define ROCKCHIP_OUT_MODE_P565 2
+#define ROCKCHIP_OUT_MODE_BT656 5
+#define ROCKCHIP_OUT_MODE_S888 8
+#define ROCKCHIP_OUT_MODE_S888_DUMMY 12
+#define ROCKCHIP_OUT_MODE_YUV420 14
+/* for use special outface */
+#define ROCKCHIP_OUT_MODE_AAAA 15
+
+enum vop_csc_format {
+ CSC_BT601L,
+ CSC_BT709L,
+ CSC_BT601F,
+ CSC_BT2020,
+};
+
+enum src_factor_mode {
+ SRC_FAC_ALPHA_ZERO,
+ SRC_FAC_ALPHA_ONE,
+ SRC_FAC_ALPHA_DST,
+ SRC_FAC_ALPHA_DST_INVERSE,
+ SRC_FAC_ALPHA_SRC,
+ SRC_FAC_ALPHA_SRC_GLOBAL,
+};
+
+enum dst_factor_mode {
+ DST_FAC_ALPHA_ZERO,
+ DST_FAC_ALPHA_ONE,
+ DST_FAC_ALPHA_SRC,
+ DST_FAC_ALPHA_SRC_INVERSE,
+ DST_FAC_ALPHA_DST,
+ DST_FAC_ALPHA_DST_GLOBAL,
+};
+
+#define RK3568_GRF_VO_CON1 0x0364
+/* System registers definition */
+#define RK3568_REG_CFG_DONE 0x000
+#define RK3568_VERSION_INFO 0x004
+#define RK3568_SYS_AUTO_GATING_CTRL 0x008
+#define RK3568_SYS_AXI_LUT_CTRL 0x024
+#define RK3568_DSP_IF_EN 0x028
+#define RK3568_DSP_IF_CTRL 0x02c
+#define RK3568_DSP_IF_POL 0x030
+#define RK3568_WB_CTRL 0x40
+#define RK3568_WB_XSCAL_FACTOR 0x44
+#define RK3568_WB_YRGB_MST 0x48
+#define RK3568_WB_CBR_MST 0x4C
+#define RK3568_OTP_WIN_EN 0x050
+#define RK3568_LUT_PORT_SEL 0x058
+#define RK3568_SYS_STATUS0 0x060
+#define RK3568_VP_LINE_FLAG(vp) (0x70 + (vp) * 0x4)
+#define RK3568_SYS0_INT_EN 0x80
+#define RK3568_SYS0_INT_CLR 0x84
+#define RK3568_SYS0_INT_STATUS 0x88
+#define RK3568_SYS1_INT_EN 0x90
+#define RK3568_SYS1_INT_CLR 0x94
+#define RK3568_SYS1_INT_STATUS 0x98
+#define RK3568_VP_INT_EN(vp) (0xA0 + (vp) * 0x10)
+#define RK3568_VP_INT_CLR(vp) (0xA4 + (vp) * 0x10)
+#define RK3568_VP_INT_STATUS(vp) (0xA8 + (vp) * 0x10)
+#define RK3568_VP_INT_RAW_STATUS(vp) (0xAC + (vp) * 0x10)
+
+/* Video Port registers definition */
+#define RK3568_VP_DSP_CTRL 0x00
+#define RK3568_VP_MIPI_CTRL 0x04
+#define RK3568_VP_COLOR_BAR_CTRL 0x08
+#define RK3568_VP_3D_LUT_CTRL 0x10
+#define RK3568_VP_3D_LUT_MST 0x20
+#define RK3568_VP_DSP_BG 0x2C
+#define RK3568_VP_PRE_SCAN_HTIMING 0x30
+#define RK3568_VP_POST_DSP_HACT_INFO 0x34
+#define RK3568_VP_POST_DSP_VACT_INFO 0x38
+#define RK3568_VP_POST_SCL_FACTOR_YRGB 0x3C
+#define RK3568_VP_POST_SCL_CTRL 0x40
+#define RK3568_VP_POST_DSP_VACT_INFO_F1 0x44
+#define RK3568_VP_DSP_HTOTAL_HS_END 0x48
+#define RK3568_VP_DSP_HACT_ST_END 0x4C
+#define RK3568_VP_DSP_VTOTAL_VS_END 0x50
+#define RK3568_VP_DSP_VACT_ST_END 0x54
+#define RK3568_VP_DSP_VS_ST_END_F1 0x58
+#define RK3568_VP_DSP_VACT_ST_END_F1 0x5C
+#define RK3568_VP_BCSH_CTRL 0x60
+#define RK3568_VP_BCSH_BCS 0x64
+#define RK3568_VP_BCSH_H 0x68
+#define RK3568_VP_BCSH_COLOR_BAR 0x6C
+
+/* Overlay registers definition */
+#define RK3568_OVL_CTRL 0x600
+#define RK3568_OVL_LAYER_SEL 0x604
+#define RK3568_OVL_PORT_SEL 0x608
+#define RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL 0x610
+#define RK3568_CLUSTER0_MIX_DST_COLOR_CTRL 0x614
+#define RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL 0x618
+#define RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL 0x61C
+#define RK3568_MIX0_SRC_COLOR_CTRL 0x650
+#define RK3568_MIX0_DST_COLOR_CTRL 0x654
+#define RK3568_MIX0_SRC_ALPHA_CTRL 0x658
+#define RK3568_MIX0_DST_ALPHA_CTRL 0x65C
+#define RK3568_HDR0_SRC_COLOR_CTRL 0x6C0
+#define RK3568_HDR0_DST_COLOR_CTRL 0x6C4
+#define RK3568_HDR0_SRC_ALPHA_CTRL 0x6C8
+#define RK3568_HDR0_DST_ALPHA_CTRL 0x6CC
+#define RK3568_VP_BG_MIX_CTRL(vp) (0x6E0 + (vp) * 4)
+#define RK3568_CLUSTER_DLY_NUM 0x6F0
+#define RK3568_SMART_DLY_NUM 0x6F8
+
+/* Cluster register definition, offset relative to window base */
+#define RK3568_CLUSTER_WIN_CTRL0 0x00
+#define RK3568_CLUSTER_WIN_CTRL1 0x04
+#define RK3568_CLUSTER_WIN_YRGB_MST 0x10
+#define RK3568_CLUSTER_WIN_CBR_MST 0x14
+#define RK3568_CLUSTER_WIN_VIR 0x18
+#define RK3568_CLUSTER_WIN_ACT_INFO 0x20
+#define RK3568_CLUSTER_WIN_DSP_INFO 0x24
+#define RK3568_CLUSTER_WIN_DSP_ST 0x28
+#define RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB 0x30
+#define RK3568_CLUSTER_WIN_AFBCD_TRANSFORM_OFFSET 0x3C
+#define RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL 0x50
+#define RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE 0x54
+#define RK3568_CLUSTER_WIN_AFBCD_HDR_PTR 0x58
+#define RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH 0x5C
+#define RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE 0x60
+#define RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET 0x64
+#define RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET 0x68
+#define RK3568_CLUSTER_WIN_AFBCD_CTRL 0x6C
+
+#define RK3568_CLUSTER_CTRL 0x100
+
+/* (E)smart register definition, offset relative to window base */
+#define RK3568_SMART_CTRL0 0x00
+#define RK3568_SMART_CTRL1 0x04
+#define RK3568_SMART_REGION0_CTRL 0x10
+#define RK3568_SMART_REGION0_YRGB_MST 0x14
+#define RK3568_SMART_REGION0_CBR_MST 0x18
+#define RK3568_SMART_REGION0_VIR 0x1C
+#define RK3568_SMART_REGION0_ACT_INFO 0x20
+#define RK3568_SMART_REGION0_DSP_INFO 0x24
+#define RK3568_SMART_REGION0_DSP_ST 0x28
+#define RK3568_SMART_REGION0_SCL_CTRL 0x30
+#define RK3568_SMART_REGION0_SCL_FACTOR_YRGB 0x34
+#define RK3568_SMART_REGION0_SCL_FACTOR_CBR 0x38
+#define RK3568_SMART_REGION0_SCL_OFFSET 0x3C
+#define RK3568_SMART_REGION1_CTRL 0x40
+#define RK3568_SMART_REGION1_YRGB_MST 0x44
+#define RK3568_SMART_REGION1_CBR_MST 0x48
+#define RK3568_SMART_REGION1_VIR 0x4C
+#define RK3568_SMART_REGION1_ACT_INFO 0x50
+#define RK3568_SMART_REGION1_DSP_INFO 0x54
+#define RK3568_SMART_REGION1_DSP_ST 0x58
+#define RK3568_SMART_REGION1_SCL_CTRL 0x60
+#define RK3568_SMART_REGION1_SCL_FACTOR_YRGB 0x64
+#define RK3568_SMART_REGION1_SCL_FACTOR_CBR 0x68
+#define RK3568_SMART_REGION1_SCL_OFFSET 0x6C
+#define RK3568_SMART_REGION2_CTRL 0x70
+#define RK3568_SMART_REGION2_YRGB_MST 0x74
+#define RK3568_SMART_REGION2_CBR_MST 0x78
+#define RK3568_SMART_REGION2_VIR 0x7C
+#define RK3568_SMART_REGION2_ACT_INFO 0x80
+#define RK3568_SMART_REGION2_DSP_INFO 0x84
+#define RK3568_SMART_REGION2_DSP_ST 0x88
+#define RK3568_SMART_REGION2_SCL_CTRL 0x90
+#define RK3568_SMART_REGION2_SCL_FACTOR_YRGB 0x94
+#define RK3568_SMART_REGION2_SCL_FACTOR_CBR 0x98
+#define RK3568_SMART_REGION2_SCL_OFFSET 0x9C
+#define RK3568_SMART_REGION3_CTRL 0xA0
+#define RK3568_SMART_REGION3_YRGB_MST 0xA4
+#define RK3568_SMART_REGION3_CBR_MST 0xA8
+#define RK3568_SMART_REGION3_VIR 0xAC
+#define RK3568_SMART_REGION3_ACT_INFO 0xB0
+#define RK3568_SMART_REGION3_DSP_INFO 0xB4
+#define RK3568_SMART_REGION3_DSP_ST 0xB8
+#define RK3568_SMART_REGION3_SCL_CTRL 0xC0
+#define RK3568_SMART_REGION3_SCL_FACTOR_YRGB 0xC4
+#define RK3568_SMART_REGION3_SCL_FACTOR_CBR 0xC8
+#define RK3568_SMART_REGION3_SCL_OFFSET 0xCC
+#define RK3568_SMART_COLOR_KEY_CTRL 0xD0
+
+/* HDR register definition */
+#define RK3568_HDR_LUT_CTRL 0x2000
+#define RK3568_HDR_LUT_MST 0x2004
+#define RK3568_SDR2HDR_CTRL 0x2010
+#define RK3568_HDR2SDR_CTRL 0x2020
+#define RK3568_HDR2SDR_SRC_RANGE 0x2024
+#define RK3568_HDR2SDR_NORMFACEETF 0x2028
+#define RK3568_HDR2SDR_DST_RANGE 0x202C
+#define RK3568_HDR2SDR_NORMFACCGAMMA 0x2030
+#define RK3568_HDR_EETF_OETF_Y0 0x203C
+#define RK3568_HDR_SAT_Y0 0x20C0
+#define RK3568_HDR_EOTF_OETF_Y0 0x20F0
+#define RK3568_HDR_OETF_DX_POW1 0x2200
+#define RK3568_HDR_OETF_XN1 0x2300
+
+#define RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN BIT(15)
+
+#define RK3568_VP_DSP_CTRL__STANDBY BIT(31)
+#define RK3568_VP_DSP_CTRL__DITHER_DOWN_MODE BIT(20)
+#define RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL GENMASK(19, 18)
+#define RK3568_VP_DSP_CTRL__DITHER_DOWN_EN BIT(17)
+#define RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN BIT(16)
+#define RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y BIT(15)
+#define RK3568_VP_DSP_CTRL__DSP_RB_SWAP BIT(9)
+#define RK3568_VP_DSP_CTRL__DSP_INTERLACE BIT(7)
+#define RK3568_VP_DSP_CTRL__DSP_FILED_POL BIT(6)
+#define RK3568_VP_DSP_CTRL__P2I_EN BIT(5)
+#define RK3568_VP_DSP_CTRL__CORE_DCLK_DIV BIT(4)
+#define RK3568_VP_DSP_CTRL__OUT_MODE GENMASK(3, 0)
+
+#define RK3568_VP_POST_SCL_CTRL__VSCALEDOWN BIT(1)
+#define RK3568_VP_POST_SCL_CTRL__HSCALEDOWN BIT(0)
+
+#define RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX GENMASK(26, 25)
+#define RK3568_SYS_DSP_INFACE_EN_LVDS1 BIT(24)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX GENMASK(22, 21)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI1 BIT(20)
+#define RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX GENMASK(19, 18)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX GENMASK(17, 16)
+#define RK3568_SYS_DSP_INFACE_EN_EDP_MUX GENMASK(15, 14)
+#define RK3568_SYS_DSP_INFACE_EN_HDMI_MUX GENMASK(11, 10)
+#define RK3568_SYS_DSP_INFACE_EN_RGB_MUX GENMASK(9, 8)
+#define RK3568_SYS_DSP_INFACE_EN_LVDS0 BIT(5)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI0 BIT(4)
+#define RK3568_SYS_DSP_INFACE_EN_EDP BIT(3)
+#define RK3568_SYS_DSP_INFACE_EN_HDMI BIT(1)
+#define RK3568_SYS_DSP_INFACE_EN_RGB BIT(0)
+
+#define RK3568_DSP_IF_POL__MIPI_PIN_POL GENMASK(19, 16)
+#define RK3568_DSP_IF_POL__EDP_PIN_POL GENMASK(15, 12)
+#define RK3568_DSP_IF_POL__HDMI_PIN_POL GENMASK(7, 4)
+#define RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL GENMASK(3, 0)
+
+#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5)
+#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4)
+
+#define RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN BIT(31)
+
+#define RK3568_DSP_IF_POL__CFG_DONE_IMD BIT(28)
+
+#define VOP2_SYS_AXI_BUS_NUM 2
+
+#define VOP2_CLUSTER_YUV444_10 0x12
+
+#define VOP2_COLOR_KEY_MASK BIT(31)
+
+#define RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD BIT(28)
+
+#define RK3568_VP_BG_MIX_CTRL__BG_DLY GENMASK(31, 24)
+
+#define RK3568_OVL_PORT_SEL__SEL_PORT GENMASK(31, 16)
+#define RK3568_OVL_PORT_SEL__SMART1 GENMASK(31, 30)
+#define RK3568_OVL_PORT_SEL__SMART0 GENMASK(29, 28)
+#define RK3568_OVL_PORT_SEL__ESMART1 GENMASK(27, 26)
+#define RK3568_OVL_PORT_SEL__ESMART0 GENMASK(25, 24)
+#define RK3568_OVL_PORT_SEL__CLUSTER1 GENMASK(19, 18)
+#define RK3568_OVL_PORT_SEL__CLUSTER0 GENMASK(17, 16)
+#define RK3568_OVL_PORT_SET__PORT2_MUX GENMASK(11, 8)
+#define RK3568_OVL_PORT_SET__PORT1_MUX GENMASK(7, 4)
+#define RK3568_OVL_PORT_SET__PORT0_MUX GENMASK(3, 0)
+#define RK3568_OVL_LAYER_SEL__LAYER(layer, x) ((x) << ((layer) * 4))
+
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_1 GENMASK(31, 24)
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_0 GENMASK(23, 16)
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_1 GENMASK(15, 8)
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_0 GENMASK(7, 0)
+
+#define RK3568_SMART_DLY_NUM__SMART1 GENMASK(31, 24)
+#define RK3568_SMART_DLY_NUM__SMART0 GENMASK(23, 16)
+#define RK3568_SMART_DLY_NUM__ESMART1 GENMASK(15, 8)
+#define RK3568_SMART_DLY_NUM__ESMART0 GENMASK(7, 0)
+
+#define VP_INT_DSP_HOLD_VALID BIT(6)
+#define VP_INT_FS_FIELD BIT(5)
+#define VP_INT_POST_BUF_EMPTY BIT(4)
+#define VP_INT_LINE_FLAG1 BIT(3)
+#define VP_INT_LINE_FLAG0 BIT(2)
+#define VOP2_INT_BUS_ERRPR BIT(1)
+#define VP_INT_FS BIT(0)
+
+#define POLFLAG_DCLK_INV BIT(3)
+
+enum vop2_layer_phy_id {
+ ROCKCHIP_VOP2_CLUSTER0 = 0,
+ ROCKCHIP_VOP2_CLUSTER1,
+ ROCKCHIP_VOP2_ESMART0,
+ ROCKCHIP_VOP2_ESMART1,
+ ROCKCHIP_VOP2_SMART0,
+ ROCKCHIP_VOP2_SMART1,
+ ROCKCHIP_VOP2_CLUSTER2,
+ ROCKCHIP_VOP2_CLUSTER3,
+ ROCKCHIP_VOP2_ESMART2,
+ ROCKCHIP_VOP2_ESMART3,
+ ROCKCHIP_VOP2_PHY_ID_INVALID = -1,
+};
+
+extern const struct component_ops vop2_component_ops;
+
+#endif /* _ROCKCHIP_DRM_VOP2_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 997b7d46a2d1..5a284332ec49 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -36,12 +36,6 @@
struct rockchip_lvds;
-#define connector_to_lvds(c) \
- container_of(c, struct rockchip_lvds, connector)
-
-#define encoder_to_lvds(c) \
- container_of(c, struct rockchip_lvds, encoder)
-
/**
* struct rockchip_lvds_soc_data - rockchip lvds Soc private data
* @probe: LVDS platform probe function
@@ -65,10 +59,22 @@ struct rockchip_lvds {
struct drm_panel *panel;
struct drm_bridge *bridge;
struct drm_connector connector;
- struct drm_encoder encoder;
+ struct rockchip_encoder encoder;
struct dev_pin_info *pins;
};
+static inline struct rockchip_lvds *connector_to_lvds(struct drm_connector *connector)
+{
+ return container_of(connector, struct rockchip_lvds, connector);
+}
+
+static inline struct rockchip_lvds *encoder_to_lvds(struct drm_encoder *encoder)
+{
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ return container_of(rkencoder, struct rockchip_lvds, encoder);
+}
+
static inline void rk3288_writel(struct rockchip_lvds *lvds, u32 offset,
u32 val)
{
@@ -599,7 +605,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
goto err_put_remote;
}
- encoder = &lvds->encoder;
+ encoder = &lvds->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
@@ -674,10 +680,10 @@ static void rockchip_lvds_unbind(struct device *dev, struct device *master,
const struct drm_encoder_helper_funcs *encoder_funcs;
encoder_funcs = lvds->soc_data->helper_funcs;
- encoder_funcs->disable(&lvds->encoder);
+ encoder_funcs->disable(&lvds->encoder.encoder);
pm_runtime_disable(dev);
drm_connector_cleanup(&lvds->connector);
- drm_encoder_cleanup(&lvds->encoder);
+ drm_encoder_cleanup(&lvds->encoder.encoder);
}
static const struct component_ops rockchip_lvds_component_ops = {
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
new file mode 100644
index 000000000000..9bf0637bf8e2
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Rockchip Electronics Co.Ltd
+ * Author: Andy Yan <[email protected]>
+ */
+
+#include <linux/kernel.h>
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "rockchip_drm_vop2.h"
+
+static const uint32_t formats_win_full_10bit[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+};
+
+static const uint32_t formats_win_full_10bit_yuyv[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_VYUY,
+};
+
+static const uint32_t formats_win_lite[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+};
+
+static const uint64_t format_modifiers[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const uint64_t format_modifiers_afbc[] = {
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_CBR),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_CBR |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_CBR),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_CBR |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ /* SPLIT mandates SPARSE, RGB modes mandates YTR */
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_SPARSE |
+ AFBC_FORMAT_MOD_SPLIT),
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const struct vop2_video_port_data rk3568_vop_video_ports[] = {
+ {
+ .id = 0,
+ .feature = VOP_FEATURE_OUTPUT_10BIT,
+ .gamma_lut_len = 1024,
+ .cubic_lut_len = 9 * 9 * 9,
+ .max_output = { 4096, 2304 },
+ .pre_scan_max_dly = { 69, 53, 53, 42 },
+ .offset = 0xc00,
+ }, {
+ .id = 1,
+ .gamma_lut_len = 1024,
+ .max_output = { 2048, 1536 },
+ .pre_scan_max_dly = { 40, 40, 40, 40 },
+ .offset = 0xd00,
+ }, {
+ .id = 2,
+ .gamma_lut_len = 1024,
+ .max_output = { 1920, 1080 },
+ .pre_scan_max_dly = { 40, 40, 40, 40 },
+ .offset = 0xe00,
+ },
+};
+
+/*
+ * rk3568 vop with 2 cluster, 2 esmart win, 2 smart win.
+ * Every cluster can work as 4K win or split into two win.
+ * All win in cluster support AFBCD.
+ *
+ * Every esmart win and smart win support 4 Multi-region.
+ *
+ * Scale filter mode:
+ *
+ * * Cluster: bicubic for horizontal scale up, others use bilinear
+ * * ESmart:
+ * * nearest-neighbor/bilinear/bicubic for scale up
+ * * nearest-neighbor/bilinear/average for scale down
+ *
+ *
+ * @TODO describe the wind like cpu-map dt nodes;
+ */
+static const struct vop2_win_data rk3568_vop_win_data[] = {
+ {
+ .name = "Smart0-win0",
+ .phys_id = ROCKCHIP_VOP2_SMART0,
+ .base = 0x1c00,
+ .formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers,
+ .layer_sel_id = 3,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Smart1-win0",
+ .phys_id = ROCKCHIP_VOP2_SMART1,
+ .formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers,
+ .base = 0x1e00,
+ .layer_sel_id = 7,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Esmart1-win0",
+ .phys_id = ROCKCHIP_VOP2_ESMART1,
+ .formats = formats_win_full_10bit_yuyv,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit_yuyv),
+ .format_modifiers = format_modifiers,
+ .base = 0x1a00,
+ .layer_sel_id = 6,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Esmart0-win0",
+ .phys_id = ROCKCHIP_VOP2_ESMART0,
+ .formats = formats_win_full_10bit_yuyv,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit_yuyv),
+ .format_modifiers = format_modifiers,
+ .base = 0x1800,
+ .layer_sel_id = 2,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Cluster0-win0",
+ .phys_id = ROCKCHIP_VOP2_CLUSTER0,
+ .base = 0x1000,
+ .formats = formats_win_full_10bit,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit),
+ .format_modifiers = format_modifiers_afbc,
+ .layer_sel_id = 0,
+ .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y,
+ .max_upscale_factor = 4,
+ .max_downscale_factor = 4,
+ .dly = { 0, 27, 21 },
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER,
+ }, {
+ .name = "Cluster1-win0",
+ .phys_id = ROCKCHIP_VOP2_CLUSTER1,
+ .base = 0x1200,
+ .formats = formats_win_full_10bit,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit),
+ .format_modifiers = format_modifiers_afbc,
+ .layer_sel_id = 1,
+ .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .max_upscale_factor = 4,
+ .max_downscale_factor = 4,
+ .dly = { 0, 27, 21 },
+ .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER,
+ },
+};
+
+static const struct vop2_data rk3566_vop = {
+ .nr_vps = 3,
+ .max_input = { 4096, 2304 },
+ .max_output = { 4096, 2304 },
+ .vp = rk3568_vop_video_ports,
+ .win = rk3568_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3568_vop_win_data),
+ .soc_id = 3566,
+};
+
+static const struct vop2_data rk3568_vop = {
+ .nr_vps = 3,
+ .max_input = { 4096, 2304 },
+ .max_output = { 4096, 2304 },
+ .vp = rk3568_vop_video_ports,
+ .win = rk3568_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3568_vop_win_data),
+ .soc_id = 3568,
+};
+
+static const struct of_device_id vop2_dt_match[] = {
+ {
+ .compatible = "rockchip,rk3566-vop",
+ .data = &rk3566_vop,
+ }, {
+ .compatible = "rockchip,rk3568-vop",
+ .data = &rk3568_vop,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, vop2_dt_match);
+
+static int vop2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ return component_add(dev, &vop2_component_ops);
+}
+
+static int vop2_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &vop2_component_ops);
+
+ return 0;
+}
+
+struct platform_driver vop2_platform_driver = {
+ .probe = vop2_probe,
+ .remove = vop2_remove,
+ .driver = {
+ .name = "rockchip-vop2",
+ .of_match_table = of_match_ptr(vop2_dt_match),
+ },
+};
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
index 798b542e5916..d03dd0402923 100644
--- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
+++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c
@@ -46,8 +46,11 @@ static const uint32_t formats_win_full[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR565,
DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
+ DRM_FORMAT_NV61,
DRM_FORMAT_NV24,
+ DRM_FORMAT_NV42,
};
static const uint64_t format_modifiers_win_full[] = {
@@ -272,6 +275,7 @@ static const struct vop_win_phy px30_win0_data = {
.enable = VOP_REG(PX30_WIN0_CTRL0, 0x1, 0),
.format = VOP_REG(PX30_WIN0_CTRL0, 0x7, 1),
.rb_swap = VOP_REG(PX30_WIN0_CTRL0, 0x1, 12),
+ .uv_swap = VOP_REG(PX30_WIN0_CTRL0, 0x1, 15),
.act_info = VOP_REG(PX30_WIN0_ACT_INFO, 0xffffffff, 0),
.dsp_info = VOP_REG(PX30_WIN0_DSP_INFO, 0xffffffff, 0),
.dsp_st = VOP_REG(PX30_WIN0_DSP_ST, 0xffffffff, 0),
@@ -291,6 +295,7 @@ static const struct vop_win_phy px30_win1_data = {
.enable = VOP_REG(PX30_WIN1_CTRL0, 0x1, 0),
.format = VOP_REG(PX30_WIN1_CTRL0, 0x7, 4),
.rb_swap = VOP_REG(PX30_WIN1_CTRL0, 0x1, 12),
+ .uv_swap = VOP_REG(PX30_WIN1_CTRL0, 0x1, 15),
.dsp_info = VOP_REG(PX30_WIN1_DSP_INFO, 0xffffffff, 0),
.dsp_st = VOP_REG(PX30_WIN1_DSP_ST, 0xffffffff, 0),
.yrgb_mst = VOP_REG(PX30_WIN1_MST, 0xffffffff, 0),
@@ -368,6 +373,7 @@ static const struct vop_win_phy rk3066_win0_data = {
.enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 0),
.format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 4),
.rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 19),
+ .uv_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 22),
.act_info = VOP_REG(RK3066_WIN0_ACT_INFO, 0x1fff1fff, 0),
.dsp_info = VOP_REG(RK3066_WIN0_DSP_INFO, 0x0fff0fff, 0),
.dsp_st = VOP_REG(RK3066_WIN0_DSP_ST, 0x1fff1fff, 0),
@@ -386,6 +392,7 @@ static const struct vop_win_phy rk3066_win1_data = {
.enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 1),
.format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 7),
.rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 23),
+ .uv_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 26),
.act_info = VOP_REG(RK3066_WIN1_ACT_INFO, 0x1fff1fff, 0),
.dsp_info = VOP_REG(RK3066_WIN1_DSP_INFO, 0x0fff0fff, 0),
.dsp_st = VOP_REG(RK3066_WIN1_DSP_ST, 0x1fff1fff, 0),
@@ -489,6 +496,7 @@ static const struct vop_win_phy rk3188_win0_data = {
.enable = VOP_REG(RK3188_SYS_CTRL, 0x1, 0),
.format = VOP_REG(RK3188_SYS_CTRL, 0x7, 3),
.rb_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 15),
+ .uv_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 18),
.act_info = VOP_REG(RK3188_WIN0_ACT_INFO, 0x1fff1fff, 0),
.dsp_info = VOP_REG(RK3188_WIN0_DSP_INFO, 0x0fff0fff, 0),
.dsp_st = VOP_REG(RK3188_WIN0_DSP_ST, 0x1fff1fff, 0),
@@ -619,6 +627,7 @@ static const struct vop_win_phy rk3288_win01_data = {
.enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0),
.format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1),
.rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12),
+ .uv_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 15),
.act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0),
.dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0),
.dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0),
@@ -753,6 +762,7 @@ static const struct vop_win_phy rk3368_win01_data = {
.enable = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 0),
.format = VOP_REG(RK3368_WIN0_CTRL0, 0x7, 1),
.rb_swap = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 12),
+ .uv_swap = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 15),
.x_mir_en = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 21),
.y_mir_en = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 22),
.act_info = VOP_REG(RK3368_WIN0_ACT_INFO, 0x1fff1fff, 0),
@@ -902,6 +912,7 @@ static const struct vop_win_phy rk3399_win01_data = {
.enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0),
.format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1),
.rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12),
+ .uv_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 15),
.x_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 21),
.y_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 22),
.act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0),
diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
index 527c7b2474da..b4dfa166eccd 100644
--- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
+++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c
@@ -1193,8 +1193,8 @@ err_remove_dsi_host:
err_unprotect_clk:
clk_rate_exclusive_put(dsi->mod_clk);
err_attach_clk:
- if (!IS_ERR(dsi->bus_clk))
- regmap_mmio_detach_clk(dsi->regs);
+ regmap_mmio_detach_clk(dsi->regs);
+
return ret;
}
@@ -1207,8 +1207,7 @@ static int sun6i_dsi_remove(struct platform_device *pdev)
mipi_dsi_host_unregister(&dsi->host);
clk_rate_exclusive_put(dsi->mod_clk);
- if (!IS_ERR(dsi->bus_clk))
- regmap_mmio_detach_clk(dsi->regs);
+ regmap_mmio_detach_clk(dsi->regs);
return 0;
}
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c
index f5b8e864a5cd..768242a78e2b 100644
--- a/drivers/gpu/drm/tiny/simpledrm.c
+++ b/drivers/gpu/drm/tiny/simpledrm.c
@@ -801,7 +801,6 @@ static int simpledrm_device_init_modeset(struct simpledrm_device *sdev)
dev->mode_config.max_width = max_width;
dev->mode_config.min_height = mode->vdisplay;
dev->mode_config.max_height = max_height;
- dev->mode_config.prefer_shadow_fbdev = true;
dev->mode_config.preferred_depth = sdev->format->cpp[0] * 8;
dev->mode_config.funcs = &simpledrm_mode_config_funcs;
diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c
index e451cc5bcfac..49c0f2ac868b 100644
--- a/drivers/gpu/drm/vc4/vc4_bo.c
+++ b/drivers/gpu/drm/vc4/vc4_bo.c
@@ -738,19 +738,13 @@ static const struct drm_gem_object_funcs vc4_gem_object_funcs = {
static int vc4_grab_bin_bo(struct vc4_dev *vc4, struct vc4_file *vc4file)
{
- int ret;
-
if (!vc4->v3d)
return -ENODEV;
if (vc4file->bin_bo_used)
return 0;
- ret = vc4_v3d_bin_bo_get(vc4, &vc4file->bin_bo_used);
- if (ret)
- return ret;
-
- return 0;
+ return vc4_v3d_bin_bo_get(vc4, &vc4file->bin_bo_used);
}
int vc4_create_bo_ioctl(struct drm_device *dev, void *data,
diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c
index 920a9eefe426..b3438f4a81ce 100644
--- a/drivers/gpu/drm/vc4/vc4_plane.c
+++ b/drivers/gpu/drm/vc4/vc4_plane.c
@@ -1350,7 +1350,6 @@ static int vc4_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct vc4_bo *bo;
- int ret;
if (!state->fb)
return 0;
@@ -1362,11 +1361,7 @@ static int vc4_prepare_fb(struct drm_plane *plane,
if (plane->state->fb == state->fb)
return 0;
- ret = vc4_bo_inc_usecnt(bo);
- if (ret)
- return ret;
-
- return 0;
+ return vc4_bo_inc_usecnt(bo);
}
static void vc4_cleanup_fb(struct drm_plane *plane,
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c
index adf17c740656..0ba9739f406d 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c
@@ -316,19 +316,18 @@ static int vmw_fb_pan_display(struct fb_var_screeninfo *var,
return 0;
}
-static void vmw_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void vmw_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
struct vmw_fb_par *par = info->par;
unsigned long start, end, min, max;
unsigned long flags;
- struct page *page;
+ struct fb_deferred_io_pageref *pageref;
int y1, y2;
min = ULONG_MAX;
max = 0;
- list_for_each_entry(page, pagelist, lru) {
- start = page->index << PAGE_SHIFT;
+ list_for_each_entry(pageref, pagereflist, list) {
+ start = pageref->offset;
end = start + PAGE_SIZE - 1;
min = min(min, start);
max = max(max, end);
@@ -619,6 +618,7 @@ static const struct fb_ops vmw_fb_ops = {
.fb_imageblit = vmw_fb_imageblit,
.fb_pan_display = vmw_fb_pan_display,
.fb_blank = vmw_fb_blank,
+ .fb_mmap = fb_deferred_io_mmap,
};
int vmw_fb_init(struct vmw_private *vmw_priv)
diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c
index 33c102a60992..de61c08fabea 100644
--- a/drivers/hid/hid-picolcd_fb.c
+++ b/drivers/hid/hid-picolcd_fb.c
@@ -428,11 +428,12 @@ static const struct fb_ops picolcdfb_ops = {
.fb_imageblit = picolcd_fb_imageblit,
.fb_check_var = picolcd_fb_check_var,
.fb_set_par = picolcd_set_par,
+ .fb_mmap = fb_deferred_io_mmap,
};
/* Callback from deferred IO workqueue */
-static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist)
+static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
picolcd_fb_update(info);
}
diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c
index 9c4d797e7ae4..60b2278d8b16 100644
--- a/drivers/staging/fbtft/fbtft-core.c
+++ b/drivers/staging/fbtft/fbtft-core.c
@@ -322,12 +322,11 @@ static void fbtft_mkdirty(struct fb_info *info, int y, int height)
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
-static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist)
+static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
struct fbtft_par *par = info->par;
unsigned int dirty_lines_start, dirty_lines_end;
- struct page *page;
- unsigned long index;
+ struct fb_deferred_io_pageref *pageref;
unsigned int y_low = 0, y_high = 0;
int count = 0;
@@ -340,14 +339,13 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist)
spin_unlock(&par->dirty_lock);
/* Mark display lines as dirty */
- list_for_each_entry(page, pagelist, lru) {
+ list_for_each_entry(pageref, pagereflist, list) {
count++;
- index = page->index << PAGE_SHIFT;
- y_low = index / info->fix.line_length;
- y_high = (index + PAGE_SIZE - 1) / info->fix.line_length;
+ y_low = pageref->offset / info->fix.line_length;
+ y_high = (pageref->offset + PAGE_SIZE - 1) / info->fix.line_length;
dev_dbg(info->device,
"page->index=%lu y_low=%d y_high=%d\n",
- page->index, y_low, y_high);
+ pageref->page->index, y_low, y_high);
if (y_high > info->var.yres - 1)
y_high = info->var.yres - 1;
if (y_low < dirty_lines_start)
@@ -652,10 +650,11 @@ struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display,
fbops->fb_imageblit = fbtft_fb_imageblit;
fbops->fb_setcolreg = fbtft_fb_setcolreg;
fbops->fb_blank = fbtft_fb_blank;
+ fbops->fb_mmap = fb_deferred_io_mmap;
- fbdefio->delay = HZ / fps;
- fbdefio->sort_pagelist = true;
- fbdefio->deferred_io = fbtft_deferred_io;
+ fbdefio->delay = HZ / fps;
+ fbdefio->sort_pagereflist = true;
+ fbdefio->deferred_io = fbtft_deferred_io;
fb_deferred_io_init(info);
snprintf(info->fix.id, sizeof(info->fix.id), "%s", dev->driver->name);
diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c
index b9054f658838..55e62dd96f9b 100644
--- a/drivers/video/fbdev/broadsheetfb.c
+++ b/drivers/video/fbdev/broadsheetfb.c
@@ -929,13 +929,11 @@ static void broadsheetfb_dpy_update(struct broadsheetfb_par *par)
}
/* this is called back from the deferred io workqueue */
-static void broadsheetfb_dpy_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void broadsheetfb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
u16 y1 = 0, h = 0;
- int prev_index = -1;
- struct page *cur;
- struct fb_deferred_io *fbdefio = info->fbdefio;
+ unsigned long prev_offset = ULONG_MAX;
+ struct fb_deferred_io_pageref *pageref;
int h_inc;
u16 yres = info->var.yres;
u16 xres = info->var.xres;
@@ -944,22 +942,22 @@ static void broadsheetfb_dpy_deferred_io(struct fb_info *info,
h_inc = DIV_ROUND_UP(PAGE_SIZE , xres);
/* walk the written page list and swizzle the data */
- list_for_each_entry(cur, &fbdefio->pagelist, lru) {
- if (prev_index < 0) {
+ list_for_each_entry(pageref, pagereflist, list) {
+ if (prev_offset == ULONG_MAX) {
/* just starting so assign first page */
- y1 = (cur->index << PAGE_SHIFT) / xres;
+ y1 = pageref->offset / xres;
h = h_inc;
- } else if ((prev_index + 1) == cur->index) {
+ } else if ((prev_offset + PAGE_SIZE) == pageref->offset) {
/* this page is consecutive so increase our height */
h += h_inc;
} else {
/* page not consecutive, issue previous update first */
broadsheetfb_dpy_update_pages(info->par, y1, y1 + h);
/* start over with our non consecutive page */
- y1 = (cur->index << PAGE_SHIFT) / xres;
+ y1 = pageref->offset / xres;
h = h_inc;
}
- prev_index = cur->index;
+ prev_offset = pageref->offset;
}
/* if we still have any pages to update we do so now */
@@ -1055,12 +1053,13 @@ static const struct fb_ops broadsheetfb_ops = {
.fb_fillrect = broadsheetfb_fillrect,
.fb_copyarea = broadsheetfb_copyarea,
.fb_imageblit = broadsheetfb_imageblit,
+ .fb_mmap = fb_deferred_io_mmap,
};
static struct fb_deferred_io broadsheetfb_defio = {
- .delay = HZ/4,
- .sort_pagelist = true,
- .deferred_io = broadsheetfb_dpy_deferred_io,
+ .delay = HZ/4,
+ .sort_pagereflist = true,
+ .deferred_io = broadsheetfb_dpy_deferred_io,
};
static int broadsheetfb_probe(struct platform_device *dev)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index 6aaf6d0abf39..c730253ab85c 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -36,6 +36,60 @@ static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs
return page;
}
+static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info *info,
+ unsigned long offset,
+ struct page *page)
+{
+ struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct list_head *pos = &fbdefio->pagereflist;
+ unsigned long pgoff = offset >> PAGE_SHIFT;
+ struct fb_deferred_io_pageref *pageref, *cur;
+
+ if (WARN_ON_ONCE(pgoff >= info->npagerefs))
+ return NULL; /* incorrect allocation size */
+
+ /* 1:1 mapping between pageref and page offset */
+ pageref = &info->pagerefs[pgoff];
+
+ /*
+ * This check is to catch the case where a new process could start
+ * writing to the same page through a new PTE. This new access
+ * can cause a call to .page_mkwrite even if the original process'
+ * PTE is marked writable.
+ */
+ if (!list_empty(&pageref->list))
+ goto pageref_already_added;
+
+ pageref->page = page;
+ pageref->offset = pgoff << PAGE_SHIFT;
+
+ if (unlikely(fbdefio->sort_pagereflist)) {
+ /*
+ * We loop through the list of pagerefs before adding in
+ * order to keep the pagerefs sorted. This has significant
+ * overhead of O(n^2) with n being the number of written
+ * pages. If possible, drivers should try to work with
+ * unsorted page lists instead.
+ */
+ list_for_each_entry(cur, &fbdefio->pagereflist, list) {
+ if (cur->offset > pageref->offset)
+ break;
+ }
+ pos = &cur->list;
+ }
+
+ list_add_tail(&pageref->list, pos);
+
+pageref_already_added:
+ return pageref;
+}
+
+static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
+ struct fb_info *info)
+{
+ list_del_init(&pageref->list);
+}
+
/* this is to find and return the vmalloc-ed fb pages */
static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
{
@@ -59,7 +113,7 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
printk(KERN_ERR "no mapping available\n");
BUG_ON(!page->mapping);
- page->index = vmf->pgoff;
+ page->index = vmf->pgoff; /* for page_mkclean() */
vmf->page = page;
return 0;
@@ -89,29 +143,30 @@ int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasy
}
EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
-/* vm_ops->page_mkwrite handler */
-static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
+/*
+ * Adds a page to the dirty list. Call this from struct
+ * vm_operations_struct.page_mkwrite.
+ */
+static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
+ struct page *page)
{
- struct page *page = vmf->page;
- struct fb_info *info = vmf->vma->vm_private_data;
struct fb_deferred_io *fbdefio = info->fbdefio;
- struct list_head *pos = &fbdefio->pagelist;
-
- /* this is a callback we get when userspace first tries to
- write to the page. we schedule a workqueue. that workqueue
- will eventually mkclean the touched pages and execute the
- deferred framebuffer IO. then if userspace touches a page
- again, we repeat the same scheme */
-
- file_update_time(vmf->vma->vm_file);
+ struct fb_deferred_io_pageref *pageref;
+ vm_fault_t ret;
/* protect against the workqueue changing the page list */
mutex_lock(&fbdefio->lock);
/* first write in this cycle, notify the driver */
- if (fbdefio->first_io && list_empty(&fbdefio->pagelist))
+ if (fbdefio->first_io && list_empty(&fbdefio->pagereflist))
fbdefio->first_io(info);
+ pageref = fb_deferred_io_pageref_get(info, offset, page);
+ if (WARN_ON_ONCE(!pageref)) {
+ ret = VM_FAULT_OOM;
+ goto err_mutex_unlock;
+ }
+
/*
* We want the page to remain locked from ->page_mkwrite until
* the PTE is marked dirty to avoid page_mkclean() being called
@@ -120,47 +175,49 @@ static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
* Do this by locking the page here and informing the caller
* about it with VM_FAULT_LOCKED.
*/
- lock_page(page);
-
- /*
- * This check is to catch the case where a new process could start
- * writing to the same page through a new PTE. This new access
- * can cause a call to .page_mkwrite even if the original process'
- * PTE is marked writable.
- *
- * TODO: The lru field is owned by the page cache; hence the name.
- * We dequeue in fb_deferred_io_work() after flushing the
- * page's content into video memory. Instead of lru, fbdefio
- * should have it's own field.
- */
- if (!list_empty(&page->lru))
- goto page_already_added;
-
- if (unlikely(fbdefio->sort_pagelist)) {
- /*
- * We loop through the pagelist before adding in order to
- * keep the pagelist sorted. This has significant overhead
- * of O(n^2) with n being the number of written pages. If
- * possible, drivers should try to work with unsorted page
- * lists instead.
- */
- struct page *cur;
+ lock_page(pageref->page);
- list_for_each_entry(cur, &fbdefio->pagelist, lru) {
- if (cur->index > page->index)
- break;
- }
- pos = &cur->lru;
- }
-
- list_add_tail(&page->lru, pos);
-
-page_already_added:
mutex_unlock(&fbdefio->lock);
/* come back after delay to process the deferred IO */
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
return VM_FAULT_LOCKED;
+
+err_mutex_unlock:
+ mutex_unlock(&fbdefio->lock);
+ return ret;
+}
+
+/*
+ * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
+ * @fb_info: The fbdev info structure
+ * @vmf: The VM fault
+ *
+ * This is a callback we get when userspace first tries to
+ * write to the page. We schedule a workqueue. That workqueue
+ * will eventually mkclean the touched pages and execute the
+ * deferred framebuffer IO. Then if userspace touches a page
+ * again, we repeat the same scheme.
+ *
+ * Returns:
+ * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
+ */
+static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
+{
+ unsigned long offset = vmf->address - vmf->vma->vm_start;
+ struct page *page = vmf->page;
+
+ file_update_time(vmf->vma->vm_file);
+
+ return fb_deferred_io_track_page(info, offset, page);
+}
+
+/* vm_ops->page_mkwrite handler */
+static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
+{
+ struct fb_info *info = vmf->vma->vm_private_data;
+
+ return fb_deferred_io_page_mkwrite(info, vmf);
}
static const struct vm_operations_struct fb_deferred_io_vm_ops = {
@@ -181,52 +238,70 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
vma->vm_private_data = info;
return 0;
}
+EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
/* workqueue callback */
static void fb_deferred_io_work(struct work_struct *work)
{
- struct fb_info *info = container_of(work, struct fb_info,
- deferred_work.work);
- struct list_head *node, *next;
- struct page *cur;
+ struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
+ struct fb_deferred_io_pageref *pageref, *next;
struct fb_deferred_io *fbdefio = info->fbdefio;
/* here we mkclean the pages, then do all deferred IO */
mutex_lock(&fbdefio->lock);
- list_for_each_entry(cur, &fbdefio->pagelist, lru) {
+ list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
+ struct page *cur = pageref->page;
lock_page(cur);
page_mkclean(cur);
unlock_page(cur);
}
- /* driver's callback with pagelist */
- fbdefio->deferred_io(info, &fbdefio->pagelist);
+ /* driver's callback with pagereflist */
+ fbdefio->deferred_io(info, &fbdefio->pagereflist);
/* clear the list */
- list_for_each_safe(node, next, &fbdefio->pagelist) {
- list_del_init(node);
- }
+ list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
+ fb_deferred_io_pageref_put(pageref, info);
+
mutex_unlock(&fbdefio->lock);
}
-void fb_deferred_io_init(struct fb_info *info)
+int fb_deferred_io_init(struct fb_info *info)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
- struct page *page;
- unsigned int i;
+ struct fb_deferred_io_pageref *pagerefs;
+ unsigned long npagerefs, i;
+ int ret;
BUG_ON(!fbdefio);
+
+ if (WARN_ON(!info->fix.smem_len))
+ return -EINVAL;
+
mutex_init(&fbdefio->lock);
INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
- INIT_LIST_HEAD(&fbdefio->pagelist);
+ INIT_LIST_HEAD(&fbdefio->pagereflist);
if (fbdefio->delay == 0) /* set a default of 1 s */
fbdefio->delay = HZ;
- /* initialize all the page lists one time */
- for (i = 0; i < info->fix.smem_len; i += PAGE_SIZE) {
- page = fb_deferred_io_page(info, i);
- INIT_LIST_HEAD(&page->lru);
+ npagerefs = DIV_ROUND_UP(info->fix.smem_len, PAGE_SIZE);
+
+ /* alloc a page ref for each page of the display memory */
+ pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL);
+ if (!pagerefs) {
+ ret = -ENOMEM;
+ goto err;
}
+ for (i = 0; i < npagerefs; ++i)
+ INIT_LIST_HEAD(&pagerefs[i].list);
+ info->npagerefs = npagerefs;
+ info->pagerefs = pagerefs;
+
+ return 0;
+
+err:
+ mutex_destroy(&fbdefio->lock);
+ return ret;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_init);
@@ -253,6 +328,7 @@ void fb_deferred_io_cleanup(struct fb_info *info)
page->mapping = NULL;
}
+ kvfree(info->pagerefs);
mutex_destroy(&fbdefio->lock);
}
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
index 84427470367b..b445a7a00def 100644
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -1334,7 +1334,6 @@ static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
- int (*fb_mmap_fn)(struct fb_info *info, struct vm_area_struct *vma);
unsigned long mmio_pgoff;
unsigned long start;
u32 len;
@@ -1343,14 +1342,7 @@ fb_mmap(struct file *file, struct vm_area_struct * vma)
return -ENODEV;
mutex_lock(&info->mm_lock);
- fb_mmap_fn = info->fbops->fb_mmap;
-
-#if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
- if (info->fbdefio)
- fb_mmap_fn = fb_deferred_io_mmap;
-#endif
-
- if (fb_mmap_fn) {
+ if (info->fbops->fb_mmap) {
int res;
/*
@@ -1358,9 +1350,19 @@ fb_mmap(struct file *file, struct vm_area_struct * vma)
* SME protection is removed ahead of the call
*/
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
- res = fb_mmap_fn(info, vma);
+ res = info->fbops->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
+#if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
+ } else if (info->fbdefio) {
+ /*
+ * FB deferred I/O wants you to handle mmap in your drivers. At a
+ * minimum, point struct fb_ops.fb_mmap to fb_deferred_io_mmap().
+ */
+ dev_warn_once(info->dev, "fbdev mmap not set up for deferred I/O.\n");
+ mutex_unlock(&info->mm_lock);
+ return -ENODEV;
+#endif
}
/*
diff --git a/drivers/video/fbdev/hecubafb.c b/drivers/video/fbdev/hecubafb.c
index 00d77105161a..eb1eaadc1bbb 100644
--- a/drivers/video/fbdev/hecubafb.c
+++ b/drivers/video/fbdev/hecubafb.c
@@ -115,8 +115,7 @@ static void hecubafb_dpy_update(struct hecubafb_par *par)
}
/* this is called back from the deferred io workqueue */
-static void hecubafb_dpy_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void hecubafb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
hecubafb_dpy_update(info->par);
}
@@ -204,6 +203,7 @@ static const struct fb_ops hecubafb_ops = {
.fb_fillrect = hecubafb_fillrect,
.fb_copyarea = hecubafb_copyarea,
.fb_imageblit = hecubafb_imageblit,
+ .fb_mmap = fb_deferred_io_mmap,
};
static struct fb_deferred_io hecubafb_defio = {
diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c
index c8e0ea27caf1..8359a513b600 100644
--- a/drivers/video/fbdev/hyperv_fb.c
+++ b/drivers/video/fbdev/hyperv_fb.c
@@ -420,11 +420,10 @@ static void hvfb_docopy(struct hvfb_par *par,
}
/* Deferred IO callback */
-static void synthvid_deferred_io(struct fb_info *p,
- struct list_head *pagelist)
+static void synthvid_deferred_io(struct fb_info *p, struct list_head *pagereflist)
{
struct hvfb_par *par = p->par;
- struct page *page;
+ struct fb_deferred_io_pageref *pageref;
unsigned long start, end;
int y1, y2, miny, maxy;
@@ -437,8 +436,8 @@ static void synthvid_deferred_io(struct fb_info *p,
* in synthvid_update function by clamping the y2
* value to yres.
*/
- list_for_each_entry(page, pagelist, lru) {
- start = page->index << PAGE_SHIFT;
+ list_for_each_entry(pageref, pagereflist, list) {
+ start = pageref->offset;
end = start + PAGE_SIZE - 1;
y1 = start / p->fix.line_length;
y2 = end / p->fix.line_length;
@@ -909,6 +908,7 @@ static const struct fb_ops hvfb_ops = {
.fb_copyarea = hvfb_cfb_copyarea,
.fb_imageblit = hvfb_cfb_imageblit,
.fb_blank = hvfb_blank,
+ .fb_mmap = fb_deferred_io_mmap,
};
diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c
index af858dd23ea6..9fd4bb85d735 100644
--- a/drivers/video/fbdev/metronomefb.c
+++ b/drivers/video/fbdev/metronomefb.c
@@ -465,20 +465,18 @@ static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index)
}
/* this is called back from the deferred io workqueue */
-static void metronomefb_dpy_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
u16 cksum;
- struct page *cur;
- struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_pageref *pageref;
struct metronomefb_par *par = info->par;
/* walk the written page list and swizzle the data */
- list_for_each_entry(cur, &fbdefio->pagelist, lru) {
- cksum = metronomefb_dpy_update_page(par,
- (cur->index << PAGE_SHIFT));
- par->metromem_img_csum -= par->csum_table[cur->index];
- par->csum_table[cur->index] = cksum;
+ list_for_each_entry(pageref, pagereflist, list) {
+ unsigned long pgoffset = pageref->offset >> PAGE_SHIFT;
+ cksum = metronomefb_dpy_update_page(par, pageref->offset);
+ par->metromem_img_csum -= par->csum_table[pgoffset];
+ par->csum_table[pgoffset] = cksum;
par->metromem_img_csum += cksum;
}
@@ -564,12 +562,13 @@ static const struct fb_ops metronomefb_ops = {
.fb_fillrect = metronomefb_fillrect,
.fb_copyarea = metronomefb_copyarea,
.fb_imageblit = metronomefb_imageblit,
+ .fb_mmap = fb_deferred_io_mmap,
};
static struct fb_deferred_io metronomefb_defio = {
- .delay = HZ,
- .sort_pagelist = true,
- .deferred_io = metronomefb_dpy_deferred_io,
+ .delay = HZ,
+ .sort_pagereflist = true,
+ .deferred_io = metronomefb_dpy_deferred_io,
};
static int metronomefb_probe(struct platform_device *dev)
diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.c b/drivers/video/fbdev/sh_mobile_lcdcfb.c
index 9a4417430b4e..6d00893d41f4 100644
--- a/drivers/video/fbdev/sh_mobile_lcdcfb.c
+++ b/drivers/video/fbdev/sh_mobile_lcdcfb.c
@@ -435,24 +435,23 @@ static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
.read_data = lcdc_sys_read_data,
};
-static int sh_mobile_lcdc_sginit(struct fb_info *info,
- struct list_head *pagelist)
+static int sh_mobile_lcdc_sginit(struct fb_info *info, struct list_head *pagereflist)
{
struct sh_mobile_lcdc_chan *ch = info->par;
unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT;
- struct page *page;
+ struct fb_deferred_io_pageref *pageref;
int nr_pages = 0;
sg_init_table(ch->sglist, nr_pages_max);
- list_for_each_entry(page, pagelist, lru)
- sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0);
+ list_for_each_entry(pageref, pagereflist, list) {
+ sg_set_page(&ch->sglist[nr_pages++], pageref->page, PAGE_SIZE, 0);
+ }
return nr_pages;
}
-static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void sh_mobile_lcdc_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
struct sh_mobile_lcdc_chan *ch = info->par;
const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
@@ -461,7 +460,7 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
sh_mobile_lcdc_clk_on(ch->lcdc);
/*
- * It's possible to get here without anything on the pagelist via
+ * It's possible to get here without anything on the pagereflist via
* sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync()
* invocation. In the former case, the acceleration routines are
* stepped in to when using the framebuffer console causing the
@@ -471,12 +470,12 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
* acceleration routines have their own methods for writing in
* that still need to be updated.
*
- * The fsync() and empty pagelist case could be optimized for,
+ * The fsync() and empty pagereflist case could be optimized for,
* but we don't bother, as any application exhibiting such
* behaviour is fundamentally broken anyways.
*/
- if (!list_empty(pagelist)) {
- unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist);
+ if (!list_empty(pagereflist)) {
+ unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagereflist);
/* trigger panel update */
dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE);
@@ -1480,6 +1479,9 @@ sh_mobile_lcdc_overlay_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct sh_mobile_lcdc_overlay *ovl = info->par;
+ if (info->fbdefio)
+ return fb_deferred_io_mmap(info, vma);
+
return dma_mmap_coherent(ovl->channel->lcdc->dev, vma, ovl->fb_mem,
ovl->dma_handle, ovl->fb_size);
}
@@ -1954,6 +1956,9 @@ sh_mobile_lcdc_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct sh_mobile_lcdc_chan *ch = info->par;
+ if (info->fbdefio)
+ return fb_deferred_io_mmap(info, vma);
+
return dma_mmap_coherent(ch->lcdc->dev, vma, ch->fb_mem,
ch->dma_handle, ch->fb_size);
}
diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c
index 28768c272b73..d7aa5511c361 100644
--- a/drivers/video/fbdev/smscufx.c
+++ b/drivers/video/fbdev/smscufx.c
@@ -779,6 +779,9 @@ static int ufx_ops_mmap(struct fb_info *info, struct vm_area_struct *vma)
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long page, pos;
+ if (info->fbdefio)
+ return fb_deferred_io_mmap(info, vma);
+
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
if (size > info->fix.smem_len)
@@ -952,12 +955,10 @@ static void ufx_ops_fillrect(struct fb_info *info,
* Touching ANY framebuffer memory that triggers a page fault
* in fb_defio will cause a deadlock, when it also tries to
* grab the same mutex. */
-static void ufx_dpy_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void ufx_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
- struct page *cur;
- struct fb_deferred_io *fbdefio = info->fbdefio;
struct ufx_data *dev = info->par;
+ struct fb_deferred_io_pageref *pageref;
if (!fb_defio)
return;
@@ -966,12 +967,12 @@ static void ufx_dpy_deferred_io(struct fb_info *info,
return;
/* walk the written page list and render each to device */
- list_for_each_entry(cur, &fbdefio->pagelist, lru) {
+ list_for_each_entry(pageref, pagereflist, list) {
/* create a rectangle of full screen width that encloses the
* entire dirty framebuffer page */
const int x = 0;
const int width = dev->info->var.xres;
- const int y = (cur->index << PAGE_SHIFT) / (width * 2);
+ const int y = pageref->offset / (width * 2);
int height = (PAGE_SIZE / (width * 2)) + 1;
height = min(height, (int)(dev->info->var.yres - y));
diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c
index c6d5df31111d..5c765655d000 100644
--- a/drivers/video/fbdev/ssd1307fb.c
+++ b/drivers/video/fbdev/ssd1307fb.c
@@ -368,10 +368,10 @@ static const struct fb_ops ssd1307fb_ops = {
.fb_fillrect = ssd1307fb_fillrect,
.fb_copyarea = ssd1307fb_copyarea,
.fb_imageblit = ssd1307fb_imageblit,
+ .fb_mmap = fb_deferred_io_mmap,
};
-static void ssd1307fb_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void ssd1307fb_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
ssd1307fb_update_display(info->par);
}
diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c
index d280733f283b..c863244ef12c 100644
--- a/drivers/video/fbdev/udlfb.c
+++ b/drivers/video/fbdev/udlfb.c
@@ -326,6 +326,9 @@ static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma)
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long page, pos;
+ if (info->fbdefio)
+ return fb_deferred_io_mmap(info, vma);
+
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
if (size > info->fix.smem_len)
@@ -778,11 +781,9 @@ static void dlfb_ops_fillrect(struct fb_info *info,
* in fb_defio will cause a deadlock, when it also tries to
* grab the same mutex.
*/
-static void dlfb_dpy_deferred_io(struct fb_info *info,
- struct list_head *pagelist)
+static void dlfb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist)
{
- struct page *cur;
- struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_pageref *pageref;
struct dlfb_data *dlfb = info->par;
struct urb *urb;
char *cmd;
@@ -808,11 +809,10 @@ static void dlfb_dpy_deferred_io(struct fb_info *info,
cmd = urb->transfer_buffer;
/* walk the written page list and render each to device */
- list_for_each_entry(cur, &fbdefio->pagelist, lru) {
-
+ list_for_each_entry(pageref, pagereflist, list) {
if (dlfb_render_hline(dlfb, &urb, (char *) info->fix.smem_start,
- &cmd, cur->index << PAGE_SHIFT,
- PAGE_SIZE, &bytes_identical, &bytes_sent))
+ &cmd, pageref->offset, PAGE_SIZE,
+ &bytes_identical, &bytes_sent))
goto error;
bytes_rendered += PAGE_SIZE;
}
@@ -980,7 +980,7 @@ static int dlfb_ops_open(struct fb_info *info, int user)
if (fbdefio) {
fbdefio->delay = DL_DEFIO_WRITE_DELAY;
- fbdefio->sort_pagelist = true;
+ fbdefio->sort_pagereflist = true;
fbdefio->deferred_io = dlfb_dpy_deferred_io;
}
diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c
index 6826f986da43..3bed357a9870 100644
--- a/drivers/video/fbdev/xen-fbfront.c
+++ b/drivers/video/fbdev/xen-fbfront.c
@@ -181,18 +181,17 @@ static void xenfb_refresh(struct xenfb_info *info,
xenfb_do_update(info, x1, y1, x2 - x1 + 1, y2 - y1 + 1);
}
-static void xenfb_deferred_io(struct fb_info *fb_info,
- struct list_head *pagelist)
+static void xenfb_deferred_io(struct fb_info *fb_info, struct list_head *pagereflist)
{
struct xenfb_info *info = fb_info->par;
- struct page *page;
+ struct fb_deferred_io_pageref *pageref;
unsigned long beg, end;
int y1, y2, miny, maxy;
miny = INT_MAX;
maxy = 0;
- list_for_each_entry(page, pagelist, lru) {
- beg = page->index << PAGE_SHIFT;
+ list_for_each_entry(pageref, pagereflist, list) {
+ beg = pageref->offset;
end = beg + PAGE_SIZE - 1;
y1 = beg / fb_info->fix.line_length;
y2 = end / fb_info->fix.line_length;
@@ -338,6 +337,7 @@ static const struct fb_ops xenfb_fb_ops = {
.fb_imageblit = xenfb_imageblit,
.fb_check_var = xenfb_check_var,
.fb_set_par = xenfb_set_par,
+ .fb_mmap = fb_deferred_io_mmap,
};
static irqreturn_t xenfb_event_handler(int rq, void *dev_id)
diff --git a/include/drm/drm_atomic_uapi.h b/include/drm/drm_atomic_uapi.h
index 8cec52ad1277..4c6d39d7bdb2 100644
--- a/include/drm/drm_atomic_uapi.h
+++ b/include/drm/drm_atomic_uapi.h
@@ -49,8 +49,6 @@ drm_atomic_set_crtc_for_plane(struct drm_plane_state *plane_state,
struct drm_crtc *crtc);
void drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state,
struct drm_framebuffer *fb);
-void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
- struct dma_fence *fence);
int __must_check
drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
struct drm_crtc *crtc);
diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
index 3af4624368d8..329607ca65c0 100644
--- a/include/drm/drm_fb_helper.h
+++ b/include/drm/drm_fb_helper.h
@@ -229,8 +229,7 @@ void drm_fb_helper_fill_info(struct fb_info *info,
struct drm_fb_helper *fb_helper,
struct drm_fb_helper_surface_size *sizes);
-void drm_fb_helper_deferred_io(struct fb_info *info,
- struct list_head *pagelist);
+void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagereflist);
ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
diff --git a/include/drm/drm_managed.h b/include/drm/drm_managed.h
index b45c6fbf53ac..359883942612 100644
--- a/include/drm/drm_managed.h
+++ b/include/drm/drm_managed.h
@@ -8,6 +8,7 @@
#include <linux/types.h>
struct drm_device;
+struct mutex;
typedef void (*drmres_release_t)(struct drm_device *dev, void *res);
@@ -104,4 +105,6 @@ char *drmm_kstrdup(struct drm_device *dev, const char *s, gfp_t gfp);
void drmm_kfree(struct drm_device *dev, void *data);
+int drmm_mutex_init(struct drm_device *dev, struct mutex *lock);
+
#endif
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 2628c7cde2da..89ea54652e87 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -74,9 +74,7 @@ struct drm_plane_state {
*
* Optional fence to wait for before scanning out @fb. The core atomic
* code will set this when userspace is using explicit fencing. Do not
- * write this field directly for a driver's implicit fence, use
- * drm_atomic_set_fence_for_plane() to ensure that an explicit fence is
- * preserved.
+ * write this field directly for a driver's implicit fence.
*
* Drivers should store any implicit fence in this from their
* &drm_plane_helper_funcs.prepare_fb callback. See drm_gem_plane_helper_prepare_fb()
diff --git a/include/dt-bindings/soc/rockchip,vop2.h b/include/dt-bindings/soc/rockchip,vop2.h
new file mode 100644
index 000000000000..6e66a802b96a
--- /dev/null
+++ b/include/dt-bindings/soc/rockchip,vop2.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
+
+#ifndef __DT_BINDINGS_ROCKCHIP_VOP2_H
+#define __DT_BINDINGS_ROCKCHIP_VOP2_H
+
+#define ROCKCHIP_VOP2_EP_RGB0 1
+#define ROCKCHIP_VOP2_EP_HDMI0 2
+#define ROCKCHIP_VOP2_EP_EDP0 3
+#define ROCKCHIP_VOP2_EP_MIPI0 4
+#define ROCKCHIP_VOP2_EP_LVDS0 5
+#define ROCKCHIP_VOP2_EP_MIPI1 6
+#define ROCKCHIP_VOP2_EP_LVDS1 7
+
+#endif /* __DT_BINDINGS_ROCKCHIP_VOP2_H */
diff --git a/include/linux/fb.h b/include/linux/fb.h
index f95da1af9ff6..69c67c70fa78 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -201,12 +201,19 @@ struct fb_pixmap {
};
#ifdef CONFIG_FB_DEFERRED_IO
+struct fb_deferred_io_pageref {
+ struct page *page;
+ unsigned long offset;
+ /* private */
+ struct list_head list;
+};
+
struct fb_deferred_io {
/* delay between mkwrite and deferred handler */
unsigned long delay;
- bool sort_pagelist; /* sort pagelist by offset */
- struct mutex lock; /* mutex that protects the page list */
- struct list_head pagelist; /* list of touched pages */
+ bool sort_pagereflist; /* sort pagelist by offset */
+ struct mutex lock; /* mutex that protects the pageref list */
+ struct list_head pagereflist; /* list of pagerefs for touched pages */
/* callback */
void (*first_io)(struct fb_info *info);
void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
@@ -468,6 +475,8 @@ struct fb_info {
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
+ unsigned long npagerefs;
+ struct fb_deferred_io_pageref *pagerefs;
struct fb_deferred_io *fbdefio;
#endif
@@ -661,7 +670,7 @@ static inline void __fb_pad_aligned_buffer(u8 *dst, u32 d_pitch,
/* drivers/video/fb_defio.c */
int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma);
-extern void fb_deferred_io_init(struct fb_info *info);
+extern int fb_deferred_io_init(struct fb_info *info);
extern void fb_deferred_io_open(struct fb_info *info,
struct inode *inode,
struct file *file);