diff options
author | Dave Airlie <[email protected]> | 2022-05-06 17:19:11 +1000 |
---|---|---|
committer | Dave Airlie <[email protected]> | 2022-05-06 17:20:13 +1000 |
commit | c67f84e97bafe73c47d5773105b114118ffb84df (patch) | |
tree | d8e01ee364dda98fe99426ab62b4dd9fbd89c980 | |
parent | af3847a7472d2def8358b7ae94b14f1d20fd8661 (diff) | |
parent | 6071c4c2a319da360b0bf2bc397d4fefad10b2c8 (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
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 = &ire_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); |