diff --git a/Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml b/Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml new file mode 100644 index 000000000000..f2543aebc312 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/innosilicon,inno-hdmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Common Properties for Innosilicon HDMI TX IP + +maintainers: + - keith zhao + +description: | + Innosilicon HDMI TX is an HDMI transmission device integrated into the zap SoC. + This document specifies the device tree properties for the INNO HDMI IP core. + It is intended to be referenced by platform-specific device tree bindings, + which will determine the necessity of each property. + +properties: + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Port node with one endpoint connected to a display controller node. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Port node with one endpoint connected to a hdmi-connector node. + + required: + - port@0 + - port@1 + +additionalProperties: true + +... diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,inno-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,inno-hdmi.yaml index 5b87b0f1963e..589dedfcbee0 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,inno-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,inno-hdmi.yaml @@ -16,12 +16,6 @@ properties: - rockchip,rk3036-inno-hdmi - rockchip,rk3128-inno-hdmi - reg: - maxItems: 1 - - interrupts: - maxItems: 1 - clocks: minItems: 1 items: @@ -40,24 +34,6 @@ properties: "#sound-dai-cells": const: 0 - ports: - $ref: /schemas/graph.yaml#/properties/ports - - properties: - port@0: - $ref: /schemas/graph.yaml#/properties/port - description: - Port node with one endpoint connected to a vop node. - - port@1: - $ref: /schemas/graph.yaml#/properties/port - description: - Port node with one endpoint connected to a hdmi-connector node. - - required: - - port@0 - - port@1 - required: - compatible - reg @@ -69,6 +45,7 @@ required: - ports allOf: + - $ref: ../bridge/innosilicon,inno-hdmi.yaml# - $ref: /schemas/sound/dai-common.yaml# - if: properties: @@ -95,7 +72,7 @@ allOf: required: - power-domains -additionalProperties: false +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/display/starfive/starfive,jh7110-dc8200.yaml b/Documentation/devicetree/bindings/display/starfive/starfive,jh7110-dc8200.yaml new file mode 100644 index 000000000000..5f57c405e857 --- /dev/null +++ b/Documentation/devicetree/bindings/display/starfive/starfive,jh7110-dc8200.yaml @@ -0,0 +1,176 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/starfive/starfive,jh7110-dc8200.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Starfive JH7110 SoC Display Controller (DC8200) + +description: + The STARFIVE JH7110 SoC uses the display controller based on Verisilicon + IP(DC8200) to transfer the image data from a video memory buffer to an + external LCD interface. + + pipe0 binds HDMI for primary display, + pipe1 binds DSI for external display. + + +------------------------------+ + | | + | | + +----+ | +-------------------+ | +-------+ +------+ +------+ + | +----->+ dc controller 0 +--->----->+HDMICtl| ->+ PHY +-->+PANEL0+ + |AXI | | +-------------------+ | +-------+ +------+ +------+ + | | | | + | | | | + | | | | + | | | | + |APB | | +-------------------+ +---------+ +------+ +-------+ + | +----->+ dc controller 1 +--->---->+ dsiTx +--->+DPHY +->+ PANEL1+ + | | | +-------------------+ +---------+ +------+ +-------+ + +----+ | | + +------------------------------+ + +maintainers: + - keith zhao + +properties: + compatible: + const: starfive,jh7110-dc8200 + + reg: + items: + - description: host interface + - description: display physical base + + reg-names: + items: + - const: host + - const: base + + clocks: + items: + - description: Clock for display system noc bus. + - description: Core clock for display controller. + - description: Clock for axi bus to access ddr. + - description: Clock for ahb bus to R/W the phy regs. + - description: Pixel clock for display channel 0. + - description: Pixel clock for display channel 1. + - description: Pixel clock from hdmi. + - description: Pixel clock for soc . + + clock-names: + items: + - const: noc_bus + - const: dc_core + - const: axi_core + - const: ahb + - const: channel0 + - const: channel1 + - const: hdmi_tx + - const: dc_parent + + resets: + items: + - description: Reset for axi bus. + - description: Reset for ahb bus. + - description: Core reset of display controller. + + reset-names: + items: + - const: axi + - const: ahb + - const: core + + interrupts: + items: + - description: The interrupt will be generated when DC finish one frame + + starfive,syscon: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the general register files syscon + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + channel 0 output + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + channel 1 output + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + dc8200: lcd-controller@29400000 { + compatible = "starfive,jh7110-dc8200"; + reg = <0x29400000 0x100>, + <0x29400800 0x2000>; + reg-names = "host", "base"; + + interrupts = <95>; + clocks = <&syscrg JH7110_SYSCLK_NOC_BUS_DISP_AXI>, + <&voutcrg JH7110_VOUTCLK_DC8200_CORE>, + <&voutcrg JH7110_VOUTCLK_DC8200_AXI>, + <&voutcrg JH7110_VOUTCLK_DC8200_AHB>, + <&voutcrg JH7110_VOUTCLK_DC8200_PIX0>, + <&voutcrg JH7110_VOUTCLK_DC8200_PIX1>, + <&hdmitx0_pixelclk>, + <&voutcrg JH7110_VOUTCLK_DC8200_PIX>; + clock-names = "noc_bus", "dc_core", "axi_core", "ahb", + "channel0", "channel1", "hdmi_tx", "dc_parent"; + resets = <&voutcrg JH7110_VOUTRST_DC8200_AXI>, + <&voutcrg JH7110_VOUTRST_DC8200_AHB>, + <&voutcrg JH7110_VOUTRST_DC8200_CORE>; + reset-names = "axi", "ahb", "core"; + + starfive,syscon = <&vout_syscon>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + dc_out0: port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + dc_out_dpi0: endpoint@0 { + reg = <0>; + remote-endpoint = <&hdmi_enc>; + }; + + }; + + dc_out1: port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + dc_out_dpi1: endpoint@1 { + reg = <1>; + remote-endpoint = <&dsi_enc>; + }; + + }; + }; + }; diff --git a/Documentation/devicetree/bindings/display/starfive/starfive,jh7110-inno-hdmi.yaml b/Documentation/devicetree/bindings/display/starfive/starfive,jh7110-inno-hdmi.yaml new file mode 100644 index 000000000000..62cb62885d71 --- /dev/null +++ b/Documentation/devicetree/bindings/display/starfive/starfive,jh7110-inno-hdmi.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/starfive/starfive,jh7110-inno-hdmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Starfive JH7110 Innosilicon HDMI controller + +maintainers: + - keith zhao + +description: | + Innosilicon HDMI TX IP is designed for transmitting video and audio data + from a video source device to a display device. + It contains a digital controller and a physical layer. + +allOf: + - $ref: ../bridge/innosilicon,inno-hdmi.yaml# + +properties: + compatible: + const: "starfive,jh7110-inno-hdmi" + + clocks: + items: + - description: System clock of HDMI module. + - description: Mclk clock of HDMI audio. + - description: Bclk clock of HDMI audio. + + clock-names: + items: + - const: sysclk + - const: mclk + - const: bclk + + resets: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - ports + +unevaluatedProperties: false + +examples: + - | + hdmi: hdmi@29590000 { + compatible = "starfive,jh7110-inno-hdmi"; + reg = <0x29590000 0x4000>; + interrupts = <99>; + clocks = <&voutcrg 17>, <&voutcrg 15>, <&voutcrg 16>; + clock-names = "sysclk", "mclk","bclk"; + resets = <&voutcrg 9>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + hdmi_in: port@0 { + reg = <0>; + hdmi_in_vop: endpoint { + remote-endpoint = <&dc_out_dpi0>; + }; + }; + + hdmi_out: port@1 { + reg = <1>; + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; + }; + }; + }; + + hdmi-out { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/soc/starfive/starfive,jh7110-syscon.yaml b/Documentation/devicetree/bindings/soc/starfive/starfive,jh7110-syscon.yaml index 0039319e91fe..cf9b657d0e8a 100644 --- a/Documentation/devicetree/bindings/soc/starfive/starfive,jh7110-syscon.yaml +++ b/Documentation/devicetree/bindings/soc/starfive/starfive,jh7110-syscon.yaml @@ -24,6 +24,7 @@ properties: - enum: - starfive,jh7110-aon-syscon - starfive,jh7110-stg-syscon + - starfive,jh7110-vout-syscon - const: syscon reg: diff --git a/MAINTAINERS b/MAINTAINERS index 84086d47db69..f787dd625497 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7432,6 +7432,14 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/ste,mcde.yaml F: drivers/gpu/drm/mcde/ +DRM DRIVERS FOR STARFIVE +M: Keith Zhao +L: dri-devel@lists.freedesktop.org +S: Maintained +T: git https://gitlab.freedesktop.org/drm/misc/kernel.git +F: Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml +F: Documentation/devicetree/bindings/display/starfive/ + DRM DRIVER FOR SYNAPTICS R63353 PANELS M: Michael Trimarchi S: Maintained diff --git a/arch/riscv/boot/dts/starfive/jh7110-common.dtsi b/arch/riscv/boot/dts/starfive/jh7110-common.dtsi index 9d77713f5361..301b56f2ef0c 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-common.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110-common.dtsi @@ -30,6 +30,25 @@ memory@40000000 { reg = <0x0 0x40000000 0x1 0x0>; }; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + /* vout applies for space from this CMA + * Without this CMA reservation, + * vout may not work properly. + */ + linux,cma { + compatible = "shared-dma-pool"; + reusable; + size = <0x0 0x20000000>; + alignment = <0x0 0x1000>; + alloc-ranges = <0x0 0x70000000 0x0 0x20000000>; + linux,cma-default; + }; + }; + gpio-restart { compatible = "gpio-restart"; gpios = <&sysgpio 35 GPIO_ACTIVE_HIGH>; @@ -62,12 +81,55 @@ codec { }; }; }; + + hdmi_con: hdmi-con { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi_out_con>; + }; + }; + }; }; &cpus { timebase-frequency = <4000000>; }; +&dc8200 { + status = "okay"; + crtc_out: ports { + #address-cells = <1>; + #size-cells = <0>; + + dc_out0: port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + + dc_out_dpi0: endpoint@0 { + reg = <0>; + remote-endpoint = <&hdmi_enc>; + }; + + }; + + dc_out1: port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + dc_out_dpi1: endpoint@1 { + reg = <1>; + //remote-endpoint = <&dsi_enc>; + }; + + }; + }; +}; + &dvp_clk { clock-frequency = <74250000>; }; @@ -88,6 +150,31 @@ &gmac1_rmii_refin { clock-frequency = <50000000>; }; +&hdmi { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_pins>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + hdmi_in: port@0 { + reg = <0>; + hdmi_enc: endpoint { + remote-endpoint = <&dc_out_dpi0>; + }; + }; + + hdmi_out: port@1 { + reg = <1>; + hdmi_out_con: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; + }; + }; +}; + &hdmitx0_pixelclk { clock-frequency = <297000000>; }; @@ -366,6 +453,40 @@ &syscrg { }; &sysgpio { + hdmi_pins: hdmi-0 { + hdmi-cec-pins { + pinmux = ; + input-enable; + bias-pull-up; + }; + + hdmi-hpd-pins { + pinmux = ; + input-enable; + bias-disable; /* external pull-up */ + }; + + hdmi-scl-pins { + pinmux = ; + input-enable; + bias-pull-up; + }; + + hdmi-sda-pins { + pinmux = ; + input-enable; + bias-pull-up; + }; + }; + i2c0_pins: i2c0-0 { i2c-pins { pinmux = ; }; + +&voutcrg { + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/starfive/jh7110.dtsi b/arch/riscv/boot/dts/starfive/jh7110.dtsi index 0d8339357bad..d48825822017 100644 --- a/arch/riscv/boot/dts/starfive/jh7110.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110.dtsi @@ -1203,6 +1203,47 @@ camss: isp@19840000 { status = "disabled"; }; + dc8200: lcd-controller@29400000 { + compatible = "starfive,jh7110-dc8200"; + reg = <0x0 0x29400000 0x0 0x100>, + <0x0 0x29400800 0x0 0x2000>; + + interrupts = <95>; + clocks = <&syscrg JH7110_SYSCLK_NOC_BUS_DISP_AXI>, + <&voutcrg JH7110_VOUTCLK_DC8200_CORE>, + <&voutcrg JH7110_VOUTCLK_DC8200_AXI>, + <&voutcrg JH7110_VOUTCLK_DC8200_AHB>, + <&voutcrg JH7110_VOUTCLK_DC8200_PIX0>, + <&voutcrg JH7110_VOUTCLK_DC8200_PIX1>, + <&hdmitx0_pixelclk>, + <&voutcrg JH7110_VOUTCLK_DC8200_PIX>; + clock-names = "noc_bus", "dc_core", "axi_core", "ahb", + "channel0", "channel1", "hdmi_tx", "dc_parent"; + resets = <&voutcrg JH7110_VOUTRST_DC8200_AXI>, + <&voutcrg JH7110_VOUTRST_DC8200_AHB>, + <&voutcrg JH7110_VOUTRST_DC8200_CORE>; + reset-names = "axi","ahb", "core"; + + starfive,syscon = <&vout_syscon>; + }; + + hdmi: hdmi@29590000 { + compatible = "starfive,jh7110-inno-hdmi"; + reg = <0x0 0x29590000 0x0 0x4000>; + interrupts = <99>; + + clocks = <&voutcrg JH7110_VOUTCLK_HDMI_TX_SYS>, + <&voutcrg JH7110_VOUTCLK_HDMI_TX_MCLK>, + <&voutcrg JH7110_VOUTCLK_HDMI_TX_BCLK>; + clock-names = "sysclk", "mclk", "bclk"; + resets = <&voutcrg JH7110_VOUTRST_HDMI_TX_HDMI>; + }; + + vout_syscon: syscon@295b0000 { + compatible = "starfive,jh7110-vout-syscon", "syscon"; + reg = <0 0x295b0000 0 0x90>; + }; + voutcrg: clock-controller@295c0000 { compatible = "starfive,jh7110-voutcrg"; reg = <0x0 0x295c0000 0x0 0x10000>; diff --git a/MAINTAINERS b/MAINTAINERS index f787dd625497..7766ee0bdd74 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7439,6 +7439,8 @@ S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml F: Documentation/devicetree/bindings/display/starfive/ +F: drivers/gpu/drm/bridge/innosilicon/ +F: include/drm/bridge/inno_hdmi.h DRM DRIVER FOR SYNAPTICS R63353 PANELS M: Michael Trimarchi diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 683cb33805b2..37be5dd5213f 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -423,6 +423,8 @@ source "drivers/gpu/drm/bridge/cadence/Kconfig" source "drivers/gpu/drm/bridge/imx/Kconfig" +source "drivers/gpu/drm/bridge/innosilicon/Kconfig" + source "drivers/gpu/drm/bridge/synopsys/Kconfig" endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 3daf803ce80b..866d1e0fd8aa 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -41,4 +41,5 @@ obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o obj-y += analogix/ obj-y += cadence/ obj-y += imx/ +obj-y += innosilicon/ obj-y += synopsys/ diff --git a/drivers/gpu/drm/bridge/innosilicon/Kconfig b/drivers/gpu/drm/bridge/innosilicon/Kconfig new file mode 100644 index 000000000000..73dbed3b1c4d --- /dev/null +++ b/drivers/gpu/drm/bridge/innosilicon/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_INNO_HDMI + tristate + help + Support the common interface which is part of the INNO + Designware HDMI block. diff --git a/drivers/gpu/drm/bridge/innosilicon/Makefile b/drivers/gpu/drm/bridge/innosilicon/Makefile new file mode 100644 index 000000000000..3b3a961ab9fb --- /dev/null +++ b/drivers/gpu/drm/bridge/innosilicon/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o diff --git a/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c new file mode 100644 index 000000000000..c26132b41bdf --- /dev/null +++ b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Zheng Yang + * Yakir Yang + * Copyright (C) StarFive Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inno-hdmi.h" + +u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) +{ + return readl_relaxed(hdmi->regs + (offset) * 0x04); +} +EXPORT_SYMBOL_GPL(hdmi_readb); + +void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) +{ + writel_relaxed(val, hdmi->regs + (offset) * 0x04); +} +EXPORT_SYMBOL_GPL(hdmi_writeb); + +void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, u32 msk, u32 val) +{ + u8 temp = hdmi_readb(hdmi, offset) & ~msk; + + temp |= val & msk; + hdmi_writeb(hdmi, offset, temp); +} +EXPORT_SYMBOL_GPL(hdmi_modb); + +void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate) +{ + unsigned long long ddc_bus_freq = rate >> 2; + + do_div(ddc_bus_freq, HDMI_SCL_RATE); + + hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF); + hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF); + + /* Clear the EDID interrupt flag and mute the interrupt */ + hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); + hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); +} +EXPORT_SYMBOL_GPL(inno_hdmi_i2c_init); + +static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi) +{ + struct inno_hdmi_i2c *i2c = hdmi->i2c; + u8 stat; + + stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1); + if (!(stat & m_INT_EDID_READY)) + return IRQ_NONE; + + /* Clear HDMI EDID interrupt flag */ + hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); + + complete(&i2c->cmp); + + return IRQ_HANDLED; +} + +static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id) +{ + struct inno_hdmi *hdmi = dev_id; + irqreturn_t ret = IRQ_NONE; + u8 interrupt; + + if (hdmi->i2c) + ret = inno_hdmi_i2c_irq(hdmi); + + interrupt = hdmi_readb(hdmi, HDMI_STATUS); + if (interrupt & m_INT_HOTPLUG) { + hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t inno_hdmi_irq(int irq, void *dev_id) +{ + struct inno_hdmi *hdmi = dev_id; + + drm_helper_hpd_irq_event(hdmi->bridge.dev); + + return IRQ_HANDLED; +} + +static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs) +{ + int length = msgs->len; + u8 *buf = msgs->buf; + int ret; + + ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10); + if (!ret) + return -EAGAIN; + + while (length--) + *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR); + + return 0; +} + +static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs) +{ + /* + * The DDC module only support read EDID message, so + * we assume that each word write to this i2c adapter + * should be the offset of EDID word address. + */ + if (msgs->len != 1 || (msgs->addr != DDC_ADDR && msgs->addr != DDC_SEGMENT_ADDR)) + return -EINVAL; + + reinit_completion(&hdmi->i2c->cmp); + + if (msgs->addr == DDC_SEGMENT_ADDR) + hdmi->i2c->segment_addr = msgs->buf[0]; + if (msgs->addr == DDC_ADDR) + hdmi->i2c->ddc_addr = msgs->buf[0]; + + /* Set edid fifo first addr */ + hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00); + + /* Set edid word address 0x00/0x80 */ + hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr); + + /* Set edid segment pointer */ + hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr); + + return 0; +} + +static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct inno_hdmi *hdmi = i2c_get_adapdata(adap); + struct inno_hdmi_i2c *i2c = hdmi->i2c; + int i, ret = 0; + + guard(mutex)(&i2c->lock); + /* Clear the EDID interrupt flag and unmute the interrupt */ + hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY); + hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); + + for (i = 0; i < num; i++) { + DRM_DEV_DEBUG(hdmi->dev, + "xfer: num: %d/%d, len: %d, flags: %#x\n", + i + 1, num, msgs[i].len, msgs[i].flags); + + if (msgs[i].flags & I2C_M_RD) + ret = inno_hdmi_i2c_read(hdmi, &msgs[i]); + else + ret = inno_hdmi_i2c_write(hdmi, &msgs[i]); + + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + /* Mute HDMI EDID interrupt */ + hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); + + return ret; +} + +static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm inno_hdmi_algorithm = { + .master_xfer = inno_hdmi_i2c_xfer, + .functionality = inno_hdmi_i2c_func, +}; + +static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi) +{ + struct i2c_adapter *adap; + struct inno_hdmi_i2c *i2c; + int ret; + + i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return ERR_PTR(-ENOMEM); + + mutex_init(&i2c->lock); + init_completion(&i2c->cmp); + + adap = &i2c->adap; + adap->owner = THIS_MODULE; + adap->dev.parent = hdmi->dev; + adap->dev.of_node = hdmi->dev->of_node; + adap->algo = &inno_hdmi_algorithm; + strscpy(adap->name, "Inno HDMI", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = devm_i2c_add_adapter(hdmi->dev, adap); + if (ret) { + dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); + return ERR_PTR(ret); + } + + hdmi->i2c = i2c; + + DRM_DEV_DEBUG(hdmi->dev, "registered %s I2C bus driver\n", adap->name); + + return adap; +} + +static int inno_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct inno_hdmi *hdmi = bridge->driver_private; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return drm_bridge_attach(bridge->encoder, hdmi->next_bridge, + bridge, flags); + + return 0; +} + +static enum drm_connector_status +inno_hdmi_bridge_detect(struct drm_bridge *bridge) +{ + struct inno_hdmi *hdmi = bridge->driver_private; + + return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? + connector_status_connected : connector_status_disconnected; +} + +static const struct drm_edid *inno_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct inno_hdmi *hdmi = bridge->driver_private; + + return drm_edid_read_ddc(connector, hdmi->bridge.ddc); +} + +static enum drm_mode_status +inno_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct inno_hdmi *hdmi = bridge->driver_private; + + enum drm_mode_status mode_status = MODE_OK; + + if (hdmi->plat_data->mode_valid) + mode_status = hdmi->plat_data->mode_valid(hdmi, mode); + + return mode_status; +} + +static int inno_hdmi_clear_infoframe(struct drm_bridge *bridge, + enum hdmi_infoframe_type type) +{ + struct inno_hdmi *hdmi = bridge->driver_private; + int ret = 0; + + if (hdmi->plat_data->hdmi_clear_infoframe) + ret = hdmi->plat_data->hdmi_clear_infoframe(hdmi, type); + + return ret; +} + +static int inno_write_infoframe(struct drm_bridge *bridge, + enum hdmi_infoframe_type type, + const u8 *buffer, size_t len) +{ + struct inno_hdmi *hdmi = bridge->driver_private; + int ret = 0; + + if (hdmi->plat_data->hdmi_write_infoframe) + ret = hdmi->plat_data->hdmi_write_infoframe(hdmi, type, buffer, len); + + return ret; +} + +static int inno_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return drm_atomic_helper_connector_hdmi_check(conn_state->connector, + bridge_state->base.state); +} + +static const struct drm_bridge_funcs inno_bridge_funcs = { + .atomic_check = inno_bridge_atomic_check, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .attach = inno_bridge_attach, + .detect = inno_hdmi_bridge_detect, + .edid_read = inno_bridge_edid_read, + .mode_valid = inno_bridge_mode_valid, + .hdmi_clear_infoframe = inno_hdmi_clear_infoframe, + .hdmi_write_infoframe = inno_write_infoframe, +}; + +int inno_hdmi_bind(struct drm_device *drm, struct inno_hdmi *hdmi, struct drm_encoder *encoder) +{ + int ret, irq; + struct platform_device *pdev = to_platform_device(hdmi->dev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + return ret; + } + + hdmi->ddc = inno_hdmi_i2c_adapter(hdmi); + if (IS_ERR(hdmi->ddc)) { + ret = PTR_ERR(hdmi->ddc); + hdmi->ddc = NULL; + return ret; + } + + hdmi->next_bridge = devm_drm_of_get_bridge(hdmi->dev, hdmi->dev->of_node, 1, 0); + if (IS_ERR(hdmi->next_bridge)) + return dev_err_probe(hdmi->dev, PTR_ERR(hdmi->next_bridge), + "failed to acquire drm_bridge\n"); + + hdmi->bridge.driver_private = hdmi; + hdmi->bridge.funcs = &inno_bridge_funcs; + hdmi->bridge.ddc = hdmi->ddc; +#ifdef CONFIG_OF + hdmi->bridge.of_node = hdmi->dev->of_node; +#endif + hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_HDMI; + hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + hdmi->bridge.vendor = hdmi->plat_data->vendor; + hdmi->bridge.product = hdmi->plat_data->product; + + devm_drm_bridge_add(hdmi->dev, &hdmi->bridge); + + /* Unmute hotplug interrupt */ + hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1)); + + ret = devm_request_threaded_irq(hdmi->dev, irq, inno_hdmi_hardirq, + inno_hdmi_irq, IRQF_SHARED, + dev_name(hdmi->dev), hdmi); + return ret; +} +EXPORT_SYMBOL_GPL(inno_hdmi_bind); + +MODULE_AUTHOR("Keith Zhao "); +MODULE_DESCRIPTION("INNO HDMI transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:inno-hdmi"); diff --git a/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h new file mode 100644 index 000000000000..75e3e002947f --- /dev/null +++ b/drivers/gpu/drm/bridge/innosilicon/inno-hdmi.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Zheng Yang + * Yakir Yang + * Copyright (C) StarFive Technology Co., Ltd. + */ + +#ifndef __INNO_H__ +#define __INNO_H__ + +#define DDC_SEGMENT_ADDR 0x30 + +#define HDMI_SCL_RATE (100 * 1000) +#define DDC_BUS_FREQ_L 0x4b +#define DDC_BUS_FREQ_H 0x4c + +#define HDMI_EDID_SEGMENT_POINTER 0x4d +#define HDMI_EDID_WORD_ADDR 0x4e +#define HDMI_EDID_FIFO_OFFSET 0x4f +#define HDMI_EDID_FIFO_ADDR 0x50 + +#define HDMI_INTERRUPT_MASK1 0xc0 +#define HDMI_INTERRUPT_STATUS1 0xc1 +#define m_INT_ACTIVE_VSYNC (1 << 5) +#define m_INT_EDID_READY (1 << 2) + +#define HDMI_STATUS 0xc8 +#define m_HOTPLUG (1 << 7) +#define m_MASK_INT_HOTPLUG (1 << 5) +#define m_INT_HOTPLUG (1 << 1) +#define v_MASK_INT_HOTPLUG(n) (((n) & 0x1) << 5) + +#endif /* __INNO_H__ */ diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 258dd38e1a8e..e728b5ee2219 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -78,6 +78,7 @@ config ROCKCHIP_INNO_HDMI select DRM_DISPLAY_HDMI_HELPER select DRM_DISPLAY_HDMI_STATE_HELPER select DRM_DISPLAY_HELPER + select DRM_INNO_HDMI help This selects support for Rockchip SoC specific extensions for the Innosilicon HDMI driver. If you want to enable diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 3ff7b21c0414..4b2d0cba8db3 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -12,7 +12,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o -rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o +rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o diff --git a/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c new file mode 100644 index 000000000000..2bde9d539e9e --- /dev/null +++ b/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Zheng Yang + * Yakir Yang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rockchip_drm_drv.h" + +#include "inno_hdmi-rockchip.h" + +#define INNO_HDMI_MIN_TMDS_CLOCK 25000000U + +struct inno_hdmi_rockchip_phy_config { + unsigned long pixelclock; + u8 pre_emphasis; + u8 voltage_level_control; +}; + +struct inno_hdmi_rockchip_variant { + struct inno_hdmi_rockchip_phy_config *phy_configs; + struct inno_hdmi_rockchip_phy_config *default_phy_config; + const struct inno_hdmi_plat_data *plat_data; +}; + +struct rockchip_inno_hdmi { + struct device *dev; + + struct clk *pclk; + struct clk *refclk; + + struct inno_hdmi inno_hdmi; + struct drm_connector *connector; + struct rockchip_encoder encoder; + + const struct inno_hdmi_rockchip_variant *variant; + unsigned int colorimetry; +}; + +static struct rockchip_inno_hdmi *encoder_to_rk_hdmi(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct rockchip_inno_hdmi, encoder); +} + +static struct rockchip_inno_hdmi *inno_hdmi_to_rk_hdmi(struct inno_hdmi *inno_hdmi) +{ + return container_of(inno_hdmi, struct rockchip_inno_hdmi, inno_hdmi); +} + +enum { + CSC_RGB_0_255_TO_ITU601_16_235_8BIT, + CSC_RGB_0_255_TO_ITU709_16_235_8BIT, + CSC_RGB_0_255_TO_RGB_16_235_8BIT, +}; + +static const char coeff_csc[][24] = { + /* + * RGB2YUV:601 SD mode: + * Cb = -0.291G - 0.148R + 0.439B + 128 + * Y = 0.504G + 0.257R + 0.098B + 16 + * Cr = -0.368G + 0.439R - 0.071B + 128 + */ + { + 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80, + 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e, + 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80 + }, + /* + * RGB2YUV:709 HD mode: + * Cb = - 0.338G - 0.101R + 0.439B + 128 + * Y = 0.614G + 0.183R + 0.062B + 16 + * Cr = - 0.399G + 0.439R - 0.040B + 128 + */ + { + 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80, + 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10, + 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80 + }, + /* + * RGB[0:255]2RGB[16:235]: + * R' = R x (235-16)/255 + 16; + * G' = G x (235-16)/255 + 16; + * B' = B x (235-16)/255 + 16; + */ + { + 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10, + 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10 + }, +}; + +static struct inno_hdmi_rockchip_phy_config rk3036_hdmi_phy_configs[] = { + { 74250000, 0x3f, 0xbb }, + { 165000000, 0x6f, 0xbb }, + { ~0UL, 0x00, 0x00 } +}; + +static struct inno_hdmi_rockchip_phy_config rk3128_hdmi_phy_configs[] = { + { 74250000, 0x3f, 0xaa }, + { 165000000, 0x5f, 0xaa }, + { ~0UL, 0x00, 0x00 } +}; + +static int inno_hdmi_rockchip_find_phy_config(struct rockchip_inno_hdmi *rk_hdmi, + unsigned long pixelclk) +{ + const struct inno_hdmi_rockchip_phy_config *phy_configs = + rk_hdmi->variant->phy_configs; + int i; + + for (i = 0; phy_configs[i].pixelclock != ~0UL; i++) { + if (pixelclk <= phy_configs[i].pixelclock) + return i; + } + + DRM_DEV_DEBUG(rk_hdmi->dev, "No phy configuration for pixelclock %lu\n", + pixelclk); + + return -EINVAL; +} + +static void inno_hdmi_rockchip_sys_power(struct inno_hdmi *hdmi, bool enable) +{ + if (enable) + hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON); + else + hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF); +} + +static void inno_hdmi_rockchip_standby(struct inno_hdmi *hdmi) +{ + inno_hdmi_rockchip_sys_power(hdmi, false); + + hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00); + hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00); + hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00); + hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); +}; + +static void inno_hdmi_rockchip_power_up(struct rockchip_inno_hdmi *rk_hdmi, + unsigned long mpixelclock) +{ + struct inno_hdmi_rockchip_phy_config *phy_config; + struct inno_hdmi *hdmi = &rk_hdmi->inno_hdmi; + int ret = inno_hdmi_rockchip_find_phy_config(rk_hdmi, mpixelclock); + + if (ret < 0) { + phy_config = rk_hdmi->variant->default_phy_config; + DRM_DEV_ERROR(rk_hdmi->dev, + "Using default phy configuration for TMDS rate %lu", + mpixelclock); + } else { + phy_config = &rk_hdmi->variant->phy_configs[ret]; + } + + inno_hdmi_rockchip_sys_power(hdmi, false); + + hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, phy_config->pre_emphasis); + hdmi_writeb(hdmi, HDMI_PHY_DRIVER, phy_config->voltage_level_control); + hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); + hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14); + hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10); + hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f); + hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00); + hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01); + + inno_hdmi_rockchip_sys_power(hdmi, true); +}; + +static void inno_hdmi_rockchip_reset(struct inno_hdmi *hdmi) +{ + u32 val; + u32 msk; + + hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL); + udelay(100); + + hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG); + udelay(100); + + msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL; + val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH; + hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val); + + inno_hdmi_rockchip_standby(hdmi); +} + +static int inno_hdmi_rockchip_disable_frame(struct inno_hdmi *hdmi, + enum hdmi_infoframe_type type) +{ + if (type != HDMI_INFOFRAME_TYPE_AVI) { + drm_err(hdmi->bridge.dev, + "Unsupported infoframe type: %u\n", type); + return 0; + } + + hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, INFOFRAME_AVI); + + return 0; +} + +static int inno_hdmi_rockchip_upload_frame(struct inno_hdmi *hdmi, + enum hdmi_infoframe_type type, + const u8 *buffer, size_t len) +{ + ssize_t i; + + if (type != HDMI_INFOFRAME_TYPE_AVI) { + drm_err(hdmi->bridge.dev, + "Unsupported infoframe type: %u\n", type); + return 0; + } + + inno_hdmi_rockchip_disable_frame(hdmi, type); + + for (i = 0; i < len; i++) + hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i, buffer[i]); + + return 0; +} + +static int inno_hdmi_rockchip_config_video_csc(struct rockchip_inno_hdmi *rk_hdmi) +{ + struct drm_connector *connector = rk_hdmi->connector; + struct drm_connector_state *conn_state = connector->state; + struct inno_hdmi *hdmi = &rk_hdmi->inno_hdmi; + + int c0_c2_change = 0; + int csc_enable = 0; + int csc_mode = 0; + int auto_csc = 0; + int value; + int i; + + /* Input video mode is SDR RGB24bit, data enable signal from external */ + hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL | + v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444)); + + /* Input color hardcode to RGB, and output color hardcode to RGB888 */ + value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) | + v_VIDEO_OUTPUT_COLOR(0) | + v_VIDEO_INPUT_CSP(0); + hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value); + + if (conn_state->hdmi.output_format == HDMI_COLORSPACE_RGB) { + if (conn_state->hdmi.is_limited_range) { + csc_mode = CSC_RGB_0_255_TO_RGB_16_235_8BIT; + auto_csc = AUTO_CSC_DISABLE; + c0_c2_change = C0_C2_CHANGE_DISABLE; + csc_enable = v_CSC_ENABLE; + + } else { + value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1); + hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); + + hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, + m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP, + v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) | + v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE)); + return 0; + } + } else { + if (rk_hdmi->colorimetry == HDMI_COLORIMETRY_ITU_601) { + if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { + csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; + auto_csc = AUTO_CSC_DISABLE; + c0_c2_change = C0_C2_CHANGE_DISABLE; + csc_enable = v_CSC_ENABLE; + } + } else { + if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { + csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT; + auto_csc = AUTO_CSC_DISABLE; + c0_c2_change = C0_C2_CHANGE_DISABLE; + csc_enable = v_CSC_ENABLE; + } + } + } + + for (i = 0; i < 24; i++) + hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, + coeff_csc[csc_mode][i]); + + value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1); + hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); + hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC | + m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) | + v_VIDEO_C0_C2_SWAP(c0_c2_change)); + + return 0; +} + +static int inno_hdmi_rockchip_config_video_timing(struct inno_hdmi *hdmi, + struct drm_display_mode *mode) +{ + int value; + + /* Set detail external video timing polarity and interlace mode */ + value = v_EXTERANL_VIDEO(1); + value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? + v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0); + value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? + v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0); + value |= mode->flags & DRM_MODE_FLAG_INTERLACE ? + v_INETLACE(1) : v_INETLACE(0); + hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value); + + /* Set detail external video timing */ + value = mode->htotal; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF); + + value = mode->htotal - mode->hdisplay; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF); + + value = mode->htotal - mode->hsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF); + + value = mode->hsync_end - mode->hsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF); + + value = mode->vtotal; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF); + + value = mode->vtotal - mode->vdisplay; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF); + + value = mode->vtotal - mode->vsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF); + + value = mode->vsync_end - mode->vsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF); + + hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e); + hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c); + hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01); + + return 0; +} + +static int inno_hdmi_rockchip_setup(struct rockchip_inno_hdmi *rk_hdmi, + struct drm_atomic_state *state) +{ + struct inno_hdmi *hdmi = &rk_hdmi->inno_hdmi; + struct drm_connector *connector = rk_hdmi->connector; + struct drm_display_info *display = &connector->display_info; + struct drm_connector_state *new_conn_state; + struct drm_crtc_state *new_crtc_state; + + new_conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!new_conn_state)) + return -EINVAL; + + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); + if (WARN_ON(!new_crtc_state)) + return -EINVAL; + + /* Mute video and audio output */ + hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, + v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1)); + + /* Set HDMI Mode */ + hdmi_writeb(hdmi, HDMI_HDCP_CTRL, + v_HDMI_DVI(display->is_hdmi)); + + inno_hdmi_rockchip_config_video_timing(hdmi, &new_crtc_state->adjusted_mode); + + inno_hdmi_rockchip_config_video_csc(rk_hdmi); + + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); + + /* + * When IP controller have configured to an accurate video + * timing, then the TMDS clock source would be switched to + * DCLK_LCDC, so we need to init the TMDS rate to mode pixel + * clock rate, and reconfigure the DDC clock. + */ + inno_hdmi_i2c_init(hdmi, new_conn_state->hdmi.tmds_char_rate); + + /* Unmute video and audio output */ + hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, + v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0)); + + inno_hdmi_rockchip_power_up(rk_hdmi, new_conn_state->hdmi.tmds_char_rate); + + return 0; +} + +static enum drm_mode_status inno_hdmi_rockchip_mode_valid(struct inno_hdmi *hdmi, + const struct drm_display_mode *mode) +{ + unsigned long mpixelclk, max_tolerance; + long rounded_refclk; + struct rockchip_inno_hdmi *rk_hdmi = inno_hdmi_to_rk_hdmi(hdmi); + + /* No support for double-clock modes */ + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return MODE_BAD; + + mpixelclk = mode->clock * 1000; + + if (mpixelclk < INNO_HDMI_MIN_TMDS_CLOCK) + return MODE_CLOCK_LOW; + + if (inno_hdmi_rockchip_find_phy_config(rk_hdmi, mpixelclk) < 0) + return MODE_CLOCK_HIGH; + + if (rk_hdmi->refclk) { + rounded_refclk = clk_round_rate(rk_hdmi->refclk, mpixelclk); + if (rounded_refclk < 0) + return MODE_BAD; + + /* Vesa DMT standard mentions +/- 0.5% max tolerance */ + max_tolerance = mpixelclk / 200; + if (abs_diff((unsigned long)rounded_refclk, mpixelclk) > max_tolerance) + return MODE_NOCLOCK; + } + + return MODE_OK; +} + +static void inno_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct rockchip_inno_hdmi *rk_hdmi = encoder_to_rk_hdmi(encoder); + + inno_hdmi_rockchip_setup(rk_hdmi, state); +} + +static void inno_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct rockchip_inno_hdmi *rk_hdmi = encoder_to_rk_hdmi(encoder); + + inno_hdmi_rockchip_standby(&rk_hdmi->inno_hdmi); +} + +static int +inno_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct drm_display_mode *mode = &crtc_state->adjusted_mode; + u8 vic = drm_match_cea_mode(mode); + struct rockchip_inno_hdmi *rk_hdmi = encoder_to_rk_hdmi(encoder); + + s->output_mode = ROCKCHIP_OUT_MODE_P888; + s->output_type = DRM_MODE_CONNECTOR_HDMIA; + + if (vic == 6 || vic == 7 || + vic == 21 || vic == 22 || + vic == 2 || vic == 3 || + vic == 17 || vic == 18) + rk_hdmi->colorimetry = HDMI_COLORIMETRY_ITU_601; + else + rk_hdmi->colorimetry = HDMI_COLORIMETRY_ITU_709; + + return 0; +} + +static const struct drm_encoder_helper_funcs inno_hdmi_rockchip_encoder_helper_funcs = { + .atomic_check = inno_hdmi_rockchip_encoder_atomic_check, + .atomic_enable = inno_hdmi_rockchip_encoder_enable, + .atomic_disable = inno_hdmi_rockchip_encoder_disable, +}; + +static int inno_hdmi_rockchip_register(struct drm_device *drm, struct inno_hdmi *hdmi) +{ + struct rockchip_inno_hdmi *rk_hdmi = inno_hdmi_to_rk_hdmi(hdmi); + struct drm_encoder *encoder = &rk_hdmi->encoder.encoder; + struct device *dev = hdmi->dev; + int ret; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + drm_encoder_helper_add(encoder, &inno_hdmi_rockchip_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + + ret = inno_hdmi_bind(drm, hdmi, encoder); + if (ret) + goto err_cleanup_encoder; + + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + goto err_cleanup_encoder; + + rk_hdmi->connector = drm_bridge_connector_init(drm, encoder); + if (IS_ERR(rk_hdmi->connector)) { + dev_err(dev, "Unable to create bridge connector\n"); + ret = PTR_ERR(rk_hdmi->connector); + return ret; + } + + drm_connector_attach_encoder(rk_hdmi->connector, encoder); + + return 0; + +err_cleanup_encoder: + drm_encoder_cleanup(encoder); + return ret; +} + +static int inno_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct rockchip_inno_hdmi *rk_hdmi; + struct inno_hdmi *hdmi; + const struct inno_hdmi_rockchip_variant *variant; + int ret; + + rk_hdmi = devm_kzalloc(dev, sizeof(*rk_hdmi), GFP_KERNEL); + if (!rk_hdmi) + return -ENOMEM; + + rk_hdmi->dev = dev; + hdmi = &rk_hdmi->inno_hdmi; + hdmi->dev = dev; + + variant = of_device_get_match_data(hdmi->dev); + if (!variant) + return -EINVAL; + + rk_hdmi->variant = variant; + hdmi->plat_data = variant->plat_data; + + hdmi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hdmi->regs)) + return PTR_ERR(hdmi->regs); + + rk_hdmi->pclk = devm_clk_get(hdmi->dev, "pclk"); + if (IS_ERR(rk_hdmi->pclk)) { + DRM_DEV_ERROR(rk_hdmi->dev, "Unable to get HDMI pclk clk\n"); + return PTR_ERR(rk_hdmi->pclk); + } + + ret = clk_prepare_enable(rk_hdmi->pclk); + if (ret) { + DRM_DEV_ERROR(rk_hdmi->dev, + "Cannot enable HDMI pclk clock: %d\n", ret); + return ret; + } + + rk_hdmi->refclk = devm_clk_get_optional(rk_hdmi->dev, "ref"); + if (IS_ERR(rk_hdmi->refclk)) { + DRM_DEV_ERROR(rk_hdmi->dev, "Unable to get HDMI reference clock\n"); + ret = PTR_ERR(rk_hdmi->refclk); + goto err_disable_pclk; + } + + ret = clk_prepare_enable(rk_hdmi->refclk); + if (ret) { + DRM_DEV_ERROR(rk_hdmi->dev, + "Cannot enable HDMI reference clock: %d\n", ret); + goto err_disable_pclk; + } + + inno_hdmi_rockchip_reset(hdmi); + + /* + * When the controller isn't configured to an accurate + * video timing and there is no reference clock available, + * then the TMDS clock source would be switched to PCLK_HDMI, + * so we need to init the TMDS rate to PCLK rate, and + * reconfigure the DDC clock. + */ + if (rk_hdmi->refclk) + inno_hdmi_i2c_init(hdmi, clk_get_rate(rk_hdmi->refclk)); + else + inno_hdmi_i2c_init(hdmi, clk_get_rate(rk_hdmi->pclk)); + + ret = inno_hdmi_rockchip_register(drm, hdmi); + if (ret) + goto err_disable_clk; + + dev_set_drvdata(dev, hdmi); + + return 0; + +err_disable_clk: + clk_disable_unprepare(rk_hdmi->refclk); +err_disable_pclk: + clk_disable_unprepare(rk_hdmi->pclk); + return ret; +} + +static void inno_hdmi_rockchip_unbind(struct device *dev, struct device *master, + void *data) +{ + struct inno_hdmi *hdmi = dev_get_drvdata(dev); + struct rockchip_inno_hdmi *rk_hdmi = inno_hdmi_to_rk_hdmi(hdmi); + + rk_hdmi->encoder.encoder.funcs->destroy(&rk_hdmi->encoder.encoder); + + clk_disable_unprepare(rk_hdmi->refclk); + clk_disable_unprepare(rk_hdmi->pclk); +} + +static const struct component_ops inno_hdmi_rockchip_ops = { + .bind = inno_hdmi_rockchip_bind, + .unbind = inno_hdmi_rockchip_unbind, +}; + +static int inno_hdmi_rockchip_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &inno_hdmi_rockchip_ops); +} + +static void inno_hdmi_rockchip_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &inno_hdmi_rockchip_ops); +} + +const struct inno_hdmi_plat_data data_inno = { + .vendor = "Rockchip", + .product = "Inno HDMI", + .mode_valid = inno_hdmi_rockchip_mode_valid, + .hdmi_clear_infoframe = inno_hdmi_rockchip_disable_frame, + .hdmi_write_infoframe = inno_hdmi_rockchip_upload_frame, +}; + +static const struct inno_hdmi_rockchip_variant rk3036_inno_hdmi_variant = { + .phy_configs = rk3036_hdmi_phy_configs, + .default_phy_config = &rk3036_hdmi_phy_configs[1], + .plat_data = &data_inno, +}; + +static const struct inno_hdmi_rockchip_variant rk3128_inno_hdmi_variant = { + .phy_configs = rk3128_hdmi_phy_configs, + .default_phy_config = &rk3128_hdmi_phy_configs[1], + .plat_data = &data_inno, +}; + +static const struct of_device_id inno_hdmi_dt_ids[] = { + { .compatible = "rockchip,rk3036-inno-hdmi", + .data = &rk3036_inno_hdmi_variant, + }, + { .compatible = "rockchip,rk3128-inno-hdmi", + .data = &rk3128_inno_hdmi_variant, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids); + +struct platform_driver inno_hdmi_driver = { + .probe = inno_hdmi_rockchip_probe, + .remove_new = inno_hdmi_rockchip_remove, + .driver = { + .name = "innohdmi-rockchip", + .of_match_table = inno_hdmi_dt_ids, + }, +}; diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.h b/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.h similarity index 100% rename from drivers/gpu/drm/rockchip/inno_hdmi.h rename to drivers/gpu/drm/rockchip/inno_hdmi-rockchip.h diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/rockchip/inno_hdmi.c deleted file mode 100644 index 42ef62aa0a1e..000000000000 --- a/drivers/gpu/drm/rockchip/inno_hdmi.c +++ /dev/null @@ -1,1025 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd - * Zheng Yang - * Yakir Yang - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "rockchip_drm_drv.h" - -#include "inno_hdmi.h" - -#define INNO_HDMI_MIN_TMDS_CLOCK 25000000U - -struct inno_hdmi_phy_config { - unsigned long pixelclock; - u8 pre_emphasis; - u8 voltage_level_control; -}; - -struct inno_hdmi_variant { - struct inno_hdmi_phy_config *phy_configs; - struct inno_hdmi_phy_config *default_phy_config; -}; - -struct inno_hdmi_i2c { - struct i2c_adapter adap; - - u8 ddc_addr; - u8 segment_addr; - - struct mutex lock; - struct completion cmp; -}; - -struct inno_hdmi { - struct device *dev; - - struct clk *pclk; - struct clk *refclk; - void __iomem *regs; - - struct drm_connector connector; - struct rockchip_encoder encoder; - - struct inno_hdmi_i2c *i2c; - struct i2c_adapter *ddc; - - const struct inno_hdmi_variant *variant; -}; - -struct inno_hdmi_connector_state { - struct drm_connector_state base; - unsigned int colorimetry; -}; - -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); -} - -#define to_inno_hdmi_conn_state(conn_state) \ - container_of_const(conn_state, struct inno_hdmi_connector_state, base) - -enum { - CSC_RGB_0_255_TO_ITU601_16_235_8BIT, - CSC_RGB_0_255_TO_ITU709_16_235_8BIT, - CSC_RGB_0_255_TO_RGB_16_235_8BIT, -}; - -static const char coeff_csc[][24] = { - /* - * RGB2YUV:601 SD mode: - * Cb = -0.291G - 0.148R + 0.439B + 128 - * Y = 0.504G + 0.257R + 0.098B + 16 - * Cr = -0.368G + 0.439R - 0.071B + 128 - */ - { - 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80, - 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e, - 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80 - }, - /* - * RGB2YUV:709 HD mode: - * Cb = - 0.338G - 0.101R + 0.439B + 128 - * Y = 0.614G + 0.183R + 0.062B + 16 - * Cr = - 0.399G + 0.439R - 0.040B + 128 - */ - { - 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80, - 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10, - 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80 - }, - /* - * RGB[0:255]2RGB[16:235]: - * R' = R x (235-16)/255 + 16; - * G' = G x (235-16)/255 + 16; - * B' = B x (235-16)/255 + 16; - */ - { - 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10, - 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10 - }, -}; - -static struct inno_hdmi_phy_config rk3036_hdmi_phy_configs[] = { - { 74250000, 0x3f, 0xbb }, - { 165000000, 0x6f, 0xbb }, - { ~0UL, 0x00, 0x00 } -}; - -static struct inno_hdmi_phy_config rk3128_hdmi_phy_configs[] = { - { 74250000, 0x3f, 0xaa }, - { 165000000, 0x5f, 0xaa }, - { ~0UL, 0x00, 0x00 } -}; - -static int inno_hdmi_find_phy_config(struct inno_hdmi *hdmi, - unsigned long pixelclk) -{ - const struct inno_hdmi_phy_config *phy_configs = - hdmi->variant->phy_configs; - int i; - - for (i = 0; phy_configs[i].pixelclock != ~0UL; i++) { - if (pixelclk <= phy_configs[i].pixelclock) - return i; - } - - DRM_DEV_DEBUG(hdmi->dev, "No phy configuration for pixelclock %lu\n", - pixelclk); - - return -EINVAL; -} - -static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) -{ - return readl_relaxed(hdmi->regs + (offset) * 0x04); -} - -static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) -{ - writel_relaxed(val, hdmi->regs + (offset) * 0x04); -} - -static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, - u32 msk, u32 val) -{ - u8 temp = hdmi_readb(hdmi, offset) & ~msk; - - temp |= val & msk; - hdmi_writeb(hdmi, offset, temp); -} - -static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate) -{ - unsigned long long ddc_bus_freq = rate >> 2; - - do_div(ddc_bus_freq, HDMI_SCL_RATE); - - hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF); - hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF); - - /* Clear the EDID interrupt flag and mute the interrupt */ - hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); - hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); -} - -static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable) -{ - if (enable) - hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON); - else - hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF); -} - -static void inno_hdmi_standby(struct inno_hdmi *hdmi) -{ - inno_hdmi_sys_power(hdmi, false); - - hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00); - hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00); - hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00); - hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); -}; - -static void inno_hdmi_power_up(struct inno_hdmi *hdmi, - unsigned long mpixelclock) -{ - struct inno_hdmi_phy_config *phy_config; - int ret = inno_hdmi_find_phy_config(hdmi, mpixelclock); - - if (ret < 0) { - phy_config = hdmi->variant->default_phy_config; - DRM_DEV_ERROR(hdmi->dev, - "Using default phy configuration for TMDS rate %lu", - mpixelclock); - } else { - phy_config = &hdmi->variant->phy_configs[ret]; - } - - inno_hdmi_sys_power(hdmi, false); - - hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, phy_config->pre_emphasis); - hdmi_writeb(hdmi, HDMI_PHY_DRIVER, phy_config->voltage_level_control); - hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); - hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14); - hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10); - hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f); - hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00); - hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01); - - inno_hdmi_sys_power(hdmi, true); -}; - -static void inno_hdmi_reset(struct inno_hdmi *hdmi) -{ - u32 val; - u32 msk; - - hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL); - udelay(100); - - hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG); - udelay(100); - - msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL; - val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH; - hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val); - - inno_hdmi_standby(hdmi); -} - -static int inno_hdmi_disable_frame(struct drm_connector *connector, - enum hdmi_infoframe_type type) -{ - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); - - if (type != HDMI_INFOFRAME_TYPE_AVI) { - drm_err(connector->dev, - "Unsupported infoframe type: %u\n", type); - return 0; - } - - hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, INFOFRAME_AVI); - - return 0; -} - -static int inno_hdmi_upload_frame(struct drm_connector *connector, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) -{ - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); - ssize_t i; - - if (type != HDMI_INFOFRAME_TYPE_AVI) { - drm_err(connector->dev, - "Unsupported infoframe type: %u\n", type); - return 0; - } - - inno_hdmi_disable_frame(connector, type); - - for (i = 0; i < len; i++) - hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i, buffer[i]); - - return 0; -} - -static const struct drm_connector_hdmi_funcs inno_hdmi_hdmi_connector_funcs = { - .clear_infoframe = inno_hdmi_disable_frame, - .write_infoframe = inno_hdmi_upload_frame, -}; - -static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) -{ - struct drm_connector *connector = &hdmi->connector; - struct drm_connector_state *conn_state = connector->state; - struct inno_hdmi_connector_state *inno_conn_state = - to_inno_hdmi_conn_state(conn_state); - int c0_c2_change = 0; - int csc_enable = 0; - int csc_mode = 0; - int auto_csc = 0; - int value; - int i; - - /* Input video mode is SDR RGB24bit, data enable signal from external */ - hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL | - v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444)); - - /* Input color hardcode to RGB, and output color hardcode to RGB888 */ - value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) | - v_VIDEO_OUTPUT_COLOR(0) | - v_VIDEO_INPUT_CSP(0); - hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value); - - if (conn_state->hdmi.output_format == HDMI_COLORSPACE_RGB) { - if (conn_state->hdmi.is_limited_range) { - csc_mode = CSC_RGB_0_255_TO_RGB_16_235_8BIT; - auto_csc = AUTO_CSC_DISABLE; - c0_c2_change = C0_C2_CHANGE_DISABLE; - csc_enable = v_CSC_ENABLE; - - } else { - value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1); - hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); - - hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, - m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP, - v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) | - v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE)); - return 0; - } - } else { - if (inno_conn_state->colorimetry == HDMI_COLORIMETRY_ITU_601) { - if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { - csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; - auto_csc = AUTO_CSC_DISABLE; - c0_c2_change = C0_C2_CHANGE_DISABLE; - csc_enable = v_CSC_ENABLE; - } - } else { - if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { - csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT; - auto_csc = AUTO_CSC_DISABLE; - c0_c2_change = C0_C2_CHANGE_DISABLE; - csc_enable = v_CSC_ENABLE; - } - } - } - - for (i = 0; i < 24; i++) - hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, - coeff_csc[csc_mode][i]); - - value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1); - hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); - hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC | - m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) | - v_VIDEO_C0_C2_SWAP(c0_c2_change)); - - return 0; -} - -static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, - struct drm_display_mode *mode) -{ - int value; - - /* Set detail external video timing polarity and interlace mode */ - value = v_EXTERANL_VIDEO(1); - value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? - v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0); - value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? - v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0); - value |= mode->flags & DRM_MODE_FLAG_INTERLACE ? - v_INETLACE(1) : v_INETLACE(0); - hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value); - - /* Set detail external video timing */ - value = mode->htotal; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF); - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF); - - value = mode->htotal - mode->hdisplay; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF); - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF); - - value = mode->htotal - mode->hsync_start; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF); - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF); - - value = mode->hsync_end - mode->hsync_start; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF); - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF); - - value = mode->vtotal; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF); - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF); - - value = mode->vtotal - mode->vdisplay; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF); - - value = mode->vtotal - mode->vsync_start; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF); - - value = mode->vsync_end - mode->vsync_start; - hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF); - - hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e); - hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c); - hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01); - - return 0; -} - -static int inno_hdmi_setup(struct inno_hdmi *hdmi, - struct drm_atomic_state *state) -{ - struct drm_connector *connector = &hdmi->connector; - struct drm_display_info *display = &connector->display_info; - struct drm_connector_state *new_conn_state; - struct drm_crtc_state *new_crtc_state; - - new_conn_state = drm_atomic_get_new_connector_state(state, connector); - if (WARN_ON(!new_conn_state)) - return -EINVAL; - - new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); - if (WARN_ON(!new_crtc_state)) - return -EINVAL; - - /* Mute video and audio output */ - hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, - v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1)); - - /* Set HDMI Mode */ - hdmi_writeb(hdmi, HDMI_HDCP_CTRL, - v_HDMI_DVI(display->is_hdmi)); - - inno_hdmi_config_video_timing(hdmi, &new_crtc_state->adjusted_mode); - - inno_hdmi_config_video_csc(hdmi); - - drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); - - /* - * When IP controller have configured to an accurate video - * timing, then the TMDS clock source would be switched to - * DCLK_LCDC, so we need to init the TMDS rate to mode pixel - * clock rate, and reconfigure the DDC clock. - */ - inno_hdmi_i2c_init(hdmi, new_conn_state->hdmi.tmds_char_rate); - - /* Unmute video and audio output */ - hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, - v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0)); - - inno_hdmi_power_up(hdmi, new_conn_state->hdmi.tmds_char_rate); - - return 0; -} - -static enum drm_mode_status inno_hdmi_display_mode_valid(struct inno_hdmi *hdmi, - struct drm_display_mode *mode) -{ - unsigned long mpixelclk, max_tolerance; - long rounded_refclk; - - /* No support for double-clock modes */ - if (mode->flags & DRM_MODE_FLAG_DBLCLK) - return MODE_BAD; - - mpixelclk = mode->clock * 1000; - - if (mpixelclk < INNO_HDMI_MIN_TMDS_CLOCK) - return MODE_CLOCK_LOW; - - if (inno_hdmi_find_phy_config(hdmi, mpixelclk) < 0) - return MODE_CLOCK_HIGH; - - if (hdmi->refclk) { - rounded_refclk = clk_round_rate(hdmi->refclk, mpixelclk); - if (rounded_refclk < 0) - return MODE_BAD; - - /* Vesa DMT standard mentions +/- 0.5% max tolerance */ - max_tolerance = mpixelclk / 200; - if (abs_diff((unsigned long)rounded_refclk, mpixelclk) > max_tolerance) - return MODE_NOCLOCK; - } - - return MODE_OK; -} - -static void inno_hdmi_encoder_enable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); - - inno_hdmi_setup(hdmi, state); -} - -static void inno_hdmi_encoder_disable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); - - inno_hdmi_standby(hdmi); -} - -static int -inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); - struct drm_display_mode *mode = &crtc_state->adjusted_mode; - u8 vic = drm_match_cea_mode(mode); - struct inno_hdmi_connector_state *inno_conn_state = - to_inno_hdmi_conn_state(conn_state); - - s->output_mode = ROCKCHIP_OUT_MODE_P888; - s->output_type = DRM_MODE_CONNECTOR_HDMIA; - - if (vic == 6 || vic == 7 || - vic == 21 || vic == 22 || - vic == 2 || vic == 3 || - vic == 17 || vic == 18) - inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_601; - else - inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709; - - return 0; -} - -static const struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = { - .atomic_check = inno_hdmi_encoder_atomic_check, - .atomic_enable = inno_hdmi_encoder_enable, - .atomic_disable = inno_hdmi_encoder_disable, -}; - -static enum drm_connector_status -inno_hdmi_connector_detect(struct drm_connector *connector, bool force) -{ - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); - - return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? - connector_status_connected : connector_status_disconnected; -} - -static int inno_hdmi_connector_get_modes(struct drm_connector *connector) -{ - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); - const struct drm_edid *drm_edid; - int ret = 0; - - if (!hdmi->ddc) - return 0; - - drm_edid = drm_edid_read_ddc(connector, hdmi->ddc); - drm_edid_connector_update(connector, drm_edid); - ret = drm_edid_connector_add_modes(connector); - drm_edid_free(drm_edid); - - return ret; -} - -static enum drm_mode_status -inno_hdmi_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); - - return inno_hdmi_display_mode_valid(hdmi, mode); -} - -static void -inno_hdmi_connector_destroy_state(struct drm_connector *connector, - struct drm_connector_state *state) -{ - struct inno_hdmi_connector_state *inno_conn_state = - to_inno_hdmi_conn_state(state); - - __drm_atomic_helper_connector_destroy_state(&inno_conn_state->base); - kfree(inno_conn_state); -} - -static void inno_hdmi_connector_reset(struct drm_connector *connector) -{ - struct inno_hdmi_connector_state *inno_conn_state; - - if (connector->state) { - inno_hdmi_connector_destroy_state(connector, connector->state); - connector->state = NULL; - } - - inno_conn_state = kzalloc(sizeof(*inno_conn_state), GFP_KERNEL); - if (!inno_conn_state) - return; - - __drm_atomic_helper_connector_reset(connector, &inno_conn_state->base); - __drm_atomic_helper_connector_hdmi_reset(connector, connector->state); - - inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709; -} - -static struct drm_connector_state * -inno_hdmi_connector_duplicate_state(struct drm_connector *connector) -{ - struct inno_hdmi_connector_state *inno_conn_state; - - if (WARN_ON(!connector->state)) - return NULL; - - inno_conn_state = kmemdup(to_inno_hdmi_conn_state(connector->state), - sizeof(*inno_conn_state), GFP_KERNEL); - - if (!inno_conn_state) - return NULL; - - __drm_atomic_helper_connector_duplicate_state(connector, - &inno_conn_state->base); - - return &inno_conn_state->base; -} - -static const struct drm_connector_funcs inno_hdmi_connector_funcs = { - .fill_modes = drm_helper_probe_single_connector_modes, - .detect = inno_hdmi_connector_detect, - .reset = inno_hdmi_connector_reset, - .atomic_duplicate_state = inno_hdmi_connector_duplicate_state, - .atomic_destroy_state = inno_hdmi_connector_destroy_state, -}; - -static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = { - .atomic_check = drm_atomic_helper_connector_hdmi_check, - .get_modes = inno_hdmi_connector_get_modes, - .mode_valid = inno_hdmi_connector_mode_valid, -}; - -static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) -{ - struct drm_encoder *encoder = &hdmi->encoder.encoder; - struct device *dev = hdmi->dev; - - encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); - - /* - * If we failed to find the CRTC(s) which this encoder is - * supposed to be connected to, it's because the CRTC has - * not been registered yet. Defer probing, and hope that - * the required CRTC is added later. - */ - if (encoder->possible_crtcs == 0) - return -EPROBE_DEFER; - - drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); - - hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; - - drm_connector_helper_add(&hdmi->connector, - &inno_hdmi_connector_helper_funcs); - drmm_connector_hdmi_init(drm, &hdmi->connector, - "Rockchip", "Inno HDMI", - &inno_hdmi_connector_funcs, - &inno_hdmi_hdmi_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA, - hdmi->ddc, - BIT(HDMI_COLORSPACE_RGB), - 8); - - drm_connector_attach_encoder(&hdmi->connector, encoder); - - return 0; -} - -static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi) -{ - struct inno_hdmi_i2c *i2c = hdmi->i2c; - u8 stat; - - stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1); - if (!(stat & m_INT_EDID_READY)) - return IRQ_NONE; - - /* Clear HDMI EDID interrupt flag */ - hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); - - complete(&i2c->cmp); - - return IRQ_HANDLED; -} - -static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id) -{ - struct inno_hdmi *hdmi = dev_id; - irqreturn_t ret = IRQ_NONE; - u8 interrupt; - - if (hdmi->i2c) - ret = inno_hdmi_i2c_irq(hdmi); - - interrupt = hdmi_readb(hdmi, HDMI_STATUS); - if (interrupt & m_INT_HOTPLUG) { - hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG); - ret = IRQ_WAKE_THREAD; - } - - return ret; -} - -static irqreturn_t inno_hdmi_irq(int irq, void *dev_id) -{ - struct inno_hdmi *hdmi = dev_id; - - drm_helper_hpd_irq_event(hdmi->connector.dev); - - return IRQ_HANDLED; -} - -static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs) -{ - int length = msgs->len; - u8 *buf = msgs->buf; - int ret; - - ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10); - if (!ret) - return -EAGAIN; - - while (length--) - *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR); - - return 0; -} - -static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs) -{ - /* - * The DDC module only support read EDID message, so - * we assume that each word write to this i2c adapter - * should be the offset of EDID word address. - */ - if ((msgs->len != 1) || - ((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR))) - return -EINVAL; - - reinit_completion(&hdmi->i2c->cmp); - - if (msgs->addr == DDC_SEGMENT_ADDR) - hdmi->i2c->segment_addr = msgs->buf[0]; - if (msgs->addr == DDC_ADDR) - hdmi->i2c->ddc_addr = msgs->buf[0]; - - /* Set edid fifo first addr */ - hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00); - - /* Set edid word address 0x00/0x80 */ - hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr); - - /* Set edid segment pointer */ - hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr); - - return 0; -} - -static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap, - struct i2c_msg *msgs, int num) -{ - struct inno_hdmi *hdmi = i2c_get_adapdata(adap); - struct inno_hdmi_i2c *i2c = hdmi->i2c; - int i, ret = 0; - - mutex_lock(&i2c->lock); - - /* Clear the EDID interrupt flag and unmute the interrupt */ - hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY); - hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); - - for (i = 0; i < num; i++) { - DRM_DEV_DEBUG(hdmi->dev, - "xfer: num: %d/%d, len: %d, flags: %#x\n", - i + 1, num, msgs[i].len, msgs[i].flags); - - if (msgs[i].flags & I2C_M_RD) - ret = inno_hdmi_i2c_read(hdmi, &msgs[i]); - else - ret = inno_hdmi_i2c_write(hdmi, &msgs[i]); - - if (ret < 0) - break; - } - - if (!ret) - ret = num; - - /* Mute HDMI EDID interrupt */ - hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); - - mutex_unlock(&i2c->lock); - - return ret; -} - -static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} - -static const struct i2c_algorithm inno_hdmi_algorithm = { - .master_xfer = inno_hdmi_i2c_xfer, - .functionality = inno_hdmi_i2c_func, -}; - -static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi) -{ - struct i2c_adapter *adap; - struct inno_hdmi_i2c *i2c; - int ret; - - i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); - if (!i2c) - return ERR_PTR(-ENOMEM); - - mutex_init(&i2c->lock); - init_completion(&i2c->cmp); - - adap = &i2c->adap; - adap->owner = THIS_MODULE; - adap->dev.parent = hdmi->dev; - adap->dev.of_node = hdmi->dev->of_node; - adap->algo = &inno_hdmi_algorithm; - strscpy(adap->name, "Inno HDMI", sizeof(adap->name)); - i2c_set_adapdata(adap, hdmi); - - ret = i2c_add_adapter(adap); - if (ret) { - dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); - devm_kfree(hdmi->dev, i2c); - return ERR_PTR(ret); - } - - hdmi->i2c = i2c; - - DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n", adap->name); - - return adap; -} - -static int inno_hdmi_bind(struct device *dev, struct device *master, - void *data) -{ - struct platform_device *pdev = to_platform_device(dev); - struct drm_device *drm = data; - struct inno_hdmi *hdmi; - const struct inno_hdmi_variant *variant; - int irq; - int ret; - - hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); - if (!hdmi) - return -ENOMEM; - - hdmi->dev = dev; - - variant = of_device_get_match_data(hdmi->dev); - if (!variant) - return -EINVAL; - - hdmi->variant = variant; - - hdmi->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(hdmi->regs)) - return PTR_ERR(hdmi->regs); - - hdmi->pclk = devm_clk_get(hdmi->dev, "pclk"); - if (IS_ERR(hdmi->pclk)) { - DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI pclk clk\n"); - return PTR_ERR(hdmi->pclk); - } - - ret = clk_prepare_enable(hdmi->pclk); - if (ret) { - DRM_DEV_ERROR(hdmi->dev, - "Cannot enable HDMI pclk clock: %d\n", ret); - return ret; - } - - hdmi->refclk = devm_clk_get_optional(hdmi->dev, "ref"); - if (IS_ERR(hdmi->refclk)) { - DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI reference clock\n"); - ret = PTR_ERR(hdmi->refclk); - goto err_disable_pclk; - } - - ret = clk_prepare_enable(hdmi->refclk); - if (ret) { - DRM_DEV_ERROR(hdmi->dev, - "Cannot enable HDMI reference clock: %d\n", ret); - goto err_disable_pclk; - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - ret = irq; - goto err_disable_clk; - } - - inno_hdmi_reset(hdmi); - - hdmi->ddc = inno_hdmi_i2c_adapter(hdmi); - if (IS_ERR(hdmi->ddc)) { - ret = PTR_ERR(hdmi->ddc); - hdmi->ddc = NULL; - goto err_disable_clk; - } - - /* - * When the controller isn't configured to an accurate - * video timing and there is no reference clock available, - * then the TMDS clock source would be switched to PCLK_HDMI, - * so we need to init the TMDS rate to PCLK rate, and - * reconfigure the DDC clock. - */ - if (hdmi->refclk) - inno_hdmi_i2c_init(hdmi, clk_get_rate(hdmi->refclk)); - else - inno_hdmi_i2c_init(hdmi, clk_get_rate(hdmi->pclk)); - - ret = inno_hdmi_register(drm, hdmi); - if (ret) - goto err_put_adapter; - - dev_set_drvdata(dev, hdmi); - - /* Unmute hotplug interrupt */ - hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1)); - - ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq, - inno_hdmi_irq, IRQF_SHARED, - dev_name(dev), hdmi); - if (ret < 0) - goto err_cleanup_hdmi; - - return 0; -err_cleanup_hdmi: - hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); -err_put_adapter: - i2c_put_adapter(hdmi->ddc); -err_disable_clk: - clk_disable_unprepare(hdmi->refclk); -err_disable_pclk: - clk_disable_unprepare(hdmi->pclk); - return ret; -} - -static void inno_hdmi_unbind(struct device *dev, struct device *master, - void *data) -{ - struct inno_hdmi *hdmi = dev_get_drvdata(dev); - - hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); - - i2c_put_adapter(hdmi->ddc); - clk_disable_unprepare(hdmi->refclk); - clk_disable_unprepare(hdmi->pclk); -} - -static const struct component_ops inno_hdmi_ops = { - .bind = inno_hdmi_bind, - .unbind = inno_hdmi_unbind, -}; - -static int inno_hdmi_probe(struct platform_device *pdev) -{ - return component_add(&pdev->dev, &inno_hdmi_ops); -} - -static void inno_hdmi_remove(struct platform_device *pdev) -{ - component_del(&pdev->dev, &inno_hdmi_ops); -} - -static const struct inno_hdmi_variant rk3036_inno_hdmi_variant = { - .phy_configs = rk3036_hdmi_phy_configs, - .default_phy_config = &rk3036_hdmi_phy_configs[1], -}; - -static const struct inno_hdmi_variant rk3128_inno_hdmi_variant = { - .phy_configs = rk3128_hdmi_phy_configs, - .default_phy_config = &rk3128_hdmi_phy_configs[1], -}; - -static const struct of_device_id inno_hdmi_dt_ids[] = { - { .compatible = "rockchip,rk3036-inno-hdmi", - .data = &rk3036_inno_hdmi_variant, - }, - { .compatible = "rockchip,rk3128-inno-hdmi", - .data = &rk3128_inno_hdmi_variant, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids); - -struct platform_driver inno_hdmi_driver = { - .probe = inno_hdmi_probe, - .remove_new = inno_hdmi_remove, - .driver = { - .name = "innohdmi-rockchip", - .of_match_table = inno_hdmi_dt_ids, - }, -}; diff --git a/include/drm/bridge/inno_hdmi.h b/include/drm/bridge/inno_hdmi.h new file mode 100644 index 000000000000..bfd22f180153 --- /dev/null +++ b/include/drm/bridge/inno_hdmi.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) StarFive Technology Co., Ltd. + */ + +#ifndef __INNO_COMMON_HDMI__ +#define __INNO_COMMON_HDMI__ + +#include +#include +#include +#include + +struct inno_hdmi; + +struct inno_hdmi_i2c { + u8 ddc_addr; + u8 segment_addr; + + struct i2c_adapter adap; + struct mutex lock; /* For i2c operation. */ + struct completion cmp; +}; + +struct inno_hdmi_plat_data { + const char *vendor; + const char *product; + + /* Platform-specific mode validation*/ + enum drm_mode_status (*mode_valid)(struct inno_hdmi *hdmi, + const struct drm_display_mode *mode); + int (*hdmi_clear_infoframe)(struct inno_hdmi *hdmi, enum hdmi_infoframe_type type); + int (*hdmi_write_infoframe)(struct inno_hdmi *hdmi, enum hdmi_infoframe_type type, + const u8 *buffer, size_t len); +}; + +struct inno_hdmi { + struct device *dev; + void __iomem *regs; + + struct i2c_adapter *ddc; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct inno_hdmi_i2c *i2c; + + const struct inno_hdmi_plat_data *plat_data; +}; + +u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset); +void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val); +void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, u32 msk, u32 val); + +void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate); +int inno_hdmi_bind(struct drm_device *drm, struct inno_hdmi *hdmi, struct drm_encoder *encoder); + +#endif /* __INNO_COMMON_HDMI__ */ diff --git a/MAINTAINERS b/MAINTAINERS index 7766ee0bdd74..55d6bccdd036 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7440,6 +7440,7 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/bridge/innosilicon,inno-hdmi.yaml F: Documentation/devicetree/bindings/display/starfive/ F: drivers/gpu/drm/bridge/innosilicon/ +F: drivers/gpu/drm/verisilicon F: include/drm/bridge/inno_hdmi.h DRM DRIVER FOR SYNAPTICS R63353 PANELS diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1df4e627e3d3..0abd0ecbe1e5 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -481,6 +481,8 @@ source "drivers/gpu/drm/sprd/Kconfig" source "drivers/gpu/drm/imagination/Kconfig" +source "drivers/gpu/drm/verisilicon/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && MMU && HYPERV diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 3894f43f6d47..9450b90d5df1 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -223,3 +223,4 @@ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ obj-$(CONFIG_DRM_LOONGSON) += loongson/ obj-$(CONFIG_DRM_POWERVR) += imagination/ +obj-$(CONFIG_DRM_VERISILICON_DC8200) += verisilicon/ diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig new file mode 100644 index 000000000000..874e8bcde5d5 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +config DRM_VERISILICON_DC8200 + tristate "DRM Support for VeriSilicon DC8200" + depends on DRM + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select DMA_CMA if HAVE_DMA_CONTIGUOUS + select CMA if HAVE_DMA_CONTIGUOUS + help + Choose this option if you have a VeriSilicon DC8200 chipset. + This driver provides VeriSilicon kernel mode + setting and buffer management. It does not + provide 2D or 3D acceleration. diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile new file mode 100644 index 000000000000..7da54b259940 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +vs_drm-objs := vs_dc_hw.o + +obj-$(CONFIG_DRM_VERISILICON_DC8200) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/vs_dc_hw.c b/drivers/gpu/drm/verisilicon/vs_dc_hw.c new file mode 100644 index 000000000000..44c216fdf78d --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc_hw.c @@ -0,0 +1,1104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include "vs_dc_hw.h" + +static const u32 horkernel[] = { + 0x00000000, 0x20000000, 0x00002000, 0x00000000, + 0x00000000, 0x00000000, 0x23fd1c03, 0x00000000, + 0x00000000, 0x00000000, 0x181f0000, 0x000027e1, + 0x00000000, 0x00000000, 0x00000000, 0x2b981468, + 0x00000000, 0x00000000, 0x00000000, 0x10f00000, + 0x00002f10, 0x00000000, 0x00000000, 0x00000000, + 0x32390dc7, 0x00000000, 0x00000000, 0x00000000, + 0x0af50000, 0x0000350b, 0x00000000, 0x00000000, + 0x00000000, 0x3781087f, 0x00000000, 0x00000000, + 0x00000000, 0x06660000, 0x0000399a, 0x00000000, + 0x00000000, 0x00000000, 0x3b5904a7, 0x00000000, + 0x00000000, 0x00000000, 0x033c0000, 0x00003cc4, + 0x00000000, 0x00000000, 0x00000000, 0x3de1021f, + 0x00000000, 0x00000000, 0x00000000, 0x01470000, + 0x00003eb9, 0x00000000, 0x00000000, 0x00000000, + 0x3f5300ad, 0x00000000, 0x00000000, 0x00000000, + 0x00480000, 0x00003fb8, 0x00000000, 0x00000000, + 0x00000000, 0x3fef0011, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00004000, 0x00000000, + 0x00000000, 0x00000000, 0x20002000, 0x00000000, + 0x00000000, 0x00000000, 0x1c030000, 0x000023fd, + 0x00000000, 0x00000000, 0x00000000, 0x27e1181f, + 0x00000000, 0x00000000, 0x00000000, 0x14680000, + 0x00002b98, 0x00000000, 0x00000000, 0x00000000, + 0x2f1010f0, 0x00000000, 0x00000000, 0x00000000, + 0x0dc70000, 0x00003239, 0x00000000, 0x00000000, + 0x00000000, 0x350b0af5, 0x00000000, 0x00000000, + 0x00000000, 0x087f0000, 0x00003781, 0x00000000, + 0x00000000, 0x00000000, 0x399a0666, 0x00000000, + 0x00000000, 0x00000000, 0x04a70000, 0x00003b59, + 0x00000000, 0x00000000, 0x00000000, 0x3cc4033c, + 0x00000000, 0x00000000, 0x00000000, 0x021f0000, +}; + +#define H_COEF_SIZE ARRAY_SIZE(horkernel) + +static const u32 verkernel[] = { + 0x00000000, 0x20000000, 0x00002000, 0x00000000, + 0x00000000, 0x00000000, 0x23fd1c03, 0x00000000, + 0x00000000, 0x00000000, 0x181f0000, 0x000027e1, + 0x00000000, 0x00000000, 0x00000000, 0x2b981468, + 0x00000000, 0x00000000, 0x00000000, 0x10f00000, + 0x00002f10, 0x00000000, 0x00000000, 0x00000000, + 0x32390dc7, 0x00000000, 0x00000000, 0x00000000, + 0x0af50000, 0x0000350b, 0x00000000, 0x00000000, + 0x00000000, 0x3781087f, 0x00000000, 0x00000000, + 0x00000000, 0x06660000, 0x0000399a, 0x00000000, + 0x00000000, 0x00000000, 0x3b5904a7, 0x00000000, + 0x00000000, 0x00000000, 0x033c0000, 0x00003cc4, + 0x00000000, 0x00000000, 0x00000000, 0x3de1021f, + 0x00000000, 0x00000000, 0x00000000, 0x01470000, + 0x00003eb9, 0x00000000, 0x00000000, 0x00000000, + 0x3f5300ad, 0x00000000, 0x00000000, 0x00000000, + 0x00480000, 0x00003fb8, 0x00000000, 0x00000000, + 0x00000000, 0x3fef0011, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00004000, 0x00000000, + 0xcdcd0000, 0xfdfdfdfd, 0xabababab, 0xabababab, + 0x00000000, 0x00000000, 0x5ff5f456, 0x000f5f58, + 0x02cc6c78, 0x02cc0c28, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, + 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, +}; + +#define V_COEF_SIZE ARRAY_SIZE(verkernel) + +/* + * RGB 709->2020 conversion parameters + */ +static const u16 RGB2RGB[RGB_TO_RGB_TABLE_SIZE] = { + 10279, 5395, 709, + 1132, 15065, 187, + 269, 1442, 14674 +}; + +/* + * YUV601 to RGB conversion parameters + * YUV2RGB[0] - [8] : C0 - C8; + * YUV2RGB[9] - [11]: D0 - D2; + * YUV2RGB[12] - [13]: Y clamp min & max calue; + * YUV2RGB[14] - [15]: UV clamp min & max calue; + */ +static const s32 YUV601_2RGB[YUV_TO_RGB_TABLE_SIZE] = { + 1196, 0, 1640, 1196, + -404, -836, 1196, 2076, + 0, -916224, 558336, -1202944, + 64, 940, 64, 960 +}; + +/* + * YUV709 to RGB conversion parameters + * YUV2RGB[0] - [8] : C0 - C8; + * YUV2RGB[9] - [11]: D0 - D2; + * YUV2RGB[12] - [13]: Y clamp min & max calue; + * YUV2RGB[14] - [15]: UV clamp min & max calue; + */ +static s32 YUV709_2RGB[YUV_TO_RGB_TABLE_SIZE] = { + 1196, 0, 1844, 1196, + -220, -548, 1196, 2172, + 0, -1020672, 316672, -1188608, + 64, 940, 64, 960 +}; + +/* + * YUV2020 to RGB conversion parameters + * YUV2RGB[0] - [8] : C0 - C8; + * YUV2RGB[9] - [11]: D0 - D2; + * YUV2RGB[12] - [13]: Y clamp min & max calue; + * YUV2RGB[14] - [15]: UV clamp min & max calue; + */ +static s32 YUV2020_2RGB[YUV_TO_RGB_TABLE_SIZE] = { + 1196, 0, 1724, 1196, + -192, -668, 1196, 2200, + 0, -959232, 363776, -1202944, + 64, 940, 64, 960 +}; + +/* + * RGB to YUV2020 conversion parameters + * RGB2YUV[0] - [8] : C0 - C8; + * RGB2YUV[9] - [11]: D0 - D2; + */ +static s16 RGB2YUV[RGB_TO_YUV_TABLE_SIZE] = { + 230, 594, 52, + -125, -323, 448, + 448, -412, -36, + 64, 512, 512 +}; + +/* one is for primary plane and the other is for all overlay planes */ +static const struct dc_hw_plane_reg dc_plane_reg[] = { + { + .y_address = DC_FRAMEBUFFER_ADDRESS, + .u_address = DC_FRAMEBUFFER_U_ADDRESS, + .v_address = DC_FRAMEBUFFER_V_ADDRESS, + .y_stride = DC_FRAMEBUFFER_STRIDE, + .u_stride = DC_FRAMEBUFFER_U_STRIDE, + .v_stride = DC_FRAMEBUFFER_V_STRIDE, + .size = DC_FRAMEBUFFER_SIZE, + .top_left = DC_FRAMEBUFFER_TOP_LEFT, + .bottom_right = DC_FRAMEBUFFER_BOTTOM_RIGHT, + .scale_factor_x = DC_FRAMEBUFFER_SCALE_FACTOR_X, + .scale_factor_y = DC_FRAMEBUFFER_SCALE_FACTOR_Y, + .h_filter_coef_index = DC_FRAMEBUFFER_H_FILTER_COEF_INDEX, + .h_filter_coef_data = DC_FRAMEBUFFER_H_FILTER_COEF_DATA, + .v_filter_coef_index = DC_FRAMEBUFFER_V_FILTER_COEF_INDEX, + .v_filter_coef_data = DC_FRAMEBUFFER_V_FILTER_COEF_DATA, + .init_offset = DC_FRAMEBUFFER_INIT_OFFSET, + .color_key = DC_FRAMEBUFFER_COLOR_KEY, + .color_key_high = DC_FRAMEBUFFER_COLOR_KEY_HIGH, + .clear_value = DC_FRAMEBUFFER_CLEAR_VALUE, + .color_table_index = DC_FRAMEBUFFER_COLOR_TABLE_INDEX, + .color_table_data = DC_FRAMEBUFFER_COLOR_TABLE_DATA, + .scale_config = DC_FRAMEBUFFER_SCALE_CONFIG, + .water_mark = DC_FRAMEBUFFER_WATER_MARK, + .degamma_index = DC_FRAMEBUFFER_DEGAMMA_INDEX, + .degamma_data = DC_FRAMEBUFFER_DEGAMMA_DATA, + .degamma_ex_data = DC_FRAMEBUFFER_DEGAMMA_EX_DATA, + .src_global_color = DC_FRAMEBUFFER_SRC_GLOBAL_COLOR, + .dst_global_color = DC_FRAMEBUFFER_DST_GLOBAL_COLOR, + .blend_config = DC_FRAMEBUFFER_BLEND_CONFIG, + .roi_origin = DC_FRAMEBUFFER_ROI_ORIGIN, + .roi_size = DC_FRAMEBUFFER_ROI_SIZE, + .yuv_to_rgb_coef0 = DC_FRAMEBUFFER_YUVTORGB_COEF0, + .yuv_to_rgb_coef1 = DC_FRAMEBUFFER_YUVTORGB_COEF1, + .yuv_to_rgb_coef2 = DC_FRAMEBUFFER_YUVTORGB_COEF2, + .yuv_to_rgb_coef3 = DC_FRAMEBUFFER_YUVTORGB_COEF3, + .yuv_to_rgb_coef4 = DC_FRAMEBUFFER_YUVTORGB_COEF4, + .yuv_to_rgb_coefd0 = DC_FRAMEBUFFER_YUVTORGB_COEFD0, + .yuv_to_rgb_coefd1 = DC_FRAMEBUFFER_YUVTORGB_COEFD1, + .yuv_to_rgb_coefd2 = DC_FRAMEBUFFER_YUVTORGB_COEFD2, + .y_clamp_bound = DC_FRAMEBUFFER_Y_CLAMP_BOUND, + .uv_clamp_bound = DC_FRAMEBUFFER_UV_CLAMP_BOUND, + .rgb_to_rgb_coef0 = DC_FRAMEBUFFER_RGBTORGB_COEF0, + .rgb_to_rgb_coef1 = DC_FRAMEBUFFER_RGBTORGB_COEF1, + .rgb_to_rgb_coef2 = DC_FRAMEBUFFER_RGBTORGB_COEF2, + .rgb_to_rgb_coef3 = DC_FRAMEBUFFER_RGBTORGB_COEF3, + .rgb_to_rgb_coef4 = DC_FRAMEBUFFER_RGBTORGB_COEF4, + }, + { + .y_address = DC_OVERLAY_ADDRESS, + .u_address = DC_OVERLAY_U_ADDRESS, + .v_address = DC_OVERLAY_V_ADDRESS, + .y_stride = DC_OVERLAY_STRIDE, + .u_stride = DC_OVERLAY_U_STRIDE, + .v_stride = DC_OVERLAY_V_STRIDE, + .size = DC_OVERLAY_SIZE, + .top_left = DC_OVERLAY_TOP_LEFT, + .bottom_right = DC_OVERLAY_BOTTOM_RIGHT, + .scale_factor_x = DC_OVERLAY_SCALE_FACTOR_X, + .scale_factor_y = DC_OVERLAY_SCALE_FACTOR_Y, + .h_filter_coef_index = DC_OVERLAY_H_FILTER_COEF_INDEX, + .h_filter_coef_data = DC_OVERLAY_H_FILTER_COEF_DATA, + .v_filter_coef_index = DC_OVERLAY_V_FILTER_COEF_INDEX, + .v_filter_coef_data = DC_OVERLAY_V_FILTER_COEF_DATA, + .init_offset = DC_OVERLAY_INIT_OFFSET, + .color_key = DC_OVERLAY_COLOR_KEY, + .color_key_high = DC_OVERLAY_COLOR_KEY_HIGH, + .clear_value = DC_OVERLAY_CLEAR_VALUE, + .color_table_index = DC_OVERLAY_COLOR_TABLE_INDEX, + .color_table_data = DC_OVERLAY_COLOR_TABLE_DATA, + .scale_config = DC_OVERLAY_SCALE_CONFIG, + .water_mark = DC_OVERLAY_WATER_MARK, + .degamma_index = DC_OVERLAY_DEGAMMA_INDEX, + .degamma_data = DC_OVERLAY_DEGAMMA_DATA, + .degamma_ex_data = DC_OVERLAY_DEGAMMA_EX_DATA, + .src_global_color = DC_OVERLAY_SRC_GLOBAL_COLOR, + .dst_global_color = DC_OVERLAY_DST_GLOBAL_COLOR, + .blend_config = DC_OVERLAY_BLEND_CONFIG, + .roi_origin = DC_OVERLAY_ROI_ORIGIN, + .roi_size = DC_OVERLAY_ROI_SIZE, + .yuv_to_rgb_coef0 = DC_OVERLAY_YUVTORGB_COEF0, + .yuv_to_rgb_coef1 = DC_OVERLAY_YUVTORGB_COEF1, + .yuv_to_rgb_coef2 = DC_OVERLAY_YUVTORGB_COEF2, + .yuv_to_rgb_coef3 = DC_OVERLAY_YUVTORGB_COEF3, + .yuv_to_rgb_coef4 = DC_OVERLAY_YUVTORGB_COEF4, + .yuv_to_rgb_coefd0 = DC_OVERLAY_YUVTORGB_COEFD0, + .yuv_to_rgb_coefd1 = DC_OVERLAY_YUVTORGB_COEFD1, + .yuv_to_rgb_coefd2 = DC_OVERLAY_YUVTORGB_COEFD2, + .y_clamp_bound = DC_OVERLAY_Y_CLAMP_BOUND, + .uv_clamp_bound = DC_OVERLAY_UV_CLAMP_BOUND, + .rgb_to_rgb_coef0 = DC_OVERLAY_RGBTORGB_COEF0, + .rgb_to_rgb_coef1 = DC_OVERLAY_RGBTORGB_COEF1, + .rgb_to_rgb_coef2 = DC_OVERLAY_RGBTORGB_COEF2, + .rgb_to_rgb_coef3 = DC_OVERLAY_RGBTORGB_COEF3, + .rgb_to_rgb_coef4 = DC_OVERLAY_RGBTORGB_COEF4, + }, +}; + +static inline u32 hi_read(struct dc_hw *hw, u32 reg) +{ + return readl(hw->hi_base + reg); +} + +static inline void hi_write(struct dc_hw *hw, u32 reg, u32 value) +{ + writel(value, hw->hi_base + reg); +} + +static inline void dc_write(struct dc_hw *hw, u32 reg, u32 value) +{ + writel(value, hw->reg_base + reg - DC_REG_BASE); +} + +static inline u32 dc_read(struct dc_hw *hw, u32 reg) +{ + return readl(hw->reg_base + reg - DC_REG_BASE); +} + +static inline void dc_write_mask(struct dc_hw *hw, u32 reg, + u32 val, u32 mask) +{ + dc_write(hw, reg, (dc_read(hw, reg) & ~mask) | (val & mask)); +} + +static inline void dc_set_bit(struct dc_hw *hw, u32 reg, u32 mask) +{ + dc_write(hw, reg, dc_read(hw, reg) | mask); +} + +static inline void dc_clear_bit(struct dc_hw *hw, u32 reg, u32 mask) +{ + dc_write(hw, reg, dc_read(hw, reg) & ~mask); +} + +static void dc_load_plane_default_filter(struct dc_hw *hw, + const struct dc_hw_plane_reg *reg, u32 offset) +{ + u8 i; + + dc_write(hw, reg->scale_config + offset, 0x33); + dc_write(hw, reg->init_offset + offset, 0x80008000); + dc_write(hw, reg->h_filter_coef_index + offset, 0x00); + for (i = 0; i < H_COEF_SIZE; i++) + dc_write(hw, reg->h_filter_coef_data + offset, horkernel[i]); + + dc_write(hw, reg->v_filter_coef_index + offset, 0x00); + for (i = 0; i < V_COEF_SIZE; i++) + dc_write(hw, reg->v_filter_coef_data + offset, verkernel[i]); +} + +static void dc_load_csc_common(struct dc_hw *hw, const u32 *coef_reg, + u32 *regval, u32 offset, u16 len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + dc_write(hw, coef_reg[i] + offset, regval[i]); +} + +/** + *dc_load_plane_rgb_csc- Load RGB to RGB conversion coefficient + * + *This function loads the RGB to RGB conversion coefficients into hardware registers. + * + *@hw: Hardware Context + *@reg: register address structure + *@offset: offset + *@table: contains an array of conversion coefficients + */ +static void dc_load_plane_rgb_csc(struct dc_hw *hw, const struct dc_hw_plane_reg *reg, + u32 offset, const u16 *table) +{ + u32 coef_reg[] = { + reg->rgb_to_rgb_coef0, reg->rgb_to_rgb_coef1, + reg->rgb_to_rgb_coef2, reg->rgb_to_rgb_coef3, + reg->rgb_to_rgb_coef4, + }; + u32 regval[ARRAY_SIZE(coef_reg)] = { + table[0] | (table[1] << 16), + table[2] | (table[3] << 16), + table[4] | (table[5] << 16), + table[6] | (table[7] << 16), + table[8], + }; + + dc_load_csc_common(hw, coef_reg, regval, offset, ARRAY_SIZE(coef_reg)); +} + +/** + * dc_load_plane_yuv_to_rgb_csc - Load YUV to RGB conversion coefficients into hardware registers + * @hw: Pointer to the hardware structure + * @reg: Pointer to the hardware plane register structure + * @offset: Offset value for the coefficients + * @table: Pointer to the table containing the coefficients + * + * This function loads the YUV to RGB conversion coefficients and clamping bounds + * from the provided table into the hardware registers. The coefficients are used + * for color space conversion during video processing. + */ +static void dc_load_plane_yuv_to_rgb_csc(struct dc_hw *hw, const struct dc_hw_plane_reg *reg, + u32 offset, const s32 *table) +{ + u32 coef_reg[] = { + reg->yuv_to_rgb_coef0, reg->yuv_to_rgb_coef1, + reg->yuv_to_rgb_coef2, reg->yuv_to_rgb_coef3, + reg->yuv_to_rgb_coef4, reg->yuv_to_rgb_coefd0, + reg->yuv_to_rgb_coefd1, reg->yuv_to_rgb_coefd2, + reg->y_clamp_bound, reg->uv_clamp_bound, + }; + + u32 regval[ARRAY_SIZE(coef_reg)] = { + (0xFFFF & table[0]) | (table[1] << 16),//Lower 16 of tab[0] and upper 16 of tab[1] + (0xFFFF & table[2]) | (table[3] << 16),//Lower 16 of tab[2] and upper 16 of tab[3] + (0xFFFF & table[4]) | (table[5] << 16),//Lower 16 of tab[4] and upper 16 of tab[5] + (0xFFFF & table[6]) | (table[7] << 16),//Lower 16 of tab[6] and upper 16 of tab[7] + table[8], //Direct value from tab[8] + table[9], //Direct value from tab[9] + table[10], //Direct value from tab[10] + table[11], //Direct value from tab[11] + table[12] | (table[13] << 16), //Lower 16 of tab[12] and upper 16 of tab[13] + table[14] | (table[15] << 16), //Lower 16 of tab[14] and upper 16 of tab[15] + }; + + // Load the coefficients into the hardware registers + dc_load_csc_common(hw, coef_reg, regval, offset, ARRAY_SIZE(coef_reg)); +} + +/** + * dc_load_crtc_rgb_to_yuv_csc - Crtc load RGB to YUV csc into hardware registers + * @hw: Pointer to the hardware structure + * @offset: Offset value for the coefficients + * @table: Pointer to the table containing the coefficients + * + * This function loads the RGB to YUV conversion coefficients from the provided table + * into the hardware registers. The coefficients are used for crtc color space + * conversion during video processing. + */ +static void dc_load_crtc_rgb_to_yuv_csc(struct dc_hw *hw, u32 offset, s16 *table) +{ + u32 coef_reg[] = { + DC_DISPLAY_RGBTOYUV_COEF0, DC_DISPLAY_RGBTOYUV_COEF1, + DC_DISPLAY_RGBTOYUV_COEF2, DC_DISPLAY_RGBTOYUV_COEF3, + DC_DISPLAY_RGBTOYUV_COEF4, DC_DISPLAY_RGBTOYUV_COEFD0, + DC_DISPLAY_RGBTOYUV_COEFD1, DC_DISPLAY_RGBTOYUV_COEFD2, + }; + + u32 regval[ARRAY_SIZE(coef_reg)] = { + table[0] | (table[1] << 16),// Lower 16 of table[0] and upper 16 of table[1] + table[2] | (table[3] << 16),// Lower 16 of table[2] and upper 16 of table[3] + table[4] | (table[5] << 16),// Lower 16 of table[4] and upper 16 of table[5] + table[6] | (table[7] << 16),// Lower 16 of table[6] and upper 16 of table[7] + table[8], // Direct value from table[8] + table[9], // Direct value from table[9] + table[10], // Direct value from table[10] + table[11], // Direct value from table[11] + }; + + // Load the coefficients into the hardware registers + dc_load_csc_common(hw, coef_reg, regval, offset, ARRAY_SIZE(coef_reg)); +} + +static int dc_update_vs_format(u32 drm_format) +{ + switch (drm_format) { + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_BGRX4444: + return FORMAT_X4R4G4B4; + + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_BGRA4444: + return FORMAT_A4R4G4B4; + + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_BGRX5551: + return FORMAT_X1R5G5B5; + + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGRA5551: + return FORMAT_A1R5G5B5; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return FORMAT_R5G6B5; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_BGRX8888: + return FORMAT_X8R8G8B8; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGRA8888: + return FORMAT_A8R8G8B8; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + return FORMAT_YUY2; + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + return FORMAT_UYVY; + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + return FORMAT_YV12; + case DRM_FORMAT_NV21: + return FORMAT_NV12; + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + return FORMAT_NV16; + case DRM_FORMAT_P010: + return FORMAT_P010; + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_BGRA1010102: + return FORMAT_A2R10G10B10; + case DRM_FORMAT_NV12: + return FORMAT_NV12; + case DRM_FORMAT_YUV444: + return FORMAT_YUV444; + default: + return FORMAT_A8R8G8B8; + } +} + +int dc_hw_init(struct vs_dc *dc) +{ + u8 i, id, panel_num, layer_num; + struct dc_hw *hw = &dc->hw; + u32 offset; + + layer_num = hw->info->layer_num; + for (i = 0; i < layer_num; i++) { + id = dc->planes[i].id; + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) + hw->reg[i] = dc_plane_reg[0]; + else + hw->reg[i] = dc_plane_reg[1]; + + dc_load_plane_default_filter(hw, &hw->reg[i], dc->planes[i].offset); + dc_load_plane_rgb_csc(hw, &hw->reg[i], dc->planes[i].offset, RGB2RGB); + } + + panel_num = hw->info->panel_num; + for (i = 0; i < panel_num; i++) { + offset = i << 2; + + dc_load_crtc_rgb_to_yuv_csc(hw, offset, RGB2YUV); + dc_write(hw, DC_DISPLAY_PANEL_CONFIG + offset, PANEL_DE_EN | + PANEL_DATA_EN | PANEL_CLOCK_EN); + + offset = i ? DC_CURSOR_OFFSET : 0; + dc_write(hw, DC_CURSOR_BACKGROUND + offset, 0x00FFFFFF); + dc_write(hw, DC_CURSOR_FOREGROUND + offset, 0x00AAAAAA); + } + + return 0; +} + +void dc_hw_disable_plane(struct vs_dc *dc, u8 id) +{ + struct dc_hw *hw = &dc->hw; + + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) + dc_write_mask(hw, DC_FRAMEBUFFER_CONFIG_EX + dc->planes[id].offset, + PRIMARY_EN(false), PRIMARY_EN_MASK); + else + dc_write_mask(hw, DC_OVERLAY_CONFIG + dc->planes[id].offset, + OVERLAY_FB_EN(false), OVERLAY_FB_EN_MASK); +} + +static int dc_get_cursor_size(uint32_t crtc_w) +{ + switch (crtc_w) { + case 32: + return CURSOR_SIZE_32X32; + + case 64: + return CURSOR_SIZE_64X64; + + default: + return CURSOR_SIZE_32X32; + } +} + +void dc_hw_update_cursor(struct dc_hw *hw, u8 id, dma_addr_t dma_addr, + u32 crtc_w, u32 crtc_x, u32 crtc_y, + s32 hotspot_x, s32 hotspot_y) +{ + u32 offset, size; + + offset = id ? DC_CURSOR_OFFSET : 0; + size = dc_get_cursor_size(crtc_w); + + dc_write(hw, DC_CURSOR_ADDRESS + offset, dma_addr); + dc_write(hw, DC_CURSOR_LOCATION + offset, X_LCOTION(crtc_x) | Y_LCOTION(crtc_y)); + dc_write_mask(hw, DC_CURSOR_CONFIG + offset, + CURSOR_HOT_X(hotspot_x) | + CURSOR_HOT_y(hotspot_y) | + CURSOR_SIZE(size) | + CURSOR_VALID(1) | + CURSOR_TRIG_FETCH(1) | + CURSOR_FORMAT(CURSOR_FORMAT_A8R8G8B8), + CURSOR_HOT_X_MASK | + CURSOR_HOT_y_MASK | + CURSOR_SIZE_MASK | + CURSOR_VALID_MASK | + CURSOR_TRIG_FETCH_MASK | + CURSOR_FORMAT_MASK); +} + +void dc_hw_disable_cursor(struct dc_hw *hw, u8 id) +{ + u32 offset = 0; + + offset = id ? DC_CURSOR_OFFSET : 0; + dc_clear_bit(hw, DC_CURSOR_CONFIG + offset, CURSOR_FORMAT_MASK); + dc_set_bit(hw, DC_CURSOR_CONFIG + offset, CURSOR_VALID(1)); +} + +void dc_hw_update_gamma(struct dc_hw *hw, u8 id, u16 index, + u16 r, u16 g, u16 b) +{ + if (index >= hw->info->gamma_size) + return; + + hw->gamma[id].gamma[index][0] = r; + hw->gamma[id].gamma[index][1] = g; + hw->gamma[id].gamma[index][2] = b; +} + +void dc_hw_enable_gamma(struct dc_hw *hw, u8 id, bool enable) +{ + u32 value; + + if (enable) { + dc_write(hw, DC_DISPLAY_GAMMA_EX_INDEX + (id << 2), 0x00); + for (int i = 0; i < GAMMA_EX_SIZE; i++) { + value = hw->gamma[id].gamma[i][2] | + (hw->gamma[id].gamma[i][1] << 12); + dc_write(hw, DC_DISPLAY_GAMMA_EX_DATA + (id << 2), value); + dc_write(hw, DC_DISPLAY_GAMMA_EX_ONE_DATA + (id << 2), + hw->gamma[id].gamma[i][0]); + } + dc_set_bit(hw, DC_DISPLAY_PANEL_CONFIG + (id << 2), PANEL_GAMMA_EN); + } else { + dc_clear_bit(hw, DC_DISPLAY_PANEL_CONFIG + (id << 2), PANEL_GAMMA_EN); + } +} + +void dc_hw_enable(struct dc_hw *hw, int id, struct drm_display_mode *mode, + u8 encoder_type, u32 output_fmt) +{ + u32 dp_cfg, dpi_cfg, offset = id << 2; + bool is_yuv = false; + + if (encoder_type != DRM_MODE_ENCODER_DSI) { + switch (output_fmt) { + case MEDIA_BUS_FMT_RGB565_1X16: + dp_cfg = 0; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + dp_cfg = 1; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + dp_cfg = 2; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + dp_cfg = 3; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + dp_cfg = 2 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + dp_cfg = 4 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + dp_cfg = 8 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + dp_cfg = 10 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + dp_cfg = 12 << 4; + is_yuv = true; + break; + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + dp_cfg = 13 << 4; + is_yuv = true; + break; + default: + dp_cfg = 2; + break; + } + if (is_yuv) + dc_set_bit(hw, DC_DISPLAY_PANEL_CONFIG + offset, PANEL_RGB2YUV_EN); + else + dc_clear_bit(hw, DC_DISPLAY_PANEL_CONFIG + offset, PANEL_RGB2YUV_EN); + dc_write(hw, DC_DISPLAY_DP_CONFIG + offset, dp_cfg | DP_SELECT); + } + + if (hw->out[id] == OUT_DPI) + dc_clear_bit(hw, DC_DISPLAY_DP_CONFIG + offset, DP_SELECT); + + switch (output_fmt) { + case MEDIA_BUS_FMT_RGB565_1X16: + dpi_cfg = 0; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + dpi_cfg = 3; + break; + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + dpi_cfg = 4; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + dpi_cfg = 5; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + dpi_cfg = 6; + break; + default: + dpi_cfg = 5; + break; + } + dc_write(hw, DC_DISPLAY_DPI_CONFIG + offset, dpi_cfg); + + if (id == 0) + dc_clear_bit(hw, DC_DISPLAY_PANEL_START, PANEL0_EN | TWO_PANEL_EN); + else + dc_clear_bit(hw, DC_DISPLAY_PANEL_START, PANEL1_EN | TWO_PANEL_EN); + + dc_write(hw, DC_DISPLAY_H + offset, + H_ACTIVE_LEN(mode->hdisplay) | + H_TOTAL_LEN(mode->htotal)); + + dc_write(hw, DC_DISPLAY_H_SYNC + offset, + H_SYNC_START_LEN(mode->hsync_start) | + H_SYNC_END_LEN(mode->hsync_end) | + H_POLARITY_LEN(mode->flags & DRM_MODE_FLAG_PHSYNC ? 0 : 1) | + H_PLUS_LEN(1)); + + dc_write(hw, DC_DISPLAY_V + offset, + V_ACTIVE_LEN(mode->vdisplay) | + V_TOTAL_LEN(mode->vtotal)); + + dc_write(hw, DC_DISPLAY_V_SYNC + offset, + V_SYNC_START_LEN(mode->vsync_start) | + V_SYNC_END_LEN(mode->vsync_end) | + V_POLARITY_LEN(mode->flags & DRM_MODE_FLAG_PVSYNC ? 0 : 1) | + V_PLUS_LEN(1)); + + dc_set_bit(hw, DC_DISPLAY_PANEL_CONFIG + offset, PANEL_OUTPUT_EN); + dc_clear_bit(hw, DC_DISPLAY_PANEL_START, SYNC_EN); + dc_set_bit(hw, DC_DISPLAY_PANEL_START, BIT(id)); +} + +void dc_hw_disable(struct dc_hw *hw, int id) +{ + u32 offset = id << 2; + + if (hw->out[id] == OUT_DPI) + dc_clear_bit(hw, DC_DISPLAY_DP_CONFIG + offset, DP_SELECT); + dc_clear_bit(hw, DC_DISPLAY_PANEL_CONFIG + offset, PANEL_OUTPUT_EN); + dc_clear_bit(hw, DC_DISPLAY_PANEL_START, BIT(id) | TWO_PANEL_EN); +} + +void dc_hw_enable_interrupt(struct dc_hw *hw) +{ + hi_write(hw, AQ_INTR_ENBL, 0xFFFFFFFF); +} + +void dc_hw_disable_interrupt(struct dc_hw *hw) +{ + hi_write(hw, AQ_INTR_ENBL, 0); +} + +void dc_hw_get_interrupt(struct dc_hw *hw, u8 *status) +{ + u32 intr_status = hi_read(hw, AQ_INTR_ACKNOWLEDGE); + + if (intr_status & BIT(0)) + *status |= BIT(0); /* panel 0 frame done intr */ + + if (intr_status & BIT(1)) + *status |= BIT(1); /* panel 1 frame done intr */ +} + +void dc_hw_enable_shadow_register(struct vs_dc *dc, bool enable) +{ + u32 i, offset; + struct dc_hw *hw = &dc->hw; + u8 id, layer_num = hw->info->layer_num; + u8 panel_num = hw->info->panel_num; + + for (i = 0; i < layer_num; i++) { + id = dc->planes[i].id; + offset = dc->planes[i].offset; + if (enable) { + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) + dc_set_bit(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, + PRIMARY_SHADOW_EN); + else + dc_set_bit(hw, DC_OVERLAY_CONFIG + offset, + OVERLAY_SHADOW_EN); + } else { + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) + dc_clear_bit(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, + PRIMARY_SHADOW_EN); + else + dc_clear_bit(hw, DC_OVERLAY_CONFIG + offset, + OVERLAY_SHADOW_EN); + } + } + + for (i = 0; i < panel_num; i++) { + offset = i << 2; + if (enable) + dc_clear_bit(hw, DC_DISPLAY_PANEL_CONFIG_EX + offset, PANEL_SHADOW_INVALID); + else + dc_set_bit(hw, DC_DISPLAY_PANEL_CONFIG_EX + offset, PANEL_SHADOW_INVALID); + } +} + +void dc_hw_set_out(struct dc_hw *hw, enum dc_hw_out out, u8 id) +{ + if (out < OUT_MAX) + hw->out[id] = out; +} + +static inline u8 dc_to_vs_yuv_color_space(u32 color_space) +{ + switch (color_space) { + case DRM_COLOR_YCBCR_BT601: + return COLOR_SPACE_601; + case DRM_COLOR_YCBCR_BT709: + return COLOR_SPACE_709; + case DRM_COLOR_YCBCR_BT2020: + return COLOR_SPACE_2020; + default: + return COLOR_SPACE_601; + } +} + +static inline u8 dc_update_uv_swizzle(u32 format) +{ + switch (format) { + case DRM_FORMAT_YVYU: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + return 1; + default: + return 0; + } +} + +static inline u8 dc_update_swizzle(u32 format) +{ + switch (format) { + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBA1010102: + return SWIZZLE_RGBA; + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_ABGR2101010: + return SWIZZLE_ABGR; + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRA1010102: + return SWIZZLE_BGRA; + default: + return SWIZZLE_ARGB; + } +} + +static inline u8 dc_to_vs_rotation(unsigned int rotation) +{ + switch (rotation & DRM_MODE_REFLECT_MASK) { + case DRM_MODE_REFLECT_X: + return FLIP_X; + case DRM_MODE_REFLECT_Y: + return FLIP_Y; + case DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y: + return FLIP_XY; + default: + return ROT_0; + } + + switch (rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_0: + return ROT_0; + case DRM_MODE_ROTATE_90: + return ROT_90; + case DRM_MODE_ROTATE_180: + return ROT_180; + case DRM_MODE_ROTATE_270: + return ROT_270; + default: + return ROT_0; + } + + return ROT_0; +} + +void dc_plane_hw_update_format_colorspace(struct vs_dc *dc, u32 format, + enum drm_color_encoding encoding, + u8 id, + bool is_yuv) +{ + u32 offset = dc->planes[id].offset; + struct dc_hw *hw = &dc->hw; + + if (is_yuv) { + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) { + dc_clear_bit(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, PRIMARY_RGB2RGB_EN); + dc_set_bit(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, PRIMARY_YUVCLAMP_EN); + } else { + dc_clear_bit(hw, DC_OVERLAY_CONFIG + offset, OVERLAY_RGB2RGB_EN); + dc_set_bit(hw, DC_OVERLAY_CONFIG + offset, OVERLAY_CLAMP_EN); + } + switch (dc_to_vs_yuv_color_space(encoding)) { + case COLOR_SPACE_601: + dc_load_plane_yuv_to_rgb_csc(hw, &hw->reg[id], offset, YUV601_2RGB); + break; + case COLOR_SPACE_709: + dc_load_plane_yuv_to_rgb_csc(hw, &hw->reg[id], offset, YUV709_2RGB); + break; + case COLOR_SPACE_2020: + dc_load_plane_yuv_to_rgb_csc(hw, &hw->reg[id], offset, YUV2020_2RGB); + break; + default: + break; + } + } else { + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) { + dc_clear_bit(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, PRIMARY_YUVCLAMP_EN); + dc_set_bit(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, PRIMARY_RGB2RGB_EN); + } else { + dc_clear_bit(hw, DC_OVERLAY_CONFIG + offset, + OVERLAY_CLAMP_EN); + dc_set_bit(hw, DC_OVERLAY_CONFIG + offset, + OVERLAY_RGB2RGB_EN); + } + } +} + +void dc_plane_hw_update_address(struct vs_dc *dc, u8 id, u32 format, dma_addr_t *dma_addr, + struct drm_framebuffer *drm_fb, struct drm_rect *src) +{ + u32 offset = dc->planes[id].offset; + struct dc_hw *hw = &dc->hw; + + dc_write(hw, hw->reg[id].y_address + offset, dma_addr[0]); + dc_write(hw, hw->reg[id].u_address + offset, + format == DRM_FORMAT_YVU420 ? + dma_addr[2] : dma_addr[1]); + dc_write(hw, hw->reg[id].v_address + offset, + format == DRM_FORMAT_YVU420 ? + dma_addr[1] : dma_addr[2]); + dc_write(hw, hw->reg[id].y_stride + offset, drm_fb->pitches[0]); + dc_write(hw, hw->reg[id].u_stride + offset, + format == DRM_FORMAT_YVU420 ? + drm_fb->pitches[2] : drm_fb->pitches[1]); + dc_write(hw, hw->reg[id].v_stride + offset, + format == DRM_FORMAT_YVU420 ? + drm_fb->pitches[1] : drm_fb->pitches[2]); + dc_write(hw, hw->reg[id].size + offset, + FB_SIZE(drm_rect_width(src) >> 16, drm_rect_height(src) >> 16)); +} + +void dc_plane_hw_update_format(struct vs_dc *dc, u32 format, enum drm_color_encoding encoding, + unsigned int rotation, bool visible, unsigned int zpos, + u8 id, u8 display_id) +{ + u32 offset = dc->planes[id].offset; + struct dc_hw *hw = &dc->hw; + + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) { + dc_write_mask(hw, DC_FRAMEBUFFER_CONFIG + offset, + PRIMARY_FORMAT(dc_update_vs_format(format)) | + PRIMARY_UV_SWIZ(dc_update_uv_swizzle(format)) | + PRIMARY_SWIZ(dc_update_swizzle(format)) | + PRIMARY_TILE(DRM_FORMAT_MOD_LINEAR) | + PRIMARY_YUV_COLOR(dc_to_vs_yuv_color_space(encoding)) | + PRIMARY_ROTATION(dc_to_vs_rotation(rotation)), + PRIMARY_FORMAT_MASK | + PRIMARY_UV_SWIZ_MASK | + PRIMARY_SWIZ_MASK | + PRIMARY_TILE_MASK | + PRIMARY_YUV_COLOR_MASK | + PRIMARY_ROTATION_MASK | + PRIMARY_CLEAR_EN_MASK); + dc_write_mask(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, + PRIMARY_DECODER_EN(false) | + PRIMARY_EN(visible) | + PRIMARY_ZPOS(zpos) | + PRIMARY_CHANNEL(display_id), + PRIMARY_DECODER_EN_EN_MASK | + PRIMARY_EN_MASK | + PRIMARY_ZPOS_MASK | + PRIMARY_CHANNEL_MASK); + } else { + dc_write_mask(hw, DC_OVERLAY_CONFIG + offset, + OVERLAY_FB_EN(visible) | + OVERLAY_FORMAT(dc_update_vs_format(format)) | + OVERLAY_UV_SWIZ(dc_update_uv_swizzle(format)) | + OVERLAY_SWIZ(dc_update_swizzle(format)) | + OVERLAY_TILE(DRM_FORMAT_MOD_LINEAR) | + OVERLAY_YUV_COLOR(dc_to_vs_yuv_color_space(encoding)) | + OVERLAY_ROTATION(dc_to_vs_rotation(rotation)), + OVERLAY_DEC_EN_MASK | + OVERLAY_CLEAR_EN_MASK | + OVERLAY_FB_EN_MASK | + OVERLAY_FORMAT_MASK | + OVERLAY_UV_SWIZ_MASK | + OVERLAY_SWIZ_MASK | + OVERLAY_TILE_MASK | + OVERLAY_YUV_COLOR_MASK | + OVERLAY_ROTATION_MASK); + + dc_write_mask(hw, DC_OVERLAY_CONFIG_EX + offset, + OVERLAY_LAYER_SEL(zpos) | OVERLAY_PANEL_SEL(display_id), + OVERLAY_LAYER_SEL_MASK | OVERLAY_PANEL_SEL_MASK); + } +} + +/** + * calc_factor - Calculate the scaling factor for converting + * source dimensions to destination dimensions. + * + * @src: The source dimension (width or height). + * @dest: The destination dimension (width or height). + * + * This function computes a scaling factor for converting + * a given source size to a destination size. The factor is + * represented as a 16.16 fixed-point number. If either the + * source or destination dimension is less than or equal to 1, + * it will return a factor of 1 << 16 (which corresponds to + * a scaling factor of 1.0). The function ensures that scaling + * logic is only applied when both dimensions are greater than 1, + * avoiding potential division by zero or erroneous scaling. + * + * Returns: The computed scaling factor as a 32-bit unsigned integer. + */ +static u32 calc_factor(u32 src, u32 dest) +{ + u32 factor = 1 << 16; + + if (src > 1 && dest > 1) + factor = ((src - 1) << 16) / (dest - 1); + + return factor; +} + +void dc_plane_hw_update_scale(struct vs_dc *dc, struct drm_rect *src, struct drm_rect *dst, + u8 id, u8 display_id, unsigned int rotation) +{ + u32 offset = dc->planes[id].offset; + struct dc_hw *hw = &dc->hw; + + int dst_w = drm_rect_width(dst); + int dst_h = drm_rect_height(dst); + int src_w, src_h, temp; + u32 scale_factor_x; + u32 scale_factor_y; + bool enable_scale = false; + + src_w = drm_rect_width(src) >> 16; + src_h = drm_rect_height(src) >> 16; + + if (drm_rotation_90_or_270(rotation)) { + temp = src_w; + src_w = src_h; + src_h = temp; + } + + if (src_w != dst_w) { + scale_factor_x = calc_factor(src_w, dst_w); + enable_scale = true; + } else { + scale_factor_x = 1 << 16; + } + if (src_h != dst_h) { + scale_factor_y = calc_factor(src_h, dst_h); + enable_scale = true; + } else { + scale_factor_y = 1 << 16; + } + if (enable_scale) { + dc_write(hw, hw->reg[id].scale_factor_x + offset, scale_factor_x); + dc_write(hw, hw->reg[id].scale_factor_y + offset, scale_factor_y); + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) + dc_set_bit(hw, DC_FRAMEBUFFER_CONFIG + offset, PRIMARY_SCALE_EN); + else + dc_set_bit(hw, DC_OVERLAY_SCALE_CONFIG + offset, OVERLAY_SCALE_EN); + } else { + if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) + dc_clear_bit(hw, DC_FRAMEBUFFER_CONFIG + offset, PRIMARY_SCALE_EN); + else + dc_clear_bit(hw, DC_OVERLAY_SCALE_CONFIG + offset, OVERLAY_SCALE_EN); + } + + dc_write(hw, hw->reg[id].top_left + offset, X_POS(dst->x1) | Y_POS(dst->y1)); + dc_write(hw, hw->reg[id].bottom_right + offset, X_POS(dst->x2) | Y_POS(dst->y2)); +} + +void dc_plane_hw_update_blend(struct vs_dc *dc, u16 alpha, + u16 pixel_blend_mode, u8 id, u8 display_id) +{ + u32 offset = dc->planes[id].offset; + struct dc_hw *hw = &dc->hw; + + dc_write(hw, hw->reg[id].src_global_color + offset, PRIMARY_ALPHA_LEN(alpha >> 8)); + dc_write(hw, hw->reg[id].dst_global_color + offset, PRIMARY_ALPHA_LEN(alpha >> 8)); + switch (pixel_blend_mode) { + case DRM_MODE_BLEND_PREMULTI: + dc_write(hw, hw->reg[id].blend_config + offset, BLEND_PREMULTI); + break; + case DRM_MODE_BLEND_COVERAGE: + dc_write(hw, hw->reg[id].blend_config + offset, BLEND_COVERAGE); + break; + case DRM_MODE_BLEND_PIXEL_NONE: + dc_write(hw, hw->reg[id].blend_config + offset, BLEND_PIXEL_NONE); + break; + default: + break; + } +} diff --git a/drivers/gpu/drm/verisilicon/vs_dc_hw.h b/drivers/gpu/drm/verisilicon/vs_dc_hw.h new file mode 100644 index 000000000000..faf5b4c325f8 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc_hw.h @@ -0,0 +1,492 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ + +#ifndef __VS_DC_HW_H__ +#define __VS_DC_HW_H__ + +#include +#include +#include + +#include "vs_type.h" + +#define UPDATE(x, h, l) FIELD_PREP(GENMASK(h, l), x) + +#define AQ_INTR_ACKNOWLEDGE 0x0010 +#define AQ_INTR_ENBL 0x0014 +#define DC_HW_REVISION 0x0024 +#define DC_HW_CHIP_CID 0x0030 + +#define DC_REG_BASE 0x0800 +#define DC_REG_RANGE 0x2000 +#define DC_SEC_REG_OFFSET 0x100000 + +#define DC_FRAMEBUFFER_CONFIG 0x1518 +# define PRIMARY_FORMAT(x) ((x) << 26) +# define PRIMARY_FORMAT_MASK GENMASK(31, 26) +# define PRIMARY_UV_SWIZ(x) ((x) << 25) +# define PRIMARY_UV_SWIZ_MASK GENMASK(25, 25) +# define PRIMARY_SWIZ(x) ((x) << 23) +# define PRIMARY_SWIZ_MASK GENMASK(24, 23) +# define PRIMARY_SCALE_EN BIT(12) +# define PRIMARY_TILE(x) ((x) << 17) +# define PRIMARY_TILE_MASK GENMASK(21, 17) +# define PRIMARY_YUV_COLOR(x) ((x) << 14) +# define PRIMARY_YUV_COLOR_MASK GENMASK(16, 14) +# define PRIMARY_ROTATION(x) ((x) << 11) +# define PRIMARY_ROTATION_MASK GENMASK(13, 11) +# define PRIMARY_CLEAR_EN(x) ((x) << 8) +# define PRIMARY_CLEAR_EN_MASK GENMASK(8, 8) + +#define DC_FRAMEBUFFER_CONFIG_EX 0x1CC0 +# define PRIMARY_CHANNEL(x) ((x) << 19) +# define PRIMARY_CHANNEL_MASK GENMASK(19, 19) +# define PRIMARY_ZPOS(x) ((x) << 16) +# define PRIMARY_ZPOS_MASK GENMASK(18, 16) +# define PRIMARY_EN(x) ((x) << 13) +# define PRIMARY_EN_MASK GENMASK(13, 13) +# define PRIMARY_SHADOW_EN BIT(12) +# define PRIMARY_YUVCLAMP_EN BIT(8) +# define PRIMARY_RGB2RGB_EN BIT(6) +# define PRIMARY_SYNC1_EN BIT(4) +# define PRIMARY_SYNC0_EN BIT(3) +# define PRIMARY_DECODER_EN(x) ((x) << 1) +# define PRIMARY_DECODER_EN_EN_MASK GENMASK(1, 1) + +#define DC_FRAMEBUFFER_SCALE_CONFIG 0x1520 +#define DC_FRAMEBUFFER_TOP_LEFT 0x24D8 +#define X_POS(x) (x) +#define Y_POS(x) ((x) << 15) + +#define DC_FRAMEBUFFER_BOTTOM_RIGHT 0x24E0 +#define DC_FRAMEBUFFER_ADDRESS 0x1400 +#define DC_FRAMEBUFFER_U_ADDRESS 0x1530 +#define DC_FRAMEBUFFER_V_ADDRESS 0x1538 +#define DC_FRAMEBUFFER_STRIDE 0x1408 +#define DC_FRAMEBUFFER_U_STRIDE 0x1800 +#define DC_FRAMEBUFFER_V_STRIDE 0x1808 +#define DC_FRAMEBUFFER_SIZE 0x1810 +#define FB_SIZE(w, h) ((w) | ((h) << 15)) + +#define DC_FRAMEBUFFER_SCALE_FACTOR_X 0x1828 +#define DC_FRAMEBUFFER_SCALE_FACTOR_Y 0x1830 +#define DC_FRAMEBUFFER_H_FILTER_COEF_INDEX 0x1838 +#define DC_FRAMEBUFFER_H_FILTER_COEF_DATA 0x1A00 +#define DC_FRAMEBUFFER_V_FILTER_COEF_INDEX 0x1A08 +#define DC_FRAMEBUFFER_V_FILTER_COEF_DATA 0x1A10 +#define DC_FRAMEBUFFER_INIT_OFFSET 0x1A20 +#define DC_FRAMEBUFFER_COLOR_KEY 0x1508 +#define DC_FRAMEBUFFER_COLOR_KEY_HIGH 0x1510 +#define DC_FRAMEBUFFER_CLEAR_VALUE 0x1A18 +#define DC_FRAMEBUFFER_COLOR_TABLE_INDEX 0x1818 +#define DC_FRAMEBUFFER_COLOR_TABLE_DATA 0x1820 +#define DC_FRAMEBUFFER_BG_COLOR 0x1528 +#define DC_FRAMEBUFFER_ROI_ORIGIN 0x1CB0 +#define DC_FRAMEBUFFER_ROI_SIZE 0x1CB8 +#define DC_FRAMEBUFFER_WATER_MARK 0x1CE8 +#define DC_FRAMEBUFFER_DEGAMMA_INDEX 0x1D88 +#define DC_FRAMEBUFFER_DEGAMMA_DATA 0x1D90 +#define DC_FRAMEBUFFER_DEGAMMA_EX_DATA 0x1D98 +#define DC_FRAMEBUFFER_YUVTORGB_COEF0 0x1DA0 +#define DC_FRAMEBUFFER_YUVTORGB_COEF1 0x1DA8 +#define DC_FRAMEBUFFER_YUVTORGB_COEF2 0x1DB0 +#define DC_FRAMEBUFFER_YUVTORGB_COEF3 0x1DB8 +#define DC_FRAMEBUFFER_YUVTORGB_COEF4 0x1E00 +#define DC_FRAMEBUFFER_YUVTORGB_COEFD0 0x1E08 +#define DC_FRAMEBUFFER_YUVTORGB_COEFD1 0x1E10 +#define DC_FRAMEBUFFER_YUVTORGB_COEFD2 0x1E18 +#define DC_FRAMEBUFFER_Y_CLAMP_BOUND 0x1E88 +#define DC_FRAMEBUFFER_UV_CLAMP_BOUND 0x1E90 +#define DC_FRAMEBUFFER_RGBTORGB_COEF0 0x1E20 +#define DC_FRAMEBUFFER_RGBTORGB_COEF1 0x1E28 +#define DC_FRAMEBUFFER_RGBTORGB_COEF2 0x1E30 +#define DC_FRAMEBUFFER_RGBTORGB_COEF3 0x1E38 +#define DC_FRAMEBUFFER_RGBTORGB_COEF4 0x1E40 +#define DC_FRAMEBUFFER_BLEND_CONFIG 0x2510 +# define BLEND_PREMULTI 0x3450 +# define BLEND_COVERAGE 0x3950 +# define BLEND_PIXEL_NONE 0x3548 + +#define DC_FRAMEBUFFER_SRC_GLOBAL_COLOR 0x2500 +# define PRIMARY_ALPHA_LEN(x) ((x) << 24) + +#define DC_FRAMEBUFFER_DST_GLOBAL_COLOR 0x2508 + +#define DC_OVERLAY_CONFIG 0x1540 +# define OVERLAY_SHADOW_EN BIT(31) +# define OVERLAY_CLAMP_EN BIT(30) +# define OVERLAY_RGB2RGB_EN BIT(29) +# define OVERLAY_DEC_EN(x) ((x) << 27) +# define OVERLAY_DEC_EN_MASK GENMASK(27, 27) +# define OVERLAY_CLEAR_EN(x) ((x) << 25) +# define OVERLAY_CLEAR_EN_MASK GENMASK(25, 25) +# define OVERLAY_FB_EN(x) ((x) << 24) +# define OVERLAY_FB_EN_MASK GENMASK(24, 24) +# define OVERLAY_FORMAT(x) ((x) << 16) +# define OVERLAY_FORMAT_MASK GENMASK(21, 16) +# define OVERLAY_UV_SWIZ(x) ((x) << 15) +# define OVERLAY_UV_SWIZ_MASK GENMASK(15, 15) +# define OVERLAY_SWIZ(x) ((x) << 13) +# define OVERLAY_SWIZ_MASK GENMASK(14, 13) +# define OVERLAY_TILE(x) ((x) << 8) +# define OVERLAY_TILE_MASK GENMASK(12, 8) +# define OVERLAY_YUV_COLOR(x) ((x) << 5) +# define OVERLAY_YUV_COLOR_MASK GENMASK(7, 5) +# define OVERLAY_ROTATION(x) ((x) << 2) +# define OVERLAY_ROTATION_MASK GENMASK(4, 2) + +#define DC_OVERLAY_CONFIG_EX 0x2540 +# define OVERLAY_LAYER_SEL(x) ((x) << 0) +# define OVERLAY_LAYER_SEL_MASK GENMASK(2, 0) +# define OVERLAY_PANEL_SEL(x) ((x) << 3) +# define OVERLAY_PANEL_SEL_MASK GENMASK(3, 3) + +#define DC_OVERLAY_SCALE_CONFIG 0x1C00 +# define OVERLAY_SCALE_EN BIT(8) + +#define DC_OVERLAY_BLEND_CONFIG 0x1580 +#define DC_OVERLAY_TOP_LEFT 0x1640 +#define DC_OVERLAY_BOTTOM_RIGHT 0x1680 +#define DC_OVERLAY_ADDRESS 0x15C0 +#define DC_OVERLAY_U_ADDRESS 0x1840 +#define DC_OVERLAY_V_ADDRESS 0x1880 +#define DC_OVERLAY_STRIDE 0x1600 +#define DC_OVERLAY_U_STRIDE 0x18C0 +#define DC_OVERLAY_V_STRIDE 0x1900 +#define DC_OVERLAY_SIZE 0x17C0 +#define DC_OVERLAY_SCALE_FACTOR_X 0x1A40 +#define DC_OVERLAY_SCALE_FACTOR_Y 0x1A80 +#define DC_OVERLAY_H_FILTER_COEF_INDEX 0x1AC0 +#define DC_OVERLAY_H_FILTER_COEF_DATA 0x1B00 +#define DC_OVERLAY_V_FILTER_COEF_INDEX 0x1B40 +#define DC_OVERLAY_V_FILTER_COEF_DATA 0x1B80 +#define DC_OVERLAY_INIT_OFFSET 0x1BC0 +#define DC_OVERLAY_COLOR_KEY 0x1740 +#define DC_OVERLAY_COLOR_KEY_HIGH 0x1780 +#define DC_OVERLAY_CLEAR_VALUE 0x1940 +#define DC_OVERLAY_COLOR_TABLE_INDEX 0x1980 +#define DC_OVERLAY_COLOR_TABLE_DATA 0x19C0 +#define DC_OVERLAY_SRC_GLOBAL_COLOR 0x16C0 +# define OVERLAY_ALPHA_LEN(x) ((x) << 24) + +#define DC_OVERLAY_DST_GLOBAL_COLOR 0x1700 +#define DC_OVERLAY_ROI_ORIGIN 0x1D00 +#define DC_OVERLAY_ROI_SIZE 0x1D40 +#define DC_OVERLAY_WATER_MARK 0x1DC0 +#define DC_OVERLAY_DEGAMMA_INDEX 0x2200 +#define DC_OVERLAY_DEGAMMA_DATA 0x2240 +#define DC_OVERLAY_DEGAMMA_EX_DATA 0x2280 +#define DC_OVERLAY_YUVTORGB_COEF0 0x1EC0 +#define DC_OVERLAY_YUVTORGB_COEF1 0x1F00 +#define DC_OVERLAY_YUVTORGB_COEF2 0x1F40 +#define DC_OVERLAY_YUVTORGB_COEF3 0x1F80 +#define DC_OVERLAY_YUVTORGB_COEF4 0x1FC0 +#define DC_OVERLAY_YUVTORGB_COEFD0 0x2000 +#define DC_OVERLAY_YUVTORGB_COEFD1 0x2040 +#define DC_OVERLAY_YUVTORGB_COEFD2 0x2080 +#define DC_OVERLAY_Y_CLAMP_BOUND 0x22C0 +#define DC_OVERLAY_UV_CLAMP_BOUND 0x2300 +#define DC_OVERLAY_RGBTORGB_COEF0 0x20C0 +#define DC_OVERLAY_RGBTORGB_COEF1 0x2100 +#define DC_OVERLAY_RGBTORGB_COEF2 0x2140 +#define DC_OVERLAY_RGBTORGB_COEF3 0x2180 +#define DC_OVERLAY_RGBTORGB_COEF4 0x21C0 + +#define DC_CURSOR_CONFIG 0x1468 +# define CURSOR_HOT_X(x) ((x) << 16) +# define CURSOR_HOT_X_MASK GENMASK(23, 16) +# define CURSOR_HOT_y(x) ((x) << 8) +# define CURSOR_HOT_y_MASK GENMASK(15, 8) +# define CURSOR_SIZE(x) ((x) << 5) +# define CURSOR_SIZE_MASK GENMASK(7, 5) +# define CURSOR_VALID(x) ((x) << 3) +# define CURSOR_VALID_MASK GENMASK(3, 3) +# define CURSOR_TRIG_FETCH(x) ((x) << 2) +# define CURSOR_TRIG_FETCH_MASK GENMASK(2, 2) +# define CURSOR_FORMAT(x) ((x) << 0) +# define CURSOR_FORMAT_MASK GENMASK(1, 0) +# define CURSOR_FORMAT_DISABLE 0 +# define CURSOR_FORMAT_MARK 1 +# define CURSOR_FORMAT_A8R8G8B8 2 + +#define DC_CURSOR_ADDRESS 0x146C +#define DC_CURSOR_LOCATION 0x1470 +# define X_LCOTION(x) (x) +# define Y_LCOTION(x) ((x) << 16) + +#define DC_CURSOR_BACKGROUND 0x1474 +#define DC_CURSOR_FOREGROUND 0x1478 +#define DC_CURSOR_CLK_GATING 0x1484 +#define DC_CURSOR_CONFIG_EX 0x24E8 +#define DC_CURSOR_OFFSET 0x1080 + +#define DC_DISPLAY_DITHER_CONFIG 0x1410 +#define DC_DISPLAY_PANEL_CONFIG 0x1418 +# define PANEL_RGB2YUV_EN BIT(16) +# define PANEL_GAMMA_EN BIT(13) +# define PANEL_OUTPUT_EN BIT(12) +# define PANEL_CLOCK_EN BIT(8) +# define PANEL_DATA_EN BIT(4) +# define PANEL_DE_EN BIT(0) + +#define DC_DISPLAY_PANEL_CONFIG_EX 0x2518 +# define PANEL_SHADOW_INVALID BIT(0) + +#define DC_DISPLAY_DITHER_TABLE_LOW 0x1420 +#define DC_DISPLAY_DITHER_TABLE_HIGH 0x1428 +#define DC_DISPLAY_H 0x1430 +# define H_ACTIVE_LEN(x) (x) +# define H_TOTAL_LEN(x) ((x) << 16) + +#define DC_DISPLAY_H_SYNC 0x1438 +# define H_SYNC_START_LEN(x) (x) +# define H_SYNC_END_LEN(x) ((x) << 15) +# define H_PLUS_LEN(x) ((x) << 30) +# define H_POLARITY_LEN(x) ((x) << 31) + +#define DC_DISPLAY_V 0x1440 +# define V_ACTIVE_LEN(x) (x) +# define V_TOTAL_LEN(x) ((x) << 16) + +#define DC_DISPLAY_V_SYNC 0x1448 +# define V_SYNC_START_LEN(x) (x) +# define V_SYNC_END_LEN(x) ((x) << 15) +# define V_PLUS_LEN(x) ((x) << 30) +# define V_POLARITY_LEN(x) ((x) << 31) + +#define DC_DISPLAY_CURRENT_LOCATION 0x1450 +#define DC_DISPLAY_GAMMA_INDEX 0x1458 +#define DC_DISPLAY_GAMMA_DATA 0x1460 +#define DC_DISPLAY_INT 0x147C +#define DC_DISPLAY_INT_ENABLE 0x1480 +#define DC_DISPLAY_DBI_CONFIG 0x1488 +#define DC_DISPLAY_GENERAL_CONFIG 0x14B0 +#define DC_DISPLAY_DPI_CONFIG 0x14B8 +#define DC_DISPLAY_PANEL_START 0x1CCC +# define PANEL0_EN BIT(0) +# define PANEL1_EN BIT(1) +# define TWO_PANEL_EN BIT(2) +# define SYNC_EN BIT(3) + +#define DC_DISPLAY_DEBUG_COUNTER_SELECT 0x14D0 +#define DC_DISPLAY_DEBUG_COUNTER_VALUE 0x14D8 +#define DC_DISPLAY_DP_CONFIG 0x1CD0 +# define DP_SELECT BIT(3) + +#define DC_DISPLAY_GAMMA_EX_INDEX 0x1CF0 +#define DC_DISPLAY_GAMMA_EX_DATA 0x1CF8 +#define DC_DISPLAY_GAMMA_EX_ONE_DATA 0x1D80 +#define DC_DISPLAY_RGBTOYUV_COEF0 0x1E48 +#define DC_DISPLAY_RGBTOYUV_COEF1 0x1E50 +#define DC_DISPLAY_RGBTOYUV_COEF2 0x1E58 +#define DC_DISPLAY_RGBTOYUV_COEF3 0x1E60 +#define DC_DISPLAY_RGBTOYUV_COEF4 0x1E68 +#define DC_DISPLAY_RGBTOYUV_COEFD0 0x1E70 +#define DC_DISPLAY_RGBTOYUV_COEFD1 0x1E78 +#define DC_DISPLAY_RGBTOYUV_COEFD2 0x1E80 + +#define DC_CLK_GATTING 0x1A28 +#define DC_QOS_CONFIG 0x1A38 + +#define DC_TRANSPARENCY_OPAQUE 0x00 +#define DC_TRANSPARENCY_KEY 0x02 +#define DC_DISPLAY_DITHERTABLE_LOW 0x7B48F3C0 +#define DC_DISPLAY_DITHERTABLE_HIGH 0x596AD1E2 + +#define DC_TILE_MODE4X4 0x15 + +#define GAMMA_SIZE 256 +#define GAMMA_EX_SIZE 300 +#define DEGAMMA_SIZE 260 + +#define RGB_TO_RGB_TABLE_SIZE 9 +#define YUV_TO_RGB_TABLE_SIZE 16 +#define RGB_TO_YUV_TABLE_SIZE 12 + +#define DC_LAYER_NUM 6 +#define DC_DISPLAY_NUM 2 +#define DC_CURSOR_NUM 2 + +enum dc_hw_plane_id { + PRIMARY_PLANE_0, + OVERLAY_PLANE_0, + OVERLAY_PLANE_1, + PRIMARY_PLANE_1, + OVERLAY_PLANE_2, + OVERLAY_PLANE_3, + CURSOR_PLANE_0, + CURSOR_PLANE_1, + PLANE_NUM +}; + +enum dc_hw_color_format { + FORMAT_X4R4G4B4, + FORMAT_A4R4G4B4, + FORMAT_X1R5G5B5, + FORMAT_A1R5G5B5, + FORMAT_R5G6B5, + FORMAT_X8R8G8B8, + FORMAT_A8R8G8B8, + FORMAT_YUY2, + FORMAT_UYVY, + FORMAT_INDEX8, + FORMAT_MONOCHROME, + FORMAT_YV12 = 0xf, + FORMAT_A8, + FORMAT_NV12, + FORMAT_NV16, + FORMAT_RG16, + FORMAT_R8, + FORMAT_NV12_10BIT, + FORMAT_A2R10G10B10, + FORMAT_NV16_10BIT, + FORMAT_INDEX1, + FORMAT_INDEX2, + FORMAT_INDEX4, + FORMAT_P010, + FORMAT_YUV444, + FORMAT_YUV444_10BIT, +}; + +enum dc_hw_yuv_color_space { + COLOR_SPACE_601 = 0, + COLOR_SPACE_709 = 1, + COLOR_SPACE_2020 = 3, +}; + +enum dc_hw_rotation { + ROT_0 = 0, + ROT_90 = 4, + ROT_180 = 5, + ROT_270 = 6, + FLIP_X = 1, + FLIP_Y = 2, + FLIP_XY = 3, +}; + +enum dc_hw_swizzle { + SWIZZLE_ARGB = 0, + SWIZZLE_RGBA, + SWIZZLE_ABGR, + SWIZZLE_BGRA, +}; + +enum dc_hw_out { + OUT_DPI, + OUT_DP, + OUT_MAX, +}; + +enum dc_hw_cursor_size { + CURSOR_SIZE_32X32 = 0, + CURSOR_SIZE_64X64, +}; + +struct dc_hw_plane_reg { + u32 y_address; + u32 u_address; + u32 v_address; + u32 y_stride; + u32 u_stride; + u32 v_stride; + u32 size; + u32 top_left; + u32 bottom_right; + u32 scale_factor_x; + u32 scale_factor_y; + u32 h_filter_coef_index; + u32 h_filter_coef_data; + u32 v_filter_coef_index; + u32 v_filter_coef_data; + u32 init_offset; + u32 color_key; + u32 color_key_high; + u32 clear_value; + u32 color_table_index; + u32 color_table_data; + u32 scale_config; + u32 water_mark; + u32 degamma_index; + u32 degamma_data; + u32 degamma_ex_data; + u32 src_global_color; + u32 dst_global_color; + u32 blend_config; + u32 roi_origin; + u32 roi_size; + u32 yuv_to_rgb_coef0; + u32 yuv_to_rgb_coef1; + u32 yuv_to_rgb_coef2; + u32 yuv_to_rgb_coef3; + u32 yuv_to_rgb_coef4; + u32 yuv_to_rgb_coefd0; + u32 yuv_to_rgb_coefd1; + u32 yuv_to_rgb_coefd2; + u32 y_clamp_bound; + u32 uv_clamp_bound; + u32 rgb_to_rgb_coef0; + u32 rgb_to_rgb_coef1; + u32 rgb_to_rgb_coef2; + u32 rgb_to_rgb_coef3; + u32 rgb_to_rgb_coef4; +}; + +struct dc_hw_gamma { + u16 gamma[GAMMA_EX_SIZE][3]; +}; + +struct dc_hw { + enum dc_hw_out out[DC_DISPLAY_NUM]; + void __iomem *hi_base; + void __iomem *reg_base; + struct dc_hw_plane_reg reg[DC_LAYER_NUM]; + + struct dc_hw_gamma gamma[DC_DISPLAY_NUM]; + struct vs_dc_info *info; +}; + +struct vs_dc_plane { + enum dc_hw_plane_id id; + u32 offset; +}; + +struct vs_dc { + struct vs_crtc *crtc[DC_DISPLAY_NUM]; + struct dc_hw hw; + + struct vs_dc_plane planes[PLANE_NUM]; +}; + +int dc_hw_init(struct vs_dc *dc); +void dc_hw_disable_plane(struct vs_dc *dc, u8 id); +void dc_hw_update_cursor(struct dc_hw *hw, u8 id, dma_addr_t dma_addr, + u32 crtc_w, u32 crtc_x, u32 crtc_y, + s32 hotspot_x, int32_t hotspot_y); +void dc_hw_disable_cursor(struct dc_hw *hw, u8 id); +void dc_hw_update_gamma(struct dc_hw *hw, u8 id, u16 index, + u16 r, u16 g, u16 b); +void dc_hw_enable_gamma(struct dc_hw *hw, u8 id, bool enable); +void dc_hw_enable(struct dc_hw *hw, int id, struct drm_display_mode *mode, + u8 encoder_type, u32 output_fmt); +void dc_hw_disable(struct dc_hw *hw, int id); +void dc_hw_enable_interrupt(struct dc_hw *hw); +void dc_hw_disable_interrupt(struct dc_hw *hw); +void dc_hw_get_interrupt(struct dc_hw *hw, u8 *status); +void dc_hw_enable_shadow_register(struct vs_dc *dc, bool enable); +void dc_hw_set_out(struct dc_hw *hw, enum dc_hw_out out, u8 id); +void dc_hw_commit(struct dc_hw *hw); +void dc_plane_hw_update_format_colorspace(struct vs_dc *dc, u32 format, + enum drm_color_encoding encoding, u8 id, bool is_yuv); +void dc_plane_hw_update_address(struct vs_dc *dc, u8 id, u32 format, dma_addr_t *dma_addr, + struct drm_framebuffer *drm_fb, struct drm_rect *src); +void dc_plane_hw_update_format(struct vs_dc *dc, u32 format, enum drm_color_encoding encoding, + unsigned int rotation, bool visible, unsigned int zpos, + u8 id, u8 display_id); +void dc_plane_hw_update_scale(struct vs_dc *dc, struct drm_rect *src, struct drm_rect *dst, + u8 id, u8 display_id, unsigned int rotation); +void dc_plane_hw_update_blend(struct vs_dc *dc, u16 alpha, u16 pixel_blend_mode, + u8 id, u8 display_id); + +#endif /* __VS_DC_HW_H__ */ diff --git a/drivers/gpu/drm/verisilicon/vs_type.h b/drivers/gpu/drm/verisilicon/vs_type.h new file mode 100644 index 000000000000..e9c4ef3cacd6 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_type.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ + +#ifndef __VS_TYPE_H__ +#define __VS_TYPE_H__ + +enum drm_plane_type; + +struct vs_plane_data { + unsigned int num_formats; + const u32 *formats; + u8 num_modifiers; + const u64 *modifiers; + unsigned int min_width; + unsigned int min_height; + unsigned int max_width; + unsigned int max_height; + unsigned int rotation; + unsigned int blend_mode; + unsigned int color_encoding; + int min_scale; /* 16.16 fixed point */ + int max_scale; /* 16.16 fixed point */ + u8 zpos; +}; + +struct vs_plane_info { + u32 id; + const struct vs_plane_data *data; + enum drm_plane_type type; +}; + +struct vs_dc_info { + const char *name; + + u8 panel_num; + + /* planes */ + u8 plane_num; + + u8 layer_num; + u8 primary_num; + u8 overlay_num; + u8 cursor_num; + const struct vs_plane_info *info; + /* 0 means no gamma LUT */ + u16 gamma_size; + u8 gamma_bits; + + u16 pitch_alignment; +}; + +#endif /* __VS_TYPE_H__ */ diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index 7da54b259940..842867dad4cb 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -vs_drm-objs := vs_dc_hw.o +vs_drm-objs := vs_dc_hw.o \ + vs_modeset.o obj-$(CONFIG_DRM_VERISILICON_DC8200) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/vs_modeset.c b/drivers/gpu/drm/verisilicon/vs_modeset.c new file mode 100644 index 000000000000..0873a3465143 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_modeset.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ +#include +#include +#include + +#include "vs_modeset.h" + +static const struct drm_mode_config_funcs vs_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static struct drm_mode_config_helper_funcs vs_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +void vs_mode_config_init(struct drm_device *dev) +{ + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return; + + dev->mode_config.funcs = &vs_mode_config_funcs; + dev->mode_config.helper_private = &vs_mode_config_helpers; +} diff --git a/drivers/gpu/drm/verisilicon/vs_modeset.h b/drivers/gpu/drm/verisilicon/vs_modeset.h new file mode 100644 index 000000000000..bd04f81d2ad2 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_modeset.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef __VS_MODESET_H__ +#define __VS_MODESET_H__ + +void vs_mode_config_init(struct drm_device *dev); +#endif /* __VS_FB_H__ */ diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index 842867dad4cb..37f6a4db2a12 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 vs_drm-objs := vs_dc_hw.o \ - vs_modeset.o + vs_modeset.o \ + vs_crtc.o obj-$(CONFIG_DRM_VERISILICON_DC8200) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c new file mode 100644 index 000000000000..45ce28960e27 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + * + */ +#include +#include +#include +#include + +#include "vs_crtc.h" +#include "vs_drv.h" + +static void vs_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_vs_crtc_state(state)); +} + +static void vs_crtc_reset(struct drm_crtc *crtc) +{ + struct vs_crtc_state *state; + + if (crtc->state) + vs_crtc_atomic_destroy_state(crtc, crtc->state); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + __drm_atomic_helper_crtc_reset(crtc, &state->base); +} + +static struct drm_crtc_state * +vs_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct vs_crtc_state *old_state; + struct vs_crtc_state *state; + + if (!crtc->state) + return NULL; + + old_state = to_vs_crtc_state(crtc->state); + + state = kmemdup(old_state, sizeof(*old_state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + return &state->base; +} + +static int vs_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct vs_drm_device *priv = to_vs_drm_private(crtc->dev); + struct vs_dc *dc = &priv->dc; + + dc_hw_enable_interrupt(&dc->hw); + + return 0; +} + +static void vs_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct vs_drm_device *priv = to_vs_drm_private(crtc->dev); + struct vs_dc *dc = &priv->dc; + + dc_hw_disable_interrupt(&dc->hw); +} + +static const struct drm_crtc_funcs vs_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = vs_crtc_reset, + .atomic_duplicate_state = vs_crtc_atomic_duplicate_state, + .atomic_destroy_state = vs_crtc_atomic_destroy_state, + .enable_vblank = vs_crtc_enable_vblank, + .disable_vblank = vs_crtc_disable_vblank, +}; + +static void vs_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vs_drm_device *priv = to_vs_drm_private(crtc->dev); + struct vs_dc *dc = &priv->dc; + + struct vs_crtc_state *crtc_state = to_vs_crtc_state(crtc->state); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + int id; + + id = to_vs_display_id(crtc); + if (crtc_state->encoder_type == DRM_MODE_ENCODER_DSI) { + dc_hw_set_out(&dc->hw, OUT_DPI, id); + clk_set_rate(priv->clks[7].clk, mode->clock * 1000); + clk_set_parent(priv->clks[5].clk, priv->clks[7].clk); + } else { + dc_hw_set_out(&dc->hw, OUT_DP, id); + clk_set_parent(priv->clks[4].clk, priv->clks[6].clk); + } + + dc_hw_enable(&dc->hw, id, mode, crtc_state->encoder_type, crtc_state->output_fmt); + + enable_irq(priv->irq); + + drm_crtc_vblank_on(crtc); +} + +static void vs_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vs_drm_device *priv = to_vs_drm_private(crtc->dev); + struct vs_dc *dc = &priv->dc; + int id; + + drm_crtc_vblank_off(crtc); + + disable_irq(priv->irq); + + id = to_vs_display_id(crtc); + dc_hw_disable(&dc->hw, id); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static void vs_dc_set_gamma(struct vs_dc *dc, struct drm_crtc *crtc, + struct drm_color_lut *lut, unsigned int size) +{ + u16 i, r, g, b; + u8 bits, id; + + if (size != dc->hw.info->gamma_size) { + drm_err(crtc->dev, "gamma size does not match!\n"); + return; + } + + id = to_vs_display_id(crtc); + + bits = dc->hw.info->gamma_bits; + for (i = 0; i < size; i++) { + r = drm_color_lut_extract(lut[i].red, bits); + g = drm_color_lut_extract(lut[i].green, bits); + b = drm_color_lut_extract(lut[i].blue, bits); + dc_hw_update_gamma(&dc->hw, id, i, r, g, b); + + if (i >= dc->hw.info->gamma_size) + return; + + dc->hw.gamma[id].gamma[i][0] = r; + dc->hw.gamma[id].gamma[i][1] = g; + dc->hw.gamma[id].gamma[i][2] = b; + } +} + +static void vs_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, + crtc); + + struct drm_property_blob *blob = new_state->gamma_lut; + struct drm_color_lut *lut; + struct vs_drm_device *priv = to_vs_drm_private(crtc->dev); + struct vs_dc *dc = &priv->dc; + u8 id; + + dc_hw_enable_shadow_register(dc, false); + + id = to_vs_display_id(crtc); + if (new_state->color_mgmt_changed) { + if (blob && blob->length) { + lut = blob->data; + vs_dc_set_gamma(dc, crtc, lut, + blob->length / sizeof(*lut)); + dc_hw_enable_gamma(&dc->hw, id, true); + } else { + dc_hw_enable_gamma(&dc->hw, id, false); + } + } +} + +static void vs_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + struct vs_drm_device *priv = to_vs_drm_private(crtc->dev); + struct vs_dc *dc = &priv->dc; + + if (event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_arm_vblank_event(crtc, event); + crtc->state->event = NULL; + spin_unlock_irq(&crtc->dev->event_lock); + } + + dc_hw_enable_shadow_register(dc, true); +} + +static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = { + .atomic_check = drm_crtc_helper_atomic_check, + .atomic_enable = vs_crtc_atomic_enable, + .atomic_disable = vs_crtc_atomic_disable, + .atomic_begin = vs_crtc_atomic_begin, + .atomic_flush = vs_crtc_atomic_flush, +}; + +struct vs_crtc *vs_crtc_create(struct drm_device *drm_dev, + struct vs_dc_info *info) +{ + struct vs_crtc *crtc; + int ret; + + if (!info) + return NULL; + + crtc = drmm_crtc_alloc_with_planes(drm_dev, struct vs_crtc, base, NULL, + NULL, &vs_crtc_funcs, + info->name ? info->name : NULL); + + drm_crtc_helper_add(&crtc->base, &vs_crtc_helper_funcs); + + if (info->gamma_size) { + ret = drm_mode_crtc_set_gamma_size(&crtc->base, + info->gamma_size); + if (ret) + return NULL; + + drm_crtc_enable_color_mgmt(&crtc->base, 0, false, + info->gamma_size); + } + + return crtc; +} diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h new file mode 100644 index 000000000000..58aa7a77d94e --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ + +#ifndef __VS_CRTC_H__ +#define __VS_CRTC_H__ + +#include +#include + +#include "vs_type.h" + +struct vs_crtc_state { + struct drm_crtc_state base; + + u32 output_fmt; + u8 encoder_type; + u8 bpp; +}; + +struct vs_crtc { + struct drm_crtc base; + struct device *dev; + u8 index; +}; + +static inline u8 to_vs_display_id(struct drm_crtc *crtc) +{ + return container_of(crtc, struct vs_crtc, base)->index; +} + +struct vs_crtc *vs_crtc_create(struct drm_device *drm_dev, + struct vs_dc_info *info); + +static inline struct vs_crtc_state * +to_vs_crtc_state(struct drm_crtc_state *state) +{ + return container_of(state, struct vs_crtc_state, base); +} + +#endif /* __VS_CRTC_H__ */ diff --git a/drivers/gpu/drm/verisilicon/vs_drv.h b/drivers/gpu/drm/verisilicon/vs_drv.h new file mode 100644 index 000000000000..dc6efb093205 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_drv.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ + +#ifndef __VS_DRV_H__ +#define __VS_DRV_H__ + +#include +#include +#include +#include + +#include + +#include "vs_dc_hw.h" + +/*@pitch_alignment: buffer pitch alignment required by sub-devices.*/ +struct vs_drm_device { + struct drm_device base; + unsigned int pitch_alignment; + /* clocks */ + unsigned int clk_count; + struct clk_bulk_data *clks; + struct reset_control *rsts; + struct vs_dc dc; + int irq; + struct regmap *dc_syscon_regmap; +}; + +static inline struct vs_drm_device * +to_vs_drm_private(const struct drm_device *dev) +{ + return container_of(dev, struct vs_drm_device, base); +} + +#ifdef CONFIG_DRM_INNO_STARFIVE_HDMI +extern struct platform_driver starfive_hdmi_driver; +#endif + +#endif /* __VS_DRV_H__ */ diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index 37f6a4db2a12..1a0e46f38ae8 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -2,6 +2,7 @@ vs_drm-objs := vs_dc_hw.o \ vs_modeset.o \ - vs_crtc.o + vs_crtc.o \ + vs_plane.o obj-$(CONFIG_DRM_VERISILICON_DC8200) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c new file mode 100644 index 000000000000..ba47d0185fc6 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_plane.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "vs_plane.h" +#include "vs_drv.h" +#include "vs_crtc.h" + +static inline struct vs_plane_state * +to_vs_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct vs_plane_state, base); +} + +static inline struct vs_plane *to_vs_plane(struct drm_plane *plane) +{ + return container_of(plane, struct vs_plane, base); +} + +static void vs_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct vs_plane_state *vs_plane_state = to_vs_plane_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + kfree(vs_plane_state); +} + +static void vs_plane_reset(struct drm_plane *plane) +{ + struct vs_plane_state *state; + + if (plane->state) + vs_plane_atomic_destroy_state(plane, plane->state); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + __drm_atomic_helper_plane_reset(plane, &state->base); +} + +static struct drm_plane_state * +vs_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct vs_plane_state *state; + + if (WARN_ON(!plane->state)) + return NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &state->base); + + return &state->base; +} + +static bool vs_format_mod_supported(struct drm_plane *plane, + u32 format, + u64 modifier) +{ + int i; + + /* We always have to allow these modifiers: + * 1. Core DRM checks for LINEAR support if userspace does not provide modifiers. + * 2. Not passing any modifiers is the same as explicitly passing INVALID. + */ + if (modifier == DRM_FORMAT_MOD_LINEAR) + return true; + + /* Check that the modifier is on the list of the plane's supported modifiers. */ + for (i = 0; i < plane->modifier_count; i++) { + if (modifier == plane->modifiers[i]) + break; + } + + if (i == plane->modifier_count) + return false; + + return true; +} + +static const struct drm_plane_funcs vs_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = vs_plane_reset, + .atomic_duplicate_state = vs_plane_atomic_duplicate_state, + .atomic_destroy_state = vs_plane_atomic_destroy_state, + .format_mod_supported = vs_format_mod_supported, +}; + +static unsigned char vs_get_plane_number(struct drm_framebuffer *fb) +{ + const struct drm_format_info *info; + + if (!fb) + return 0; + + info = drm_format_info(fb->format->format); + if (!info || info->num_planes > DRM_FORMAT_MAX_PLANES) + return 0; + + return info->num_planes; +} + +static bool vs_dc_mod_supported(const struct vs_plane_info *vs_info, u64 modifier) +{ + const u64 *mods; + + if (vs_info->type == DRM_PLANE_TYPE_CURSOR) + return 0; + + if (!vs_info->data->modifiers) + return false; + + for (mods = vs_info->data->modifiers; *mods != DRM_FORMAT_MOD_INVALID; mods++) { + if (*mods == modifier) + return true; + } + + return false; +} + +static int vs_common_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); + struct vs_drm_device *priv = to_vs_drm_private(plane->dev); + struct vs_dc *dc = &priv->dc; + struct drm_framebuffer *fb = new_state->fb; + const struct vs_plane_info *plane_info; + struct drm_crtc_state *crtc_state; + + if (!new_state->crtc || !new_state->fb) + return 0; + + plane_info = (struct vs_plane_info *)&dc->hw.info->info[to_vs_plane(plane)->id]; + + if (fb->width < plane_info->data->min_width || + fb->width > plane_info->data->max_width || + fb->height < plane_info->data->min_height || + fb->height > plane_info->data->max_height) + drm_err_once(plane->dev, "buffer size may not support on plane%d.\n", + to_vs_plane(plane)->id); + + if (!vs_dc_mod_supported(plane_info, fb->modifier)) { + drm_err(plane->dev, "unsupported modifier on plane%d.\n", to_vs_plane(plane)->id); + return -EINVAL; + } + + crtc_state = drm_atomic_get_existing_crtc_state(new_state->state, new_state->crtc); + return drm_atomic_helper_check_plane_state(new_state, crtc_state, + plane_info->data->min_scale, + plane_info->data->max_scale, + true, true); +} + +static void vs_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + + unsigned char i, num_planes, display_id, id; + u32 format; + bool is_yuv; + struct vs_plane *vs_plane = to_vs_plane(plane); + struct vs_plane_state *plane_state = to_vs_plane_state(new_state); + struct vs_drm_device *priv = to_vs_drm_private(plane->dev); + struct vs_dc *dc = &priv->dc; + + if (!new_state->fb || !new_state->crtc) + return; + + drm_fb_dma_sync_non_coherent(plane->dev, old_state, new_state); + + num_planes = vs_get_plane_number(new_state->fb); + + for (i = 0; i < num_planes; i++) { + dma_addr_t dma_addr; + + dma_addr = drm_fb_dma_get_gem_addr(new_state->fb, new_state, i); + plane_state->dma_addr[i] = dma_addr; + } + + display_id = to_vs_display_id(new_state->crtc); + format = new_state->fb->format->format; + is_yuv = new_state->fb->format->is_yuv; + id = vs_plane->id; + + dc_plane_hw_update_format_colorspace(dc, format, new_state->color_encoding, id, is_yuv); + if (new_state->visible) + dc_plane_hw_update_address(dc, id, format, plane_state->dma_addr, + new_state->fb, &new_state->src); + dc_plane_hw_update_format(dc, format, new_state->color_encoding, new_state->rotation, + new_state->visible, new_state->zpos, id, display_id); + dc_plane_hw_update_scale(dc, &new_state->src, &new_state->dst, id, + display_id, new_state->rotation); + dc_plane_hw_update_blend(dc, new_state->alpha, new_state->pixel_blend_mode, + id, display_id); +} + +static void vs_cursor_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, + plane); + struct vs_drm_device *priv = to_vs_drm_private(plane->dev); + struct vs_dc *dc = &priv->dc; + unsigned char display_id; + u32 crtc_w, crtc_x, crtc_y; + s32 hotspot_x, hotspot_y; + dma_addr_t dma_addr; + + display_id = to_vs_display_id(new_state->crtc); + + if (!new_state->fb || !new_state->crtc) + return; + + drm_fb_dma_sync_non_coherent(new_state->fb->dev, old_state, new_state); + dma_addr = drm_fb_dma_get_gem_addr(new_state->fb, new_state, 0); + crtc_w = new_state->crtc_w; + + if (new_state->crtc_x > 0) { + crtc_x = new_state->crtc_x; + hotspot_x = 0; + } else { + hotspot_x = -new_state->crtc_x; + crtc_x = 0; + } + if (new_state->crtc_y > 0) { + crtc_y = new_state->crtc_y; + hotspot_y = 0; + } else { + hotspot_y = -new_state->crtc_y; + crtc_y = 0; + } + dc_hw_update_cursor(&dc->hw, display_id, dma_addr, crtc_w, crtc_x, + crtc_y, hotspot_x, hotspot_y); +} + +static void vs_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct vs_plane *vs_plane = to_vs_plane(plane); + struct vs_drm_device *priv = to_vs_drm_private(plane->dev); + struct vs_dc *dc = &priv->dc; + + dc_hw_disable_plane(dc, vs_plane->id); +} + +static void vs_cursor_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_state *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct vs_drm_device *priv = to_vs_drm_private(plane->dev); + struct vs_dc *dc = &priv->dc; + unsigned char display_id; + + display_id = to_vs_display_id(old_state->crtc); + dc_hw_disable_cursor(&dc->hw, display_id); +} + +static const struct drm_plane_helper_funcs vs_primary_plane_helpers = { + .atomic_check = vs_common_plane_atomic_check, + .atomic_update = vs_plane_atomic_update, + .atomic_disable = vs_plane_atomic_disable, +}; + +static const struct drm_plane_helper_funcs vs_overlay_plane_helpers = { + .atomic_check = vs_common_plane_atomic_check, + .atomic_update = vs_plane_atomic_update, + .atomic_disable = vs_plane_atomic_disable, +}; + +static const struct drm_plane_helper_funcs vs_cursor_plane_helpers = { + .atomic_check = vs_common_plane_atomic_check, + .atomic_update = vs_cursor_plane_atomic_update, + .atomic_disable = vs_cursor_plane_atomic_disable, +}; + +struct vs_plane *vs_plane_create(struct drm_device *drm_dev, + struct vs_plane_info *info, + unsigned int layer_num, + unsigned int possible_crtcs) +{ + struct vs_plane *plane; + const struct vs_plane_data *data = info->data; + int ret; + + if (!info) + return NULL; + + plane = drmm_universal_plane_alloc(drm_dev, struct vs_plane, base, + possible_crtcs, + &vs_plane_funcs, + data->formats, data->num_formats, + data->modifiers, info->type, + NULL); + if (IS_ERR(plane)) + return ERR_CAST(plane); + + if (info->type == DRM_PLANE_TYPE_PRIMARY) + drm_plane_helper_add(&plane->base, &vs_primary_plane_helpers); + else if (info->type == DRM_PLANE_TYPE_OVERLAY) + drm_plane_helper_add(&plane->base, &vs_overlay_plane_helpers); + else + drm_plane_helper_add(&plane->base, &vs_cursor_plane_helpers); + + if (data->blend_mode) { + ret = drm_plane_create_alpha_property(&plane->base); + if (ret) + return NULL; + + ret = drm_plane_create_blend_mode_property(&plane->base, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE)); + if (ret) + return NULL; + } + + if (data->color_encoding) { + ret = drm_plane_create_color_properties(&plane->base, data->color_encoding, + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE), + DRM_COLOR_YCBCR_BT709, + DRM_COLOR_YCBCR_LIMITED_RANGE); + if (ret) + return NULL; + } + + if (data->rotation) { + ret = drm_plane_create_rotation_property(&plane->base, + DRM_MODE_ROTATE_0, + data->rotation); + if (ret) + return NULL; + } + + if (data->zpos != 255) { + ret = drm_plane_create_zpos_property(&plane->base, data->zpos, 0, layer_num - 1); + if (ret) + return NULL; + } else { + ret = drm_plane_create_zpos_immutable_property(&plane->base, data->zpos); + if (ret) + return NULL; + } + + return plane; +} diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h new file mode 100644 index 000000000000..60d45b69e30a --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_plane.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ + +#ifndef __VS_PLANE_H__ +#define __VS_PLANE_H__ + +#include + +struct vs_plane_info; + +struct vs_plane_state { + struct drm_plane_state base; + dma_addr_t dma_addr[DRM_FORMAT_MAX_PLANES]; +}; + +struct vs_plane { + struct drm_plane base; + u8 id; +}; + +struct vs_plane *vs_plane_create(struct drm_device *drm_dev, + struct vs_plane_info *info, + unsigned int layer_num, + unsigned int possible_crtcs); +#endif /* __VS_PLANE_H__ */ diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig index 874e8bcde5d5..49648a3a6deb 100644 --- a/drivers/gpu/drm/verisilicon/Kconfig +++ b/drivers/gpu/drm/verisilicon/Kconfig @@ -11,3 +11,22 @@ config DRM_VERISILICON_DC8200 This driver provides VeriSilicon kernel mode setting and buffer management. It does not provide 2D or 3D acceleration. + +config DRM_INNO_STARFIVE_HDMI + bool "Starfive JH7110 specific extensions for Innosilicon HDMI" + depends on DRM_VERISILICON_DC8200 + select DRM_INNO_HDMI + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + help + This configuration option enables support for StarFive SoC + specific extensions for the Innosilicon HDMI driver. + + Enabling this option is essential for proper functionality + of HDMI on JH7110 based SoCs. If you are using a StarFive + JH7110 SoC and require HDMI output functionalities, + you should select this option to ensure that the driver is + compiled with the necessary extensions and dependencies. + + This option may enable additional features and capabilities + specific to StarFive's implementation of the HDMI technology. diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index 1a0e46f38ae8..a602630c2416 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -5,4 +5,5 @@ vs_drm-objs := vs_dc_hw.o \ vs_crtc.o \ vs_plane.o +vs_drm-$(CONFIG_DRM_INNO_STARFIVE_HDMI) += inno_hdmi-starfive.o obj-$(CONFIG_DRM_VERISILICON_DC8200) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/inno_hdmi-starfive.c b/drivers/gpu/drm/verisilicon/inno_hdmi-starfive.c new file mode 100644 index 000000000000..6923f789bcc0 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/inno_hdmi-starfive.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) StarFive Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "inno_hdmi-starfive.h" +#include "vs_crtc.h" + +enum hdmi_clk { + CLK_SYS = 0, + CLK_M, + CLK_B, + CLK_HDMI_NUM +}; + +struct pre_pll_config { + unsigned long pixclock; + unsigned long tmdsclock; + u8 prediv; + u16 fbdiv; + u8 tmds_div_a; + u8 tmds_div_b; + u8 tmds_div_c; + u8 pclk_div_a; + u8 pclk_div_b; + u8 pclk_div_c; + u8 pclk_div_d; + u8 vco_div_5_en; + u32 fracdiv; +}; + +struct post_pll_config { + unsigned long tmdsclock; + u8 prediv; + u16 fbdiv; + u8 postdiv; + u8 post_div_en; + u8 version; +}; + +struct stf_inno_hdmi { + struct drm_encoder encoder; + struct drm_connector *connector; + struct inno_hdmi inno_hdmi; + struct clk_bulk_data clk_hdmi[CLK_HDMI_NUM]; + struct reset_control *tx_rst; + int nclks; + unsigned long tmds_rate; + struct pre_pll_config pre_cfg; + const struct post_pll_config *post_cfg; +}; + +static inline struct stf_inno_hdmi *to_starfive_inno_hdmi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct stf_inno_hdmi, encoder); +} + +static struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder) +{ + struct stf_inno_hdmi *stf_hdmi = to_starfive_inno_hdmi(encoder); + + return &stf_hdmi->inno_hdmi; +} + +static const struct post_pll_config post_pll_cfg_table[] = { + {25200000, 1, 80, 13, 3, 1}, + {27000000, 1, 40, 11, 3, 1}, + {33750000, 1, 40, 11, 3, 1}, + {49000000, 1, 20, 1, 3, 3}, + {241700000, 1, 20, 1, 3, 3}, + {297000000, 4, 20, 0, 0, 3}, + { /* sentinel */ } +}; + +static int inno_hdmi_starfive_enable_clk_rst(struct device *dev, + struct stf_inno_hdmi *stf_inno_hdmi) +{ + int ret; + + ret = clk_bulk_prepare_enable(stf_inno_hdmi->nclks, stf_inno_hdmi->clk_hdmi); + if (ret) { + dev_err(dev, "failed to enable clocks\n"); + return ret; + } + + ret = reset_control_deassert(stf_inno_hdmi->tx_rst); + if (ret < 0) { + dev_err(dev, "failed to deassert tx_rst\n"); + return ret; + } + return 0; +} + +static void inno_hdmi_starfive_disable_clk_rst(struct device *dev, + struct stf_inno_hdmi *stf_inno_hdmi) +{ + int ret; + + ret = reset_control_assert(stf_inno_hdmi->tx_rst); + if (ret < 0) + dev_err(dev, "failed to assert tx_rst\n"); + + clk_bulk_disable_unprepare(stf_inno_hdmi->nclks, stf_inno_hdmi->clk_hdmi); +} + +static void inno_hdmi_starfive_config_pll(struct stf_inno_hdmi *stf_inno_hdmi) +{ + u32 val; + struct inno_hdmi *hdmi; + + hdmi = &stf_inno_hdmi->inno_hdmi; + u8 reg_1ad_value = stf_inno_hdmi->post_cfg->post_div_en ? + stf_inno_hdmi->post_cfg->postdiv : 0x00; + u8 reg_1aa_value = stf_inno_hdmi->post_cfg->post_div_en ? + 0x0e : 0x02; + + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_CONTROL, STF_INNO_PRE_PLL_POWER_DOWN); + hdmi_writeb(hdmi, STF_INNO_POST_PLL_DIV_1, + STF_INNO_POST_PLL_POST_DIV_ENABLE | + STF_INNO_POST_PLL_REFCLK_SEL_TMDS | + STF_INNO_POST_PLL_POWER_DOWN); + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_DIV_1, + STF_INNO_PRE_PLL_PRE_DIV(stf_inno_hdmi->pre_cfg.prediv)); + + val = STF_INNO_SPREAD_SPECTRUM_MOD_DISABLE | STF_INNO_SPREAD_SPECTRUM_MOD_DOWN; + if (!stf_inno_hdmi->pre_cfg.fracdiv) + val |= STF_INNO_PRE_PLL_FRAC_DIV_DISABLE; + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_DIV_2, + STF_INNO_PRE_PLL_FB_DIV_11_8(stf_inno_hdmi->pre_cfg.fbdiv) | val); + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_DIV_3, + STF_INNO_PRE_PLL_FB_DIV_7_0(stf_inno_hdmi->pre_cfg.fbdiv)); + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_DIV_4, + STF_INNO_PRE_PLL_TMDSCLK_DIV_C(stf_inno_hdmi->pre_cfg.tmds_div_c) | + STF_INNO_PRE_PLL_TMDSCLK_DIV_A(stf_inno_hdmi->pre_cfg.tmds_div_a) | + STF_INNO_PRE_PLL_TMDSCLK_DIV_B(stf_inno_hdmi->pre_cfg.tmds_div_b)); + + if (stf_inno_hdmi->pre_cfg.fracdiv) { + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_FRAC_DIV_L, + STF_INNO_PRE_PLL_FRAC_DIV_7_0(stf_inno_hdmi->pre_cfg.fracdiv)); + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_FRAC_DIV_M, + STF_INNO_PRE_PLL_FRAC_DIV_15_8(stf_inno_hdmi->pre_cfg.fracdiv)); + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_FRAC_DIV_H, + STF_INNO_PRE_PLL_FRAC_DIV_23_16(stf_inno_hdmi->pre_cfg.fracdiv)); + } + + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_DIV_5, + STF_INNO_PRE_PLL_PCLK_DIV_A(stf_inno_hdmi->pre_cfg.pclk_div_a) | + STF_INNO_PRE_PLL_PCLK_DIV_B(stf_inno_hdmi->pre_cfg.pclk_div_b)); + hdmi_writeb(hdmi, STF_INNO_PRE_PLL_DIV_6, + STF_INNO_PRE_PLL_PCLK_DIV_C(stf_inno_hdmi->pre_cfg.pclk_div_c) | + STF_INNO_PRE_PLL_PCLK_DIV_D(stf_inno_hdmi->pre_cfg.pclk_div_d)); + + /*pre-pll power down*/ + hdmi_modb(hdmi, STF_INNO_PRE_PLL_CONTROL, STF_INNO_PRE_PLL_POWER_DOWN, 0); + + hdmi_modb(hdmi, STF_INNO_POST_PLL_DIV_2, STF_INNO_POST_PLL_Pre_DIV_MASK, + STF_INNO_POST_PLL_PRE_DIV(stf_inno_hdmi->post_cfg->prediv)); + hdmi_writeb(hdmi, STF_INNO_POST_PLL_DIV_3, stf_inno_hdmi->post_cfg->fbdiv & 0xff); + hdmi_writeb(hdmi, STF_INNO_POST_PLL_DIV_4, reg_1ad_value); + hdmi_writeb(hdmi, STF_INNO_POST_PLL_DIV_1, reg_1aa_value); +} + +static void inno_hdmi_starfive_tmds_driver_on(struct inno_hdmi *hdmi) +{ + hdmi_modb(hdmi, STF_INNO_TMDS_CONTROL, + STF_INNO_TMDS_DRIVER_ENABLE, STF_INNO_TMDS_DRIVER_ENABLE); +} + +static void inno_hdmi_starfive_sync_tmds(struct inno_hdmi *hdmi) +{ + /*first send 0 to this bit, then send 1 and keep 1 into this bit*/ + hdmi_writeb(hdmi, HDMI_SYNC, 0x0); + hdmi_writeb(hdmi, HDMI_SYNC, 0x1); +} + +static void inno_hdmi_starfive_phy_get_pre_pll_cfg(struct stf_inno_hdmi *hdmi) +{ + if (hdmi->tmds_rate > 30000000) { + hdmi->pre_cfg.pixclock = hdmi->tmds_rate; + hdmi->pre_cfg.tmdsclock = hdmi->tmds_rate; + hdmi->pre_cfg.prediv = 1; + hdmi->pre_cfg.fbdiv = hdmi->tmds_rate / 3000000; + hdmi->pre_cfg.tmds_div_a = 0; + hdmi->pre_cfg.tmds_div_b = 1; + hdmi->pre_cfg.tmds_div_c = 1; + hdmi->pre_cfg.pclk_div_a = 1; + hdmi->pre_cfg.pclk_div_b = 0; + hdmi->pre_cfg.pclk_div_c = 2; + hdmi->pre_cfg.pclk_div_d = 2; + hdmi->pre_cfg.vco_div_5_en = hdmi->tmds_rate % 3000000 ? 1 : 0; + + if (hdmi->pre_cfg.vco_div_5_en) { + hdmi->pre_cfg.fracdiv = (hdmi->tmds_rate % 3000000) * + 0xffffff / 1000000; + } + } else { + hdmi->pre_cfg.pixclock = hdmi->tmds_rate; + hdmi->pre_cfg.tmdsclock = hdmi->tmds_rate; + hdmi->pre_cfg.prediv = 1; + hdmi->pre_cfg.fbdiv = hdmi->tmds_rate / 1000000; + hdmi->pre_cfg.tmds_div_a = 2; + hdmi->pre_cfg.tmds_div_b = 1; + hdmi->pre_cfg.tmds_div_c = 1; + hdmi->pre_cfg.pclk_div_a = 3; + hdmi->pre_cfg.pclk_div_b = 0; + hdmi->pre_cfg.pclk_div_c = 3; + hdmi->pre_cfg.pclk_div_d = 4; + hdmi->pre_cfg.vco_div_5_en = hdmi->tmds_rate % 1000000 ? 1 : 0; + + if (hdmi->pre_cfg.vco_div_5_en) { + hdmi->pre_cfg.fracdiv = (hdmi->tmds_rate % 1000000) * + 0xffffff / 1000000; + } + } +} + +static int inno_hdmi_starfive_phy_clk_set_rate(struct stf_inno_hdmi *stf_inno_hdmi) +{ + stf_inno_hdmi->post_cfg = post_pll_cfg_table; + + inno_hdmi_starfive_phy_get_pre_pll_cfg(stf_inno_hdmi); + + for (; stf_inno_hdmi->post_cfg->tmdsclock != 0; stf_inno_hdmi->post_cfg++) + if (stf_inno_hdmi->tmds_rate <= stf_inno_hdmi->post_cfg->tmdsclock) + break; + + inno_hdmi_starfive_config_pll(stf_inno_hdmi); + + return 0; +} + +static int inno_hdmi_starfive_config_video_timing(struct inno_hdmi *hdmi, + struct drm_display_mode *mode) +{ + int value; + + /* Set detail external video timing polarity and interlace mode */ + value = v_EXTERANL_VIDEO(1); + + value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? + v_HSYNC_POLARITY_SF(1) : v_HSYNC_POLARITY_SF(0); + value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? + v_VSYNC_POLARITY_SF(1) : v_VSYNC_POLARITY_SF(0); + + value |= mode->flags & DRM_MODE_FLAG_INTERLACE ? + v_INETLACE(1) : v_INETLACE(0); + hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value); + + /* Set detail external video timing */ + value = mode->htotal; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF); + + value = mode->htotal - mode->hdisplay; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF); + + value = mode->htotal - mode->hsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF); + + value = mode->hsync_end - mode->hsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF); + + value = mode->vtotal; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF); + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF); + + value = mode->vtotal - mode->vdisplay; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF); + + value = mode->vtotal - mode->vsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF); + + value = mode->vsync_end - mode->vsync_start; + hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF); + + return 0; +} + +static void inno_hdmi_starfive_sys_power(struct inno_hdmi *hdmi, bool enable) +{ + if (enable) + hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON); + else + hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF); +} + +static int inno_hdmi_starfive_setup(struct inno_hdmi *hdmi, struct drm_display_mode *mode) +{ + struct stf_inno_hdmi *stf_inno_hdmi = dev_get_drvdata(hdmi->dev); + int ret; + u32 val; + + hdmi_modb(hdmi, STF_INNO_BIAS_CONTROL, STF_INNO_BIAS_ENABLE, STF_INNO_BIAS_ENABLE); + hdmi_writeb(hdmi, STF_INNO_RX_CONTROL, STF_INNO_RX_ENABLE); + + stf_inno_hdmi->tmds_rate = mode->clock * 1000; + inno_hdmi_starfive_phy_clk_set_rate(stf_inno_hdmi); + + ret = readx_poll_timeout(readl_relaxed, + hdmi->regs + (STF_INNO_PRE_PLL_LOCK_STATUS) * 0x04, + val, val & 0x1, 1000, 100000); + if (ret < 0) { + dev_err(hdmi->dev, "failed to wait pre-pll lock\n"); + return ret; + } + + ret = readx_poll_timeout(readl_relaxed, + hdmi->regs + (STF_INNO_POST_PLL_LOCK_STATUS) * 0x04, + val, val & 0x1, 1000, 100000); + if (ret < 0) { + dev_err(hdmi->dev, "failed to wait post-pll lock\n"); + return ret; + } + + /*turn on LDO*/ + hdmi_writeb(hdmi, STF_INNO_LDO_CONTROL, STF_INNO_LDO_ENABLE); + /*turn on serializer*/ + hdmi_writeb(hdmi, STF_INNO_SERIALIER_CONTROL, STF_INNO_SERIALIER_ENABLE); + + inno_hdmi_starfive_sys_power(hdmi, false); + inno_hdmi_starfive_config_video_timing(hdmi, mode); + inno_hdmi_starfive_sys_power(hdmi, true); + + inno_hdmi_starfive_tmds_driver_on(hdmi); + inno_hdmi_starfive_sync_tmds(hdmi); + + return 0; +} + +static enum drm_mode_status +inno_hdmi_starfive_mode_valid(struct inno_hdmi *hdmi, const struct drm_display_mode *mode) +{ + int pclk = mode->clock * 1000; + bool valid = false; + + if (pclk <= PIXCLOCK_4K_30FPS) + valid = true; + + return (valid) ? MODE_OK : MODE_BAD; +} + +static void inno_hdmi_starfive_encoder_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct stf_inno_hdmi *sft_hdmi = to_starfive_inno_hdmi(encoder); + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + + conn_state = drm_atomic_get_new_connector_state(state, sft_hdmi->connector); + if (WARN_ON(!conn_state)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + return; + + inno_hdmi_starfive_setup(&sft_hdmi->inno_hdmi, &crtc_state->adjusted_mode); +} + +static void inno_hdmi_starfive_encoder_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); + + inno_hdmi_starfive_sys_power(hdmi, false); +} + +static int +inno_hdmi_starfive_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct vs_crtc_state *vs_crtc_state = to_vs_crtc_state(crtc_state); + + vs_crtc_state->encoder_type = encoder->encoder_type; + vs_crtc_state->output_fmt = MEDIA_BUS_FMT_RGB888_1X24; + + return 0; +} + +static const struct drm_encoder_helper_funcs stf_inno_encoder_helper_funcs = { + .atomic_check = inno_hdmi_starfive_encoder_atomic_check, + .atomic_enable = inno_hdmi_starfive_encoder_enable, + .atomic_disable = inno_hdmi_starfive_encoder_disable, +}; + +static int inno_hdmi_starfive_get_clk_rst(struct device *dev, struct stf_inno_hdmi *stf_hdmi) +{ + int ret; + + stf_hdmi->nclks = ARRAY_SIZE(stf_hdmi->clk_hdmi); + + ret = devm_clk_bulk_get(dev, stf_hdmi->nclks, stf_hdmi->clk_hdmi); + if (ret) { + dev_err(dev, "Failed to get clk controls\n"); + return ret; + } + + stf_hdmi->tx_rst = devm_reset_control_get_by_index(dev, 0); + if (IS_ERR(stf_hdmi->tx_rst)) { + dev_err(dev, "failed to get tx_rst reset\n"); + return PTR_ERR(stf_hdmi->tx_rst); + } + + return 0; +} + +static int inno_hdmi_starfive_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = dev_get_drvdata(master); + struct stf_inno_hdmi *stf_hdmi; + struct inno_hdmi *hdmi; + struct drm_encoder *encoder; + + int ret; + unsigned long long rate; + + stf_hdmi = drmm_simple_encoder_alloc(drm, struct stf_inno_hdmi, + encoder, DRM_MODE_ENCODER_TMDS); + if (IS_ERR(stf_hdmi)) + return PTR_ERR(stf_hdmi); + + hdmi = &stf_hdmi->inno_hdmi; + hdmi->dev = dev; + + hdmi->plat_data = (struct inno_hdmi_plat_data *)of_device_get_match_data(dev); + if (!hdmi->plat_data) + return -ENODEV; + + hdmi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hdmi->regs)) + return PTR_ERR(hdmi->regs); + + ret = inno_hdmi_starfive_get_clk_rst(dev, stf_hdmi); + if (ret < 0) + return ret; + + ret = inno_hdmi_starfive_enable_clk_rst(dev, stf_hdmi); + if (ret) + return ret; + + rate = clk_get_rate(stf_hdmi->clk_hdmi[CLK_SYS].clk); + inno_hdmi_i2c_init(hdmi, rate); + + ret = inno_hdmi_bind(drm, hdmi, &stf_hdmi->encoder); + if (ret) + goto err_disable_clk; + + encoder = &stf_hdmi->encoder; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + drm_encoder_helper_add(encoder, &stf_inno_encoder_helper_funcs); + + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + stf_hdmi->connector = drm_bridge_connector_init(drm, encoder); + if (IS_ERR(stf_hdmi->connector)) { + dev_err(dev, "Unable to create bridge connector\n"); + ret = PTR_ERR(stf_hdmi->connector); + return ret; + } + + drm_connector_attach_encoder(stf_hdmi->connector, encoder); + + dev_set_drvdata(dev, stf_hdmi); + + return 0; + +err_disable_clk: + inno_hdmi_starfive_disable_clk_rst(dev, stf_hdmi); + return ret; +} + +static void inno_hdmi_starfive_unbind(struct device *dev, struct device *master, void *data) +{ + struct stf_inno_hdmi *stf_hdmi = dev_get_drvdata(dev); + + inno_hdmi_starfive_disable_clk_rst(dev, stf_hdmi); +} + +static const struct component_ops inno_hdmi_starfive_ops = { + .bind = inno_hdmi_starfive_bind, + .unbind = inno_hdmi_starfive_unbind, +}; + +static int inno_hdmi_starfive_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &inno_hdmi_starfive_ops); +} + +static void inno_hdmi_starfive_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &inno_hdmi_starfive_ops); +} + +static const struct inno_hdmi_plat_data stf_inno_info = { + .vendor = "STARFIVE", + .product = "INNO HDMI", + .mode_valid = inno_hdmi_starfive_mode_valid, +}; + +static const struct of_device_id starfive_hdmi_dt_ids[] = { + { .compatible = "starfive,jh7110-inno-hdmi", .data = &stf_inno_info,}, + {}, +}; +MODULE_DEVICE_TABLE(of, starfive_hdmi_dt_ids); + +struct platform_driver starfive_hdmi_driver = { + .probe = inno_hdmi_starfive_probe, + .remove = inno_hdmi_starfive_remove, + .driver = { + .name = "starfive-hdmi", + .of_match_table = starfive_hdmi_dt_ids, + }, +}; + +MODULE_AUTHOR("StarFive Corporation"); +MODULE_DESCRIPTION("Starfive Specific INNO-HDMI Driver"); diff --git a/drivers/gpu/drm/verisilicon/inno_hdmi-starfive.h b/drivers/gpu/drm/verisilicon/inno_hdmi-starfive.h new file mode 100644 index 000000000000..db4d4578490d --- /dev/null +++ b/drivers/gpu/drm/verisilicon/inno_hdmi-starfive.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) StarFive Technology Co., Ltd. + */ + +#ifndef __STARFIVE_HDMI_H__ +#define __STARFIVE_HDMI_H__ + +#include +#include + +#define HDMI_SYNC 0xce + +#define UPDATE(x, h, l) FIELD_PREP(GENMASK(h, l), x) +#define HDMI_SYS_CTRL 0x00 +#define m_RST_ANALOG (1 << 6) +#define v_RST_ANALOG (0 << 6) +#define v_NOT_RST_ANALOG (1 << 6) +#define m_RST_DIGITAL (1 << 5) +#define v_RST_DIGITAL (0 << 5) +#define v_NOT_RST_DIGITAL (1 << 5) +#define m_REG_CLK_INV (1 << 4) +#define v_REG_CLK_NOT_INV (0 << 4) +#define v_REG_CLK_INV (1 << 4) +#define m_VCLK_INV (1 << 3) +#define v_VCLK_NOT_INV (0 << 3) +#define v_VCLK_INV (1 << 3) +#define m_REG_CLK_SOURCE (1 << 2) +#define v_REG_CLK_SOURCE_TMDS (0 << 2) +#define v_REG_CLK_SOURCE_SYS (1 << 2) +#define m_POWER (1 << 1) +#define v_PWR_ON (0 << 1) +#define v_PWR_OFF (1 << 1) +#define m_INT_POL (1 << 0) +#define v_INT_POL_HIGH 1 +#define v_INT_POL_LOW 0 + +#define HDMI_VIDEO_TIMING_CTL 0x08 +#define v_VSYNC_POLARITY_SF(n) ((n) << 3) +#define v_HSYNC_POLARITY_SF(n) ((n) << 2) +#define v_INETLACE(n) ((n) << 1) +#define v_EXTERANL_VIDEO(n) ((n) << 0) + +#define HDMI_VIDEO_EXT_HTOTAL_L 0x09 +#define HDMI_VIDEO_EXT_HTOTAL_H 0x0a +#define HDMI_VIDEO_EXT_HBLANK_L 0x0b +#define HDMI_VIDEO_EXT_HBLANK_H 0x0c +#define HDMI_VIDEO_EXT_HDELAY_L 0x0d +#define HDMI_VIDEO_EXT_HDELAY_H 0x0e +#define HDMI_VIDEO_EXT_HDURATION_L 0x0f +#define HDMI_VIDEO_EXT_HDURATION_H 0x10 +#define HDMI_VIDEO_EXT_VTOTAL_L 0x11 +#define HDMI_VIDEO_EXT_VTOTAL_H 0x12 +#define HDMI_VIDEO_EXT_VBLANK 0x13 +#define HDMI_VIDEO_EXT_VDELAY 0x14 +#define HDMI_VIDEO_EXT_VDURATION 0x15 + +/* REG: 0x1a0 */ +#define STF_INNO_PRE_PLL_CONTROL 0x1a0 +#define STF_INNO_PCLK_VCO_DIV_5_MASK BIT(1) +#define STF_INNO_PCLK_VCO_DIV_5(x) UPDATE(x, 1, 1) +#define STF_INNO_PRE_PLL_POWER_DOWN BIT(0) + +/* REG: 0x1a1 */ +#define STF_INNO_PRE_PLL_DIV_1 0x1a1 +#define STF_INNO_PRE_PLL_PRE_DIV_MASK GENMASK(5, 0) +#define STF_INNO_PRE_PLL_PRE_DIV(x) UPDATE(x, 5, 0) + +/* REG: 0x1a2 */ +#define STF_INNO_PRE_PLL_DIV_2 0x1a2 +#define STF_INNO_SPREAD_SPECTRUM_MOD_DOWN BIT(7) +#define STF_INNO_SPREAD_SPECTRUM_MOD_DISABLE BIT(6) +#define STF_INNO_PRE_PLL_FRAC_DIV_DISABLE UPDATE(3, 5, 4) +#define STF_INNO_PRE_PLL_FB_DIV_11_8_MASK GENMASK(3, 0) +#define STF_INNO_PRE_PLL_FB_DIV_11_8(x) UPDATE((x) >> 8, 3, 0) + +/* REG: 0x1a3 */ +#define STF_INNO_PRE_PLL_DIV_3 0x1a3 +#define STF_INNO_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) + +/* REG: 0x1a4*/ +#define STF_INNO_PRE_PLL_DIV_4 0x1a4 +#define STF_INNO_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(1, 0) +#define STF_INNO_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 1, 0) +#define STF_INNO_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(3, 2) +#define STF_INNO_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 3, 2) +#define STF_INNO_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(5, 4) +#define STF_INNO_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 5, 4) + +/* REG: 0x1a5 */ +#define STF_INNO_PRE_PLL_DIV_5 0x1a5 +#define STF_INNO_PRE_PLL_PCLK_DIV_B_SHIFT 5 +#define STF_INNO_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5) +#define STF_INNO_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5) +#define STF_INNO_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0) +#define STF_INNO_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0) + +/* REG: 0x1a6 */ +#define STF_INNO_PRE_PLL_DIV_6 0x1a6 +#define STF_INNO_PRE_PLL_PCLK_DIV_C_SHIFT 5 +#define STF_INNO_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5) +#define STF_INNO_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5) +#define STF_INNO_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0) +#define STF_INNO_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0) + +/* REG: 0x1a9 */ +#define STF_INNO_PRE_PLL_LOCK_STATUS 0x1a9 + +/* REG: 0x1aa */ +#define STF_INNO_POST_PLL_DIV_1 0x1aa +#define STF_INNO_POST_PLL_POST_DIV_ENABLE GENMASK(3, 2) +#define STF_INNO_POST_PLL_REFCLK_SEL_TMDS BIT(1) +#define STF_INNO_POST_PLL_POWER_DOWN BIT(0) +#define STF_INNO_POST_PLL_FB_DIV_8(x) UPDATE(((x) >> 8) << 4, 4, 4) + +/* REG:0x1ab */ +#define STF_INNO_POST_PLL_DIV_2 0x1ab +#define STF_INNO_POST_PLL_Pre_DIV_MASK GENMASK(5, 0) +#define STF_INNO_POST_PLL_PRE_DIV(x) UPDATE(x, 5, 0) + +/* REG: 0x1ac */ +#define STF_INNO_POST_PLL_DIV_3 0x1ac +#define STF_INNO_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) + +/* REG: 0x1ad */ +#define STF_INNO_POST_PLL_DIV_4 0x1ad +#define STF_INNO_POST_PLL_POST_DIV_MASK GENMASK(2, 0) +#define STF_INNO_POST_PLL_POST_DIV_2 0x0 +#define STF_INNO_POST_PLL_POST_DIV_4 0x1 +#define STF_INNO_POST_PLL_POST_DIV_8 0x3 + +/* REG: 0x1af */ +#define STF_INNO_POST_PLL_LOCK_STATUS 0x1af + +/* REG: 0x1b0 */ +#define STF_INNO_BIAS_CONTROL 0x1b0 +#define STF_INNO_BIAS_ENABLE BIT(2) + +/* REG: 0x1b2 */ +#define STF_INNO_TMDS_CONTROL 0x1b2 +#define STF_INNO_TMDS_CLK_DRIVER_EN BIT(3) +#define STF_INNO_TMDS_D2_DRIVER_EN BIT(2) +#define STF_INNO_TMDS_D1_DRIVER_EN BIT(1) +#define STF_INNO_TMDS_D0_DRIVER_EN BIT(0) +#define STF_INNO_TMDS_DRIVER_ENABLE (STF_INNO_TMDS_CLK_DRIVER_EN | \ + STF_INNO_TMDS_D2_DRIVER_EN | \ + STF_INNO_TMDS_D1_DRIVER_EN | \ + STF_INNO_TMDS_D0_DRIVER_EN) + +/* REG: 0x1b4 */ +#define STF_INNO_LDO_CONTROL 0x1b4 +#define STF_INNO_LDO_D2_EN BIT(2) +#define STF_INNO_LDO_D1_EN BIT(1) +#define STF_INNO_LDO_D0_EN BIT(0) +#define STF_INNO_LDO_ENABLE (STF_INNO_LDO_D2_EN | \ + STF_INNO_LDO_D1_EN | \ + STF_INNO_LDO_D0_EN) + +/* REG: 0x1be */ +#define STF_INNO_SERIALIER_CONTROL 0x1be +#define STF_INNO_SERIALIER_D2_EN BIT(6) +#define STF_INNO_SERIALIER_D1_EN BIT(5) +#define STF_INNO_SERIALIER_D0_EN BIT(4) +#define STF_INNO_SERIALIER_EN BIT(0) + +#define STF_INNO_SERIALIER_ENABLE (STF_INNO_SERIALIER_D2_EN | \ + STF_INNO_SERIALIER_D1_EN | \ + STF_INNO_SERIALIER_D0_EN | \ + STF_INNO_SERIALIER_EN) + +/* REG: 0x1cc */ +#define STF_INNO_RX_CONTROL 0x1cc +#define STF_INNO_RX_EN BIT(3) +#define STF_INNO_RX_CHANNEL_2_EN BIT(2) +#define STF_INNO_RX_CHANNEL_1_EN BIT(1) +#define STF_INNO_RX_CHANNEL_0_EN BIT(0) +#define STF_INNO_RX_ENABLE (STF_INNO_RX_EN | \ + STF_INNO_RX_CHANNEL_2_EN | \ + STF_INNO_RX_CHANNEL_1_EN | \ + STF_INNO_RX_CHANNEL_0_EN) + +/* REG: 0x1d1 */ +#define STF_INNO_PRE_PLL_FRAC_DIV_H 0x1d1 +#define STF_INNO_PRE_PLL_FRAC_DIV_23_16(x) UPDATE((x) >> 16, 7, 0) +/* REG: 0x1d2 */ +#define STF_INNO_PRE_PLL_FRAC_DIV_M 0x1d2 +#define STF_INNO_PRE_PLL_FRAC_DIV_15_8(x) UPDATE((x) >> 8, 7, 0) +/* REG: 0x1d3 */ +#define STF_INNO_PRE_PLL_FRAC_DIV_L 0x1d3 +#define STF_INNO_PRE_PLL_FRAC_DIV_7_0(x) UPDATE(x, 7, 0) + +#define PIXCLOCK_4K_30FPS 297000000 + +#endif /* __STARFIVE_HDMI_H__ */ diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index a602630c2416..67c94518d810 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -3,7 +3,8 @@ vs_drm-objs := vs_dc_hw.o \ vs_modeset.o \ vs_crtc.o \ - vs_plane.o + vs_plane.o \ + vs_drv.o vs_drm-$(CONFIG_DRM_INNO_STARFIVE_HDMI) += inno_hdmi-starfive.o obj-$(CONFIG_DRM_VERISILICON_DC8200) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/vs_drv.c b/drivers/gpu/drm/verisilicon/vs_drv.c new file mode 100644 index 000000000000..830dd0b1e9a0 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_drv.c @@ -0,0 +1,777 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) VeriSilicon Holdings Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vs_drv.h" +#include "vs_crtc.h" +#include "vs_plane.h" +#include "vs_modeset.h" + +#define DRV_NAME "verisilicon" +#define DRV_DESC "Verisilicon DRM driver" +#define DRV_DATE "20230516" +#define DRV_MAJOR 1 +#define DRV_MINOR 0 + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +static const u32 primary_overlay_format[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, + DRM_FORMAT_YVU420, + DRM_FORMAT_YUV420, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_P010, +}; + +static const u32 cursor_formats[] = { + DRM_FORMAT_ARGB8888 +}; + +static const u64 format_modifier[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static const u64 secondary_format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static const struct vs_plane_data vs_plane_pri0 = { + .num_formats = ARRAY_SIZE(primary_overlay_format), + .formats = primary_overlay_format, + .num_modifiers = ARRAY_SIZE(format_modifier), + .modifiers = format_modifier, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + .rotation = DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | BIT(DRM_COLOR_YCBCR_BT2020), + .zpos = 0, +}; + +static const struct vs_plane_data vs_plane_pri1 = { + .num_formats = ARRAY_SIZE(primary_overlay_format), + .formats = primary_overlay_format, + .num_modifiers = ARRAY_SIZE(format_modifier), + .modifiers = format_modifier, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + .rotation = DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | BIT(DRM_COLOR_YCBCR_BT2020), + .zpos = 3, +}; + +static const struct vs_plane_data vs_plane_over0 = { + .num_formats = ARRAY_SIZE(primary_overlay_format), + .formats = primary_overlay_format, + .num_modifiers = ARRAY_SIZE(format_modifier), + .modifiers = format_modifier, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + .rotation = DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | BIT(DRM_COLOR_YCBCR_BT2020), + .zpos = 1, +}; + +static const struct vs_plane_data vs_plane_over1 = { + .num_formats = ARRAY_SIZE(primary_overlay_format), + .formats = primary_overlay_format, + .num_modifiers = ARRAY_SIZE(secondary_format_modifiers), + .modifiers = secondary_format_modifiers, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .min_scale = DRM_PLANE_NO_SCALING, + .max_scale = DRM_PLANE_NO_SCALING, + .rotation = 0, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | BIT(DRM_COLOR_YCBCR_BT2020), + .zpos = 2, +}; + +static const struct vs_plane_data vs_plane_over2 = { + .num_formats = ARRAY_SIZE(primary_overlay_format), + .formats = primary_overlay_format, + .num_modifiers = ARRAY_SIZE(format_modifier), + .modifiers = format_modifier, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + .rotation = DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | BIT(DRM_COLOR_YCBCR_BT2020), + .zpos = 4, +}; + +static const struct vs_plane_data vs_plane_over3 = { + .num_formats = ARRAY_SIZE(primary_overlay_format), + .formats = primary_overlay_format, + .num_modifiers = ARRAY_SIZE(format_modifier), + .modifiers = format_modifier, + .min_width = 0, + .min_height = 0, + .max_width = 4096, + .max_height = 4096, + .min_scale = FRAC_16_16(1, 3), + .max_scale = FRAC_16_16(10, 1), + .rotation = DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y, + .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) | BIT(DRM_COLOR_YCBCR_BT2020), + .zpos = 5, +}; + +static const struct vs_plane_data vs_plane_cur0 = { + .num_formats = ARRAY_SIZE(cursor_formats), + .formats = cursor_formats, + .min_width = 32, + .min_height = 32, + .max_width = 64, + .max_height = 64, + .min_scale = DRM_PLANE_NO_SCALING, + .max_scale = DRM_PLANE_NO_SCALING, + .zpos = 255, +}; + +static const struct vs_plane_data vs_plane_cur1 = { + .num_formats = ARRAY_SIZE(cursor_formats), + .formats = cursor_formats, + .min_width = 32, + .min_height = 32, + .max_width = 64, + .max_height = 64, + .zpos = 255, +}; + +static const struct vs_plane_info info[] = { + {.id = PRIMARY_PLANE_0, .data = &vs_plane_pri0, + .type = DRM_PLANE_TYPE_PRIMARY}, + {.id = OVERLAY_PLANE_0, .data = &vs_plane_over0, + .type = DRM_PLANE_TYPE_OVERLAY}, + {.id = OVERLAY_PLANE_1, .data = &vs_plane_over1, + .type = DRM_PLANE_TYPE_OVERLAY}, + {.id = PRIMARY_PLANE_1, .data = &vs_plane_pri1, + .type = DRM_PLANE_TYPE_PRIMARY}, + {.id = OVERLAY_PLANE_2, .data = &vs_plane_over2, + .type = DRM_PLANE_TYPE_OVERLAY}, + {.id = OVERLAY_PLANE_3, .data = &vs_plane_over3, + .type = DRM_PLANE_TYPE_OVERLAY}, + {.id = CURSOR_PLANE_0, .data = &vs_plane_cur0, + .type = DRM_PLANE_TYPE_CURSOR}, + {.id = CURSOR_PLANE_1, .data = &vs_plane_cur1, + .type = DRM_PLANE_TYPE_CURSOR}, +}; + +static const struct vs_dc_info dc8200_info = { + .name = "DC8200", + .plane_num = ARRAY_SIZE(info), + .panel_num = 2, + .info = info, + .layer_num = 6, + .gamma_size = GAMMA_EX_SIZE, + .gamma_bits = 12, + .pitch_alignment = 128, +}; + +#define STARFIVE_SOC_CON8 0x08 +# define STARFIVE_MIPI_SEL BIT(3) + +static int vs_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct vs_drm_device *priv = to_vs_drm_private(dev); + unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + + args->pitch = ALIGN(pitch, priv->pitch_alignment); + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +DEFINE_DRM_GEM_FOPS(vs_drm_fops); + +static struct drm_driver vs_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM, + + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create), + + .fops = &vs_drm_fops, + .name = DRV_NAME, + .desc = DRV_DESC, + .date = DRV_DATE, + .major = DRV_MAJOR, + .minor = DRV_MINOR, +}; + +static irqreturn_t vs_dc_isr(int irq, void *data) +{ + struct vs_drm_device *priv = data; + struct vs_dc *dc = &priv->dc; + u8 status = 0; + + dc_hw_get_interrupt(&dc->hw, &status); + + if (status & BIT(0)) + drm_crtc_handle_vblank(&dc->crtc[0]->base); + + if (status & BIT(1)) + drm_crtc_handle_vblank(&dc->crtc[1]->base); + + return IRQ_HANDLED; +} + +static int vs_drm_device_init_res(struct vs_drm_device *priv) +{ + struct device *dev = priv->base.dev; + struct platform_device *pdev = to_platform_device(dev); + int ret; + struct vs_dc *dc; + + dc = &priv->dc; + dc->hw.hi_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dc->hw.hi_base)) + return PTR_ERR(dc->hw.hi_base); + + dc->hw.reg_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(dc->hw.reg_base)) + return PTR_ERR(dc->hw.reg_base); + + dc->hw.info = (struct vs_dc_info *)of_device_get_match_data(dev); + + ret = devm_clk_bulk_get_all(dev, &priv->clks); + if (ret < 0) { + dev_err(dev, "can't get vout clock, ret=%d\n", ret); + return ret; + } + priv->clk_count = ret; + + priv->rsts = devm_reset_control_array_get_shared(dev); + if (IS_ERR(priv->rsts)) + return PTR_ERR(priv->rsts); + + priv->irq = platform_get_irq(pdev, 0); + + /* do not autoenable, will be enabled later */ + ret = devm_request_irq(dev, priv->irq, vs_dc_isr, IRQF_NO_AUTOEN, dev_name(dev), priv); + if (ret < 0) { + dev_err(dev, "Failed to install irq:%u.\n", priv->irq); + return ret; + } + + priv->dc_syscon_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, + "starfive,syscon"); + if (IS_ERR(priv->dc_syscon_regmap)) { + dev_err(dev, "failed to get starfive,syscon property\n"); + return PTR_ERR(priv->dc_syscon_regmap); + } + + return ret; +} + +static u32 vs_get_addr_offset(u32 id) +{ + u32 offset = 0; + + switch (id) { + case PRIMARY_PLANE_1: + case OVERLAY_PLANE_1: + offset = 0x04; + break; + case OVERLAY_PLANE_2: + offset = 0x08; + break; + case OVERLAY_PLANE_3: + offset = 0x0C; + break; + default: + break; + } + + return offset; +} + +static u32 vs_map_possible_crtc(u32 id) +{ + switch (id) { + case PRIMARY_PLANE_0: + case CURSOR_PLANE_0: + return 0x01;//crtc0 + case PRIMARY_PLANE_1: + case CURSOR_PLANE_1: + return 0x02;//crtc1 + default: + return 0x03;//crtc0&crtc1 + } +} + +static int vs_kms_init(struct vs_drm_device *priv) +{ + struct vs_dc *dc; + struct drm_device *drm_dev; + int i, ret; + struct device_node *port; + struct vs_crtc *crtc; + struct vs_dc_info *dc_info; + struct vs_plane_info *plane_info; + struct vs_plane *plane; + + u32 max_width = 0, max_height = 0; + u32 min_width = 0xffff, min_heigth = 0xffff; + + dc = &priv->dc; + dc_info = dc->hw.info; + drm_dev = &priv->base; + + for (i = 0; i < dc_info->panel_num; i++) { + crtc = vs_crtc_create(drm_dev, dc_info); + if (!crtc) { + drm_err(drm_dev, "Failed to create CRTC.\n"); + ret = -ENOMEM; + return ret; + } + crtc->dev = drm_dev->dev; + crtc->index = i; + + port = of_graph_get_port_by_id(crtc->dev->of_node, i); + if (!port) { + drm_err(drm_dev, "no port node found for crtc_port%d\n", i); + return -ENOENT; + } + + crtc->base.port = port; + dc->crtc[i] = crtc; + + of_node_put(port); + } + + if (!dc->crtc[0]->base.port || !dc->crtc[1]->base.port) { + drm_err(drm_dev, "no port no crtc mask, fail to create plane\n"); + return -ENOENT; + } + + for (i = 0; i < dc_info->plane_num; i++) { + plane_info = (struct vs_plane_info *)&dc_info->info[i]; + + plane = vs_plane_create(drm_dev, plane_info, dc_info->layer_num, + vs_map_possible_crtc(plane_info->id)); + + plane->id = i; + dc->planes[i].id = plane_info->id; + dc->planes[i].offset = vs_get_addr_offset(plane_info->id); + + if (plane_info->type == DRM_PLANE_TYPE_PRIMARY) { + if (plane_info->id == PRIMARY_PLANE_0) + dc->crtc[0]->base.primary = &plane->base; + else + dc->crtc[1]->base.primary = &plane->base; + min_width = min_t(u32, min_width, plane_info->data->min_width); + min_heigth = min_t(u32, min_heigth, plane_info->data->min_height); + /* + * Note: these values are used for multiple independent things: + * hw display mode filtering, plane buffer sizes ... + * Use the combined maximum values here to cover all use cases, + * and do more specific checking in the respective code paths. + */ + max_width = max_t(u32, max_width, plane_info->data->max_width); + max_height = max_t(u32, max_height, plane_info->data->max_height); + } + + if (plane_info->type == DRM_PLANE_TYPE_CURSOR) { + if (plane_info->id == CURSOR_PLANE_0) + dc->crtc[0]->base.cursor = &plane->base; + else + dc->crtc[1]->base.cursor = &plane->base; + drm_dev->mode_config.cursor_width = plane_info->data->max_width; + drm_dev->mode_config.cursor_height = plane_info->data->max_height; + } + } + + drm_dev->mode_config.min_width = min_width; + drm_dev->mode_config.min_height = min_heigth; + drm_dev->mode_config.max_width = max_width; + drm_dev->mode_config.max_height = max_height; + + if (dc_info->pitch_alignment > priv->pitch_alignment) + priv->pitch_alignment = dc_info->pitch_alignment; + + return 0; +} + +static int vs_load(struct vs_drm_device *priv) +{ + int ret; + + ret = clk_bulk_prepare_enable(priv->clk_count, priv->clks); + if (ret) + return ret; + + reset_control_deassert(priv->rsts); + + ret = dc_hw_init(&priv->dc); + if (ret) { + DRM_ERROR("failed to init DC HW\n"); + return ret; + } + + return 0; +} + +static void vs_mipi_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct vs_drm_device *priv = to_vs_drm_private(dev); + int idx; + + if (!drm_dev_enter(dev, &idx)) + return; + + regmap_update_bits(priv->dc_syscon_regmap, STARFIVE_SOC_CON8, STARFIVE_MIPI_SEL, 0); + + drm_dev_exit(idx); +} + +static void vs_mipi_encoder_enable(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct vs_drm_device *priv = to_vs_drm_private(dev); + int idx; + + if (!drm_dev_enter(dev, &idx)) + return; + + regmap_update_bits(priv->dc_syscon_regmap, STARFIVE_SOC_CON8, STARFIVE_MIPI_SEL, BIT(3)); + + drm_dev_exit(idx); +} + +static const struct drm_encoder_helper_funcs vs_mipi_encoder_helper_funcs = { + .disable = vs_mipi_encoder_disable, + .enable = vs_mipi_encoder_enable, +}; + +static int vs_attach_mipi_bridge(struct vs_drm_device *priv) +{ + struct device *dev = priv->base.dev; + struct drm_bridge *bridge; + struct drm_encoder *encoder; + int ret; + + bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 1); + if (IS_ERR(bridge)) { + if (PTR_ERR(bridge) == -ENODEV) { + bridge = NULL; + return 0; + } + + return PTR_ERR(bridge); + } + + /* Create the encoder and attach the bridge. */ + encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return -ENOMEM; + + encoder->possible_crtcs = drm_crtc_mask(&priv->dc.crtc[1]->base); + + ret = drmm_encoder_init(&priv->base, encoder, NULL, DRM_MODE_ENCODER_DSI, NULL); + if (ret) { + dev_err(dev, "Failed to initialize encoder\n"); + return ret; + } + + drm_encoder_helper_add(encoder, &vs_mipi_encoder_helper_funcs); + + ret = drm_bridge_attach(encoder, bridge, NULL, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to attach bridge\n"); + + return 0; +} + +static int vs_drm_bind(struct device *dev) +{ + struct vs_drm_device *priv; + int ret; + struct drm_device *drm_dev; + + priv = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_device, base); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + priv->pitch_alignment = 64; + drm_dev = &priv->base; + dev_set_drvdata(dev, drm_dev); + + ret = dma_set_coherent_mask(drm_dev->dev, DMA_BIT_MASK(40)); + if (ret) + return ret; + + ret = vs_drm_device_init_res(priv); + if (ret) + return ret; + + vs_mode_config_init(drm_dev); + + /* Remove existing drivers that may own the framebuffer memory. */ + ret = drm_aperture_remove_framebuffers(&vs_drm_driver); + if (ret) + return ret; + + ret = vs_kms_init(priv); + if (ret) { + DRM_ERROR("Failed to initialize KMS pipeline\n"); + return ret; + } + + ret = vs_load(priv); + if (ret) + return ret; + + /* Now try and bind all our sub-components */ + ret = component_bind_all(dev, drm_dev); + if (ret) { + ret = -EPROBE_DEFER; + goto unload; + } + + ret = vs_attach_mipi_bridge(priv); + if (ret) + goto err_unbind_all; + + ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc); + if (ret) + goto err_unbind_all; + + drm_mode_config_reset(drm_dev); + + ret = drmm_kms_helper_poll_init(drm_dev); + if (ret) + goto err_unbind_all; + + ret = drm_dev_register(drm_dev, 0); + if (ret) + goto err_unbind_all; + + drm_fbdev_dma_setup(drm_dev, 32); + + return 0; + +err_unbind_all: + component_unbind_all(drm_dev->dev, drm_dev); +unload: + reset_control_assert(priv->rsts); + clk_bulk_disable_unprepare(priv->clk_count, priv->clks); + return ret; +} + +static void vs_drm_unbind(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct vs_drm_device *priv = to_vs_drm_private(drm_dev); + + reset_control_assert(priv->rsts); + clk_bulk_disable_unprepare(priv->clk_count, priv->clks); + + drm_dev_unregister(drm_dev); + drm_atomic_helper_shutdown(drm_dev); + component_unbind_all(drm_dev->dev, drm_dev); +} + +static const struct component_master_ops vs_drm_ops = { + .bind = vs_drm_bind, + .unbind = vs_drm_unbind, +}; + +static struct platform_driver *drm_sub_drivers[] = { +#ifdef CONFIG_DRM_INNO_STARFIVE_HDMI + &starfive_hdmi_driver, +#endif +}; + +static struct component_match *vs_add_external_components(struct device *dev) +{ + struct component_match *match = NULL; + struct device_node *node; + +#ifdef CONFIG_DRM_INNO_STARFIVE_HDMI + node = of_graph_get_remote_node(dev->of_node, 0, 0); + drm_of_component_match_add(dev, &match, component_compare_of, node); + of_node_put(node); +#endif + + return match ? match : ERR_PTR(-ENODEV); +} + +static int vs_drm_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct component_match *match; + + /* all the planes and CRTC would be created in this platform device, + * so external components are encoder + connector + */ + match = vs_add_external_components(dev); + if (IS_ERR(match)) + return PTR_ERR(match); + + return component_master_add_with_match(dev, &vs_drm_ops, match); +} + +static void vs_drm_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &vs_drm_ops); +} + +#ifdef CONFIG_PM_SLEEP +static int vs_drm_suspend(struct device *dev) +{ + return drm_mode_config_helper_suspend(dev_get_drvdata(dev)); +} + +static int vs_drm_resume(struct device *dev) +{ + drm_mode_config_helper_resume(dev_get_drvdata(dev)); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vs_drm_pm_ops, vs_drm_suspend, vs_drm_resume); + +static const struct of_device_id vs_drm_dt_ids[] = { + { .compatible = "starfive,jh7110-dc8200", .data = &dc8200_info,}, + { }, +}; + +MODULE_DEVICE_TABLE(of, vs_drm_dt_ids); + +static struct platform_driver vs_drm_platform_driver = { + .probe = vs_drm_platform_probe, + .remove = vs_drm_platform_remove, + + .driver = { + .name = DRV_NAME, + .of_match_table = vs_drm_dt_ids, + .pm = &vs_drm_pm_ops, + }, +}; + +static int __init vs_drm_init(void) +{ + int ret; + + ret = platform_register_drivers(drm_sub_drivers, ARRAY_SIZE(drm_sub_drivers)); + if (ret) + return ret; + + ret = drm_platform_driver_register(&vs_drm_platform_driver); + if (ret) + platform_unregister_drivers(drm_sub_drivers, ARRAY_SIZE(drm_sub_drivers)); + + return ret; +} + +static void __exit vs_drm_fini(void) +{ + platform_driver_unregister(&vs_drm_platform_driver); + platform_unregister_drivers(drm_sub_drivers, ARRAY_SIZE(drm_sub_drivers)); +} + +module_init(vs_drm_init); +module_exit(vs_drm_fini); + +MODULE_DESCRIPTION("VeriSilicon DRM Driver"); +MODULE_LICENSE("GPL");