Hi,
another update to the MT8173 DRM support RFC.
Changes since v4: - Fixed author for the HDMI driver patch - Moved device tree binding documentation to Documentation/devicetree/bindings/display/mediatek - Added mtk_crtc_state to keep pending state, made mtk_drm_crtc private - Now using drm_dev_alloc and drm_dev_register directly instead of drm_platform_init, drop drm_driver->load callback - Dropped unnecessary locking in mtk_drm_gem_dump_map_offset - Removed currently unused mtk_drm_gem_mmap_buf - Stopped referencing plane framebuffers manually - Set RDMA FIFO output threshold depending on frame width/height/rate - Removed mediatek,cec and ddc-i2c-bus properties from hdmi node - Added ddc-i2c-bus property to the output connector node - Made output port@1 required in hdmi node, using of_graph_get_port_by_id - Add mediatek, prefix to ibias properties in the hdmi-phy node - Always set HDMI mode during bridge enable - Move HDMI PHY registers into a separate file - Enable the CEC clock later in the probe function
Due to the locking changes this now depends on drm-next to function, but otherwise the patch dependencies are still the same. To apply:
https://patchwork.kernel.org/patch/6980951/ ("arm64: dts: mt8173: Add subsystem clock controller device nodes"), https://patchwork.kernel.org/patch/6825601/ ("arm64: dts: mt8173: add MT8173 display PWM driver support node"), https://patchwork.kernel.org/patch/7138531/ ("arm64: dts: mediatek: add xHCI & usb phy for mt8173"), https://patchwork.kernel.org/patch/6928651/ ("dts: mt8173: Add iommu/smi nodes for mt8173"), and https://patchwork.kernel.org/patch/6983351/ ("clk: mediatek: Add USB clock support in MT8173 APMIXEDSYS").
And to build,
https://patchwork.kernel.org/patch/6914941/ ("iommu: Implement common IOMMU ops for DMA mapping"), https://patchwork.kernel.org/patch/6928621/ ("memory: mediatek: Add SMI driver"), https://patchwork.kernel.org/patch/6928561/ ("dt-bindings: iommu: Add binding for mediatek IOMMU"), https://patchwork.kernel.org/patch/6980911/ ("clk: mediatek: Removed unused dpi_ck clock from MT8173"), https://patchwork.kernel.org/patch/6980981/ ("clk: mediatek: Add __initdata and __init for data and functions"), https://patchwork.kernel.org/patch/6981021/ ("clk: mediatek: Add fixed clocks support for Mediatek SoC."), https://patchwork.kernel.org/patch/6980961/ ("clk: mediatek: Fix rate and dependency of MT8173 clocks"), https://patchwork.kernel.org/patch/6981031/ ("dt-bindings: ARM: Mediatek: Document devicetree bindings for clock controllers"), and https://patchwork.kernel.org/patch/6981041/ ("clk: mediatek: Add subsystem clocks of MT8173").
regards Philipp
CK Hu (5): dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding drm/mediatek: Add DRM Driver for Mediatek SoC MT8173. drm/mediatek: Add DSI sub driver arm64: dts: mt8173: Add display subsystem related nodes arm64: dts: mt8173: Add HDMI related nodes
Jie Qiu (3): drm/mediatek: Add DPI sub driver drm/mediatek: Add HDMI support drm/mediatek: enable hdmi output control bit
Philipp Zabel (4): dt-bindings: drm/mediatek: Add Mediatek HDMI dts binding clk: mediatek: make dpi0_sel and hdmi_sel not propagate rate changes clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output dt-bindings: hdmi-connector: add DDC I2C bus phandle documentation
.../bindings/display/mediatek/mediatek,disp.txt | 183 +++++ .../bindings/display/mediatek/mediatek,dpi.txt | 35 + .../bindings/display/mediatek/mediatek,dsi.txt | 53 ++ .../bindings/display/mediatek/mediatek,hdmi.txt | 142 ++++ .../devicetree/bindings/video/hdmi-connector.txt | 1 + arch/arm64/boot/dts/mediatek/mt8173.dtsi | 282 ++++++++ drivers/clk/mediatek/clk-mt8173.c | 9 +- drivers/clk/mediatek/clk-mtk.h | 7 +- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/mediatek/Kconfig | 22 + drivers/gpu/drm/mediatek/Makefile | 21 + drivers/gpu/drm/mediatek/mtk_cec.c | 251 +++++++ drivers/gpu/drm/mediatek/mtk_cec.h | 25 + drivers/gpu/drm/mediatek/mtk_dpi.c | 683 ++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_dpi.h | 80 +++ drivers/gpu/drm/mediatek/mtk_dpi_regs.h | 228 ++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.c | 590 +++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.h | 56 ++ drivers/gpu/drm/mediatek/mtk_drm_ddp.c | 218 ++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.h | 39 + drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 424 +++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 86 +++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 601 ++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.h | 64 ++ drivers/gpu/drm/mediatek/mtk_drm_fb.c | 151 ++++ drivers/gpu/drm/mediatek/mtk_drm_fb.h | 29 + drivers/gpu/drm/mediatek/mtk_drm_gem.c | 189 +++++ drivers/gpu/drm/mediatek/mtk_drm_gem.h | 56 ++ drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c | 642 +++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_plane.c | 167 +++++ drivers/gpu/drm/mediatek/mtk_drm_plane.h | 41 ++ drivers/gpu/drm/mediatek/mtk_dsi.c | 787 +++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_dsi.h | 54 ++ drivers/gpu/drm/mediatek/mtk_hdmi.c | 515 ++++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi.h | 118 +++ drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c | 362 ++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_hw.c | 773 ++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_hw.h | 76 ++ drivers/gpu/drm/mediatek/mtk_hdmi_phy.c | 340 +++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_phy.h | 20 + drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h | 118 +++ drivers/gpu/drm/mediatek/mtk_hdmi_regs.h | 222 ++++++ drivers/gpu/drm/mediatek/mtk_mipi_tx.c | 375 ++++++++++ drivers/gpu/drm/mediatek/mtk_mipi_tx.h | 21 + include/drm/mediatek/mtk_hdmi_audio.h | 150 ++++ include/dt-bindings/clock/mt8173-clk.h | 3 +- 47 files changed, 9307 insertions(+), 5 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt create mode 100644 drivers/gpu/drm/mediatek/Kconfig create mode 100644 drivers/gpu/drm/mediatek/Makefile create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.c create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.h create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.h create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi_regs.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.h create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_hw.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.c create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.h create mode 100644 include/drm/mediatek/mtk_hdmi_audio.h
From: CK Hu ck.hu@mediatek.com
Add device tree binding documentation for the display subsystem in Mediatek MT8173 SoCs.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- Changes since v4: - Move device tree binding documentation to Documentation/devicetree/bindings/display/mediatek - Clarified display function block nodes are siblings to mmsys --- .../bindings/display/mediatek/mediatek,disp.txt | 183 +++++++++++++++++++++ .../bindings/display/mediatek/mediatek,dpi.txt | 35 ++++ .../bindings/display/mediatek/mediatek,dsi.txt | 53 ++++++ 3 files changed, 271 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..cc3d884 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt @@ -0,0 +1,183 @@ +Mediatek display subsystem +========================== + +The Mediatek display subsystem consists of various DISP function blocks in the +MMSYS register space. The connections between them can be configured by output +and input selectors in the MMSYS_CONFIG register space and register updates can +be synchronized to video frame boundaries with help of a DISP_MUTEX function +block. + +All DISP device tree nodes must be siblings to the central MMSYS_CONFIG node. +For a description of the MMSYS_CONFIG binding, see +Documentation/devicetree/bindings/arm/mediatek/mediatek,mmsys.txt. + +DISP function blocks +==================== + +A display stream starts at a source function block that reads pixel data from +memory and ends with a sink function block that drives pixels on a display +interface, or writes pixels back to memory. All DISP function blocks have +their own register space, interrupt, and clock gate. The blocks that can +access memory additionally have to list the IOMMU and local arbiter they are +connected to. + +For a description of the display interface sink function blocks, see +Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt and +Documentation/devicetree/bindings/drm/mediatek/mediatek,dpi.txt. + +Required properties (all function blocks): +- compatible: "mediatek,<chip>-disp-<function>", one of + "mediatek,<chip>-disp-ovl" - overlay (4 layers, blending, csc) + "mediatek,<chip>-disp-rdma" - read DMA / line buffer + "mediatek,<chip>-disp-wdma" - write DMA + "mediatek,<chip>-disp-color" - color processor + "mediatek,<chip>-disp-aal" - adaptive ambient light controller + "mediatek,<chip>-disp-gamma" - gamma correction + "mediatek,<chip>-disp-ufoe" - data compression engine + "mediatek,<chip>-dsi" - DSI controller, see mediatek,dsi.txt + "mediatek,<chip>-dpi" - DPI controller, see mediatek,dpi.txt + "mediatek,<chip>-disp-mutex" - display mutex + "mediatek,<chip>-disp-od" - overdrive +- reg: Physical base address and length of the function block register space +- interrupts: The interrupt signal from the function block. +- clocks: device clocks + See Documentation/devicetree/bindings/clock/clock-bindings.txt for details. +- compatible: "mediatek,<chip>-ddp" + +Required properties (DMA function blocks): +- compatible: Should be one of + "mediatek,<chip>-disp-ovl" + "mediatek,<chip>-disp-rdma" + "mediatek,<chip>-disp-wdma" +- larb: Should contain a phandle pointing to the local arbiter device as defined + in Documentation/devicetree/bindings/soc/mediatek/mediatek,smi-larb.txt +- iommus: required a iommu node + +Examples: + +mmsys: clock-controller@14000000 { + compatible = "mediatek,mt8173-mmsys", "syscon"; + reg = <0 0x14000000 0 0x1000>; + power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>; + #clock-cells = <1>; +}; + +ovl0: ovl@1400c000 { + compatible = "mediatek,mt8173-disp-ovl"; + reg = <0 0x1400c000 0 0x1000>; + interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_OVL0>; + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_OVL0>; + mediatek,larb = <&larb0>; +}; + +ovl1: ovl@1400d000 { + compatible = "mediatek,mt8173-disp-ovl"; + reg = <0 0x1400d000 0 0x1000>; + interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_OVL1>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_OVL1>; + mediatek,larb = <&larb4>; +}; + +rdma0: rdma@1400e000 { + compatible = "mediatek,mt8173-disp-rdma"; + reg = <0 0x1400e000 0 0x1000>; + interrupts = <GIC_SPI 182 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_RDMA0>; + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_RDMA0>; + mediatek,larb = <&larb0>; +}; + +rdma1: rdma@1400f000 { + compatible = "mediatek,mt8173-disp-rdma"; + reg = <0 0x1400f000 0 0x1000>; + interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_RDMA1>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_RDMA1>; + mediatek,larb = <&larb4>; +}; + +rdma2: rdma@14010000 { + compatible = "mediatek,mt8173-disp-rdma"; + reg = <0 0x14010000 0 0x1000>; + interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_RDMA2>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_RDMA2>; + mediatek,larb = <&larb4>; +}; + +wdma0: wdma@14011000 { + compatible = "mediatek,mt8173-disp-wdma"; + reg = <0 0x14011000 0 0x1000>; + interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_WDMA0>; + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_WDMA0>; + mediatek,larb = <&larb0>; +}; + +wdma1: wdma@14012000 { + compatible = "mediatek,mt8173-disp-wdma"; + reg = <0 0x14012000 0 0x1000>; + interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_WDMA1>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_WDMA1>; + mediatek,larb = <&larb4>; +}; + +color0: color@14013000 { + compatible = "mediatek,mt8173-disp-color"; + reg = <0 0x14013000 0 0x1000>; + interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_COLOR0>; +}; + +color1: color@14014000 { + compatible = "mediatek,mt8173-disp-color"; + reg = <0 0x14014000 0 0x1000>; + interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_COLOR1>; +}; + +aal@14015000 { + compatible = "mediatek,mt8173-disp-aal"; + reg = <0 0x14015000 0 0x1000>; + interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_AAL>; +}; + +gamma@14016000 { + compatible = "mediatek,mt8173-disp-gamma"; + reg = <0 0x14016000 0 0x1000>; + interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_GAMMA>; +}; + +ufoe@1401a000 { + compatible = "mediatek,mt8173-disp-ufoe"; + reg = <0 0x1401a000 0 0x1000>; + interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_UFOE>; +}; + +dsi0: dsi@1401b000 { + /* See mediatek,dsi.txt for details */ +}; + +dpi0: dpi@1401d000 { + /* See mediatek,dpi.txt for details */ +}; + +mutex: mutex@14020000 { + compatible = "mediatek,mt8173-disp-mutex"; + reg = <0 0x14020000 0 0x1000>; + interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_LOW>; + power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>; + clocks = <&mmsys CLK_MM_MUTEX_32K>; +}; + +od@14023000 { + compatible = "mediatek,mt8173-disp-od"; + reg = <0 0x14023000 0 0x1000>; + clocks = <&mmsys CLK_MM_DISP_OD>; +}; diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt new file mode 100644 index 0000000..b6a7e73 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt @@ -0,0 +1,35 @@ +Mediatek DPI Device +=================== + +The Mediatek DPI function block is a sink of the display subsystem and +provides 8-bit RGB/YUV444 or 8/10/10-bit YUV422 pixel data on a parallel +output bus. + +Required properties: +- compatible: "mediatek,<chip>-dpi" +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clocks + See Documentation/devicetree/bindings/clock/clock-bindings.txt for details. +- clock-names: must contain "pixel", "engine", and "pll" +- port: Output port node with endpoint definitions as described in + Documentation/devicetree/bindings/graph.txt. This port should be connected + to the input port of an attached HDMI or LVDS encoder chip. + +Example: + +dpi0: dpi@1401d000 { + compatible = "mediatek,mt8173-dpi"; + reg = <0 0x1401d000 0 0x1000>; + interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DPI_PIXEL>, + <&mmsys CLK_MM_DPI_ENGINE>, + <&apmixedsys CLK_APMIXED_TVDPLL>; + clock-names = "pixel", "engine", "pll"; + + port { + dpi0_out: endpoint { + remote-endpoint = <&hdmi0_in>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt new file mode 100644 index 0000000..afa7afb --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt @@ -0,0 +1,53 @@ +Mediatek DSI Device +=================== + +The Mediatek DSI function block is a sink of the display subsystem and can +drive up to 4-lane MIPI DSI output. Two DSIs can be synchronized for dual- +channel output. + +Required properties: +- compatible: "mediatek,<chip>-dsi" +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clocks + See Documentation/devicetree/bindings/clock/clock-bindings.txt for details. +- clock-names: must contain "engine" and "digital". +- phys: phandle link to the MIPI D-PHY controller. +- phy-names: must contain "dphy" +- port: Output port node with endpoint definitions as described in + Documentation/devicetree/bindings/graph.txt. This port should be connected + to the input port of an attached DSI panel or DSI-to-eDP encoder chip. + +MIPI TX Configuration Module +============================ + +The MIPI TX configuration module controls the MIPI D-PHY. + +Required properties: +- compatible: "mediatek,<chip>-mipi-tx" +- reg: Physical base address and length of the controller's registers +- #phy-cells: must be <0>. + +Example: + +mipi_tx0: mipi-dphy@10215000 { + compatible = "mediatek,mt8173-mipi-tx"; + reg = <0 0x10215000 0 0x1000>; + #phy-cells = <0>; +}; + +dsi0: dsi@1401b000 { + compatible = "mediatek,mt8173-dsi"; + reg = <0 0x1401b000 0 0x1000>; + interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>; + clock-names = "engine", "digital"; + phys = <&mipi_tx0>; + phy-names = "dphy"; + + port { + dsi0_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; +};
On Wed, Nov 04, 2015 at 12:44:58PM +0100, Philipp Zabel wrote:
From: CK Hu ck.hu@mediatek.com
Add device tree binding documentation for the display subsystem in Mediatek MT8173 SoCs.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de
If this wasn't an RFC, I'd ack it. :) One thing you missed below though.
Changes since v4:
- Move device tree binding documentation to Documentation/devicetree/bindings/display/mediatek
- Clarified display function block nodes are siblings to mmsys
.../bindings/display/mediatek/mediatek,disp.txt | 183 +++++++++++++++++++++ .../bindings/display/mediatek/mediatek,dpi.txt | 35 ++++ .../bindings/display/mediatek/mediatek,dsi.txt | 53 ++++++ 3 files changed, 271 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..cc3d884 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt @@ -0,0 +1,183 @@ +Mediatek display subsystem +==========================
+The Mediatek display subsystem consists of various DISP function blocks in the +MMSYS register space. The connections between them can be configured by output +and input selectors in the MMSYS_CONFIG register space and register updates can +be synchronized to video frame boundaries with help of a DISP_MUTEX function +block.
+All DISP device tree nodes must be siblings to the central MMSYS_CONFIG node. +For a description of the MMSYS_CONFIG binding, see +Documentation/devicetree/bindings/arm/mediatek/mediatek,mmsys.txt.
+DISP function blocks +====================
+A display stream starts at a source function block that reads pixel data from +memory and ends with a sink function block that drives pixels on a display +interface, or writes pixels back to memory. All DISP function blocks have +their own register space, interrupt, and clock gate. The blocks that can +access memory additionally have to list the IOMMU and local arbiter they are +connected to.
+For a description of the display interface sink function blocks, see +Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt and +Documentation/devicetree/bindings/drm/mediatek/mediatek,dpi.txt.
Need to update these paths.
+Required properties (all function blocks): +- compatible: "mediatek,<chip>-disp-<function>", one of
- "mediatek,<chip>-disp-ovl" - overlay (4 layers, blending, csc)
- "mediatek,<chip>-disp-rdma" - read DMA / line buffer
- "mediatek,<chip>-disp-wdma" - write DMA
- "mediatek,<chip>-disp-color" - color processor
- "mediatek,<chip>-disp-aal" - adaptive ambient light controller
- "mediatek,<chip>-disp-gamma" - gamma correction
- "mediatek,<chip>-disp-ufoe" - data compression engine
- "mediatek,<chip>-dsi" - DSI controller, see mediatek,dsi.txt
- "mediatek,<chip>-dpi" - DPI controller, see mediatek,dpi.txt
- "mediatek,<chip>-disp-mutex" - display mutex
- "mediatek,<chip>-disp-od" - overdrive
+- reg: Physical base address and length of the function block register space +- interrupts: The interrupt signal from the function block. +- clocks: device clocks
- See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- compatible: "mediatek,<chip>-ddp"
+Required properties (DMA function blocks): +- compatible: Should be one of
- "mediatek,<chip>-disp-ovl"
- "mediatek,<chip>-disp-rdma"
- "mediatek,<chip>-disp-wdma"
+- larb: Should contain a phandle pointing to the local arbiter device as defined
- in Documentation/devicetree/bindings/soc/mediatek/mediatek,smi-larb.txt
+- iommus: required a iommu node
+Examples:
+mmsys: clock-controller@14000000 {
- compatible = "mediatek,mt8173-mmsys", "syscon";
- reg = <0 0x14000000 0 0x1000>;
- power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
- #clock-cells = <1>;
+};
+ovl0: ovl@1400c000 {
- compatible = "mediatek,mt8173-disp-ovl";
- reg = <0 0x1400c000 0 0x1000>;
- interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_OVL0>;
- iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_OVL0>;
- mediatek,larb = <&larb0>;
+};
+ovl1: ovl@1400d000 {
- compatible = "mediatek,mt8173-disp-ovl";
- reg = <0 0x1400d000 0 0x1000>;
- interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_OVL1>;
- iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_OVL1>;
- mediatek,larb = <&larb4>;
+};
+rdma0: rdma@1400e000 {
- compatible = "mediatek,mt8173-disp-rdma";
- reg = <0 0x1400e000 0 0x1000>;
- interrupts = <GIC_SPI 182 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_RDMA0>;
- iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_RDMA0>;
- mediatek,larb = <&larb0>;
+};
+rdma1: rdma@1400f000 {
- compatible = "mediatek,mt8173-disp-rdma";
- reg = <0 0x1400f000 0 0x1000>;
- interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_RDMA1>;
- iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_RDMA1>;
- mediatek,larb = <&larb4>;
+};
+rdma2: rdma@14010000 {
- compatible = "mediatek,mt8173-disp-rdma";
- reg = <0 0x14010000 0 0x1000>;
- interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_RDMA2>;
- iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_RDMA2>;
- mediatek,larb = <&larb4>;
+};
+wdma0: wdma@14011000 {
- compatible = "mediatek,mt8173-disp-wdma";
- reg = <0 0x14011000 0 0x1000>;
- interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_WDMA0>;
- iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_WDMA0>;
- mediatek,larb = <&larb0>;
+};
+wdma1: wdma@14012000 {
- compatible = "mediatek,mt8173-disp-wdma";
- reg = <0 0x14012000 0 0x1000>;
- interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_WDMA1>;
- iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_WDMA1>;
- mediatek,larb = <&larb4>;
+};
+color0: color@14013000 {
- compatible = "mediatek,mt8173-disp-color";
- reg = <0 0x14013000 0 0x1000>;
- interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_COLOR0>;
+};
+color1: color@14014000 {
- compatible = "mediatek,mt8173-disp-color";
- reg = <0 0x14014000 0 0x1000>;
- interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_COLOR1>;
+};
+aal@14015000 {
- compatible = "mediatek,mt8173-disp-aal";
- reg = <0 0x14015000 0 0x1000>;
- interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_AAL>;
+};
+gamma@14016000 {
- compatible = "mediatek,mt8173-disp-gamma";
- reg = <0 0x14016000 0 0x1000>;
- interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_GAMMA>;
+};
+ufoe@1401a000 {
- compatible = "mediatek,mt8173-disp-ufoe";
- reg = <0 0x1401a000 0 0x1000>;
- interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DISP_UFOE>;
+};
+dsi0: dsi@1401b000 {
- /* See mediatek,dsi.txt for details */
+};
+dpi0: dpi@1401d000 {
- /* See mediatek,dpi.txt for details */
+};
+mutex: mutex@14020000 {
- compatible = "mediatek,mt8173-disp-mutex";
- reg = <0 0x14020000 0 0x1000>;
- interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_LOW>;
- power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
- clocks = <&mmsys CLK_MM_MUTEX_32K>;
+};
+od@14023000 {
- compatible = "mediatek,mt8173-disp-od";
- reg = <0 0x14023000 0 0x1000>;
- clocks = <&mmsys CLK_MM_DISP_OD>;
+}; diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt new file mode 100644 index 0000000..b6a7e73 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt @@ -0,0 +1,35 @@ +Mediatek DPI Device +===================
+The Mediatek DPI function block is a sink of the display subsystem and +provides 8-bit RGB/YUV444 or 8/10/10-bit YUV422 pixel data on a parallel +output bus.
+Required properties: +- compatible: "mediatek,<chip>-dpi" +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clocks
- See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "pixel", "engine", and "pll" +- port: Output port node with endpoint definitions as described in
- Documentation/devicetree/bindings/graph.txt. This port should be connected
- to the input port of an attached HDMI or LVDS encoder chip.
+Example:
+dpi0: dpi@1401d000 {
- compatible = "mediatek,mt8173-dpi";
- reg = <0 0x1401d000 0 0x1000>;
- interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_DPI_PIXEL>,
<&mmsys CLK_MM_DPI_ENGINE>,
<&apmixedsys CLK_APMIXED_TVDPLL>;
- clock-names = "pixel", "engine", "pll";
- port {
dpi0_out: endpoint {
remote-endpoint = <&hdmi0_in>;
};
- };
+}; diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt new file mode 100644 index 0000000..afa7afb --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt @@ -0,0 +1,53 @@ +Mediatek DSI Device +===================
+The Mediatek DSI function block is a sink of the display subsystem and can +drive up to 4-lane MIPI DSI output. Two DSIs can be synchronized for dual- +channel output.
+Required properties: +- compatible: "mediatek,<chip>-dsi" +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clocks
- See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "engine" and "digital". +- phys: phandle link to the MIPI D-PHY controller. +- phy-names: must contain "dphy" +- port: Output port node with endpoint definitions as described in
- Documentation/devicetree/bindings/graph.txt. This port should be connected
- to the input port of an attached DSI panel or DSI-to-eDP encoder chip.
+MIPI TX Configuration Module +============================
+The MIPI TX configuration module controls the MIPI D-PHY.
+Required properties: +- compatible: "mediatek,<chip>-mipi-tx" +- reg: Physical base address and length of the controller's registers +- #phy-cells: must be <0>.
+Example:
+mipi_tx0: mipi-dphy@10215000 {
- compatible = "mediatek,mt8173-mipi-tx";
- reg = <0 0x10215000 0 0x1000>;
- #phy-cells = <0>;
+};
+dsi0: dsi@1401b000 {
- compatible = "mediatek,mt8173-dsi";
- reg = <0 0x1401b000 0 0x1000>;
- interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>;
- clock-names = "engine", "digital";
- phys = <&mipi_tx0>;
- phy-names = "dphy";
- port {
dsi0_out: endpoint {
remote-endpoint = <&panel_in>;
};
- };
+};
2.6.1
Am Mittwoch, den 04.11.2015, 21:28 -0600 schrieb Rob Herring:
On Wed, Nov 04, 2015 at 12:44:58PM +0100, Philipp Zabel wrote:
From: CK Hu ck.hu@mediatek.com
Add device tree binding documentation for the display subsystem in Mediatek MT8173 SoCs.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de
If this wasn't an RFC, I'd ack it. :) One thing you missed below though.
Alright, thanks for your help in sorting these bindings out.
[...]
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..cc3d884 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,disp.txt
[...]
+DISP function blocks +====================
+A display stream starts at a source function block that reads pixel data from +memory and ends with a sink function block that drives pixels on a display +interface, or writes pixels back to memory. All DISP function blocks have +their own register space, interrupt, and clock gate. The blocks that can +access memory additionally have to list the IOMMU and local arbiter they are +connected to.
+For a description of the display interface sink function blocks, see +Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt and +Documentation/devicetree/bindings/drm/mediatek/mediatek,dpi.txt.
Need to update these paths.
Will do.
regards Philipp
From: CK Hu ck.hu@mediatek.com
This patch adds an initial DRM driver for the Mediatek MT8173 DISP subsystem. It currently supports two fixed output streams from the OVL0/OVL1 sources to the DSI0/DPI0 sinks, respectively.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: YT Shen yt.shen@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- Changes since v4: - Add mtk_crtc_state to keep pending state - Move drm pending vblank event into mtk_crtc_state - Make mtk_drm_crtc private - Use drm_dev_alloc and drm_dev_register directly instead of drm_platform_init - Drop unnecessary locking in mtk_drm_gem_dump_map_offset - Remove currently unused mtk_drm_gem_mmap_buf - Stop referencing plane framebuffers manually - Set RDMA FIFO output threshold depending on frame width/height/rate --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/mediatek/Kconfig | 16 + drivers/gpu/drm/mediatek/Makefile | 10 + drivers/gpu/drm/mediatek/mtk_drm_crtc.c | 590 ++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.h | 56 +++ drivers/gpu/drm/mediatek/mtk_drm_ddp.c | 218 ++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.h | 39 ++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 424 ++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 86 ++++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 572 +++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.h | 61 +++ drivers/gpu/drm/mediatek/mtk_drm_fb.c | 151 +++++++ drivers/gpu/drm/mediatek/mtk_drm_fb.h | 29 ++ drivers/gpu/drm/mediatek/mtk_drm_gem.c | 189 +++++++++ drivers/gpu/drm/mediatek/mtk_drm_gem.h | 56 +++ drivers/gpu/drm/mediatek/mtk_drm_plane.c | 167 ++++++++ drivers/gpu/drm/mediatek/mtk_drm_plane.h | 41 ++ 18 files changed, 2708 insertions(+) create mode 100644 drivers/gpu/drm/mediatek/Kconfig create mode 100644 drivers/gpu/drm/mediatek/Makefile create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1a0a8df..9e9987b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -264,3 +264,5 @@ source "drivers/gpu/drm/sti/Kconfig" source "drivers/gpu/drm/amd/amdkfd/Kconfig"
source "drivers/gpu/drm/imx/Kconfig" + +source "drivers/gpu/drm/mediatek/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 45e7719..af6b592 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-$(CONFIG_DRM_STI) += sti/ obj-$(CONFIG_DRM_IMX) += imx/ +obj-$(CONFIG_DRM_MEDIATEK) += mediatek/ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig new file mode 100644 index 0000000..5343cf1 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -0,0 +1,16 @@ +config DRM_MEDIATEK + tristate "DRM Support for Mediatek SoCs" + depends on DRM + depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST) + select MTK_SMI + select DRM_PANEL + select DRM_MIPI_DSI + select DRM_PANEL_SIMPLE + select DRM_KMS_HELPER + select IOMMU_DMA + help + Choose this option if you have a Mediatek SoCs. + The module will be called mediatek-drm + This driver provides kernel mode setting and + buffer management to userspace. + diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile new file mode 100644 index 0000000..ba6d3fc --- /dev/null +++ b/drivers/gpu/drm/mediatek/Makefile @@ -0,0 +1,10 @@ +mediatek-drm-y := mtk_drm_drv.o \ + mtk_drm_crtc.o \ + mtk_drm_ddp.o \ + mtk_drm_ddp_comp.o \ + mtk_drm_fb.o \ + mtk_drm_gem.o \ + mtk_drm_plane.o + +obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o + diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.c b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c new file mode 100644 index 0000000..4493714 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/dma-buf.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reservation.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h" + +struct mtk_crtc_ddp_context; + +/* + * MediaTek specific crtc structure. + * + * @base: crtc object. + * @pipe: a crtc index created at load() with a new crtc object creation + * and the crtc object would be set to private->crtc array + * to get a crtc object corresponding to this pipe from private->crtc + * array when irq interrupt occurred. the reason of using this pipe is that + * drm framework doesn't support multiple irq yet. + * we can refer to the crtc to current hardware interrupt occurred through + * this pipe value. + * @enabled: crtc enabled + * @ctx: mtk crtc context object + */ +struct mtk_drm_crtc { + struct drm_crtc base; + unsigned int pipe; + bool enabled; + struct mtk_crtc_ddp_context *ctx; + + bool do_flush; +}; + +struct mtk_crtc_ddp_context { + struct device *dev; + struct drm_device *drm_dev; + struct mtk_drm_crtc *crtc; + struct mtk_drm_plane planes[OVL_LAYER_NR]; + int pipe; + + void __iomem *config_regs; + struct device *mutex_dev; + u32 ddp_comp_nr; + struct mtk_ddp_comp *ddp_comp; +}; + +static inline struct mtk_drm_crtc *to_mtk_crtc(struct drm_crtc *c) +{ + return container_of(c, struct mtk_drm_crtc, base); +} + +void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc) +{ + struct drm_crtc *crtc = &mtk_crtc->base; + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + struct drm_device *dev = mtk_crtc->base.dev; + + drm_send_vblank_event(dev, state->event->pipe, state->event); + drm_crtc_vblank_put(crtc); + state->event = NULL; +} + +static void mtk_drm_crtc_destroy(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); +} + +struct drm_crtc_state *mtk_drm_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct mtk_crtc_state *crtc_state; + + crtc_state = kzalloc(sizeof(*crtc_state), GFP_KERNEL); + if (!crtc_state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &crtc_state->base); + + crtc_state->base.crtc = crtc; + + return &crtc_state->base; +} + +static bool mtk_drm_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* drm framework doesn't check NULL */ + return true; +} + +static void mtk_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + + state->pending_width = crtc->mode.hdisplay; + state->pending_height = crtc->mode.vdisplay; + state->pending_vrefresh = crtc->mode.vrefresh; + state->pending_config = true; +} + +int mtk_drm_crtc_enable_vblank(struct drm_device *drm, int pipe) +{ + struct mtk_drm_private *priv = drm->dev_private; + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]); + struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx; + struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0]; + + if (ovl->funcs->comp_enable_vblank) + ovl->funcs->comp_enable_vblank(ovl->regs); + + return 0; +} + +void mtk_drm_crtc_disable_vblank(struct drm_device *drm, int pipe) +{ + struct mtk_drm_private *priv = drm->dev_private; + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]); + struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx; + struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0]; + + if (ovl->funcs->comp_disable_vblank) + ovl->funcs->comp_disable_vblank(ovl->regs); +} + +static void mtk_crtc_ddp_power_on(struct mtk_crtc_ddp_context *ctx) +{ + int ret; + int i; + + DRM_INFO("mtk_crtc_ddp_power_on\n"); + for (i = 0; i < ctx->ddp_comp_nr; i++) { + ret = clk_enable(ctx->ddp_comp[i].clk); + if (ret) + DRM_ERROR("clk_enable(ctx->ddp_comp_clk[%d])\n", i); + } +} + +static void mtk_crtc_ddp_power_off(struct mtk_crtc_ddp_context *ctx) +{ + int i; + + DRM_INFO("mtk_crtc_ddp_power_off\n"); + for (i = 0; i < ctx->ddp_comp_nr; i++) + clk_disable(ctx->ddp_comp[i].clk); +} + +static void mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *crtc) +{ + struct mtk_crtc_ddp_context *ctx = crtc->ctx; + struct drm_device *drm = crtc->base.dev; + unsigned int width, height, vrefresh; + int ret; + int i; + + if (ctx->crtc->base.state) { + width = crtc->base.state->adjusted_mode.hdisplay; + height = crtc->base.state->adjusted_mode.vdisplay; + vrefresh = crtc->base.state->adjusted_mode.vrefresh; + } else { + width = 1920; + height = 1080; + vrefresh = 60; + } + + DRM_INFO("mtk_crtc_ddp_hw_init\n"); + + /* disp_mtcmos */ + ret = pm_runtime_get_sync(drm->dev); + if (ret < 0) + DRM_ERROR("Failed to enable power domain: %d\n", ret); + + mtk_ddp_clock_on(ctx->mutex_dev); + mtk_crtc_ddp_power_on(ctx); + + DRM_INFO("mediatek_ddp_ddp_path_setup\n"); + for (i = 0; i < (ctx->ddp_comp_nr - 1); i++) { + mtk_ddp_add_comp_to_path(ctx->config_regs, ctx->pipe, + ctx->ddp_comp[i].funcs->comp_id, + ctx->ddp_comp[i+1].funcs->comp_id); + mtk_ddp_add_comp_to_mutex(ctx->mutex_dev, ctx->pipe, + ctx->ddp_comp[i].funcs->comp_id, + ctx->ddp_comp[i+1].funcs->comp_id); + } + + DRM_INFO("ddp_disp_path_power_on %dx%d\n", width, height); + for (i = 0; i < ctx->ddp_comp_nr; i++) { + struct mtk_ddp_comp *comp = &ctx->ddp_comp[i]; + + if (comp->funcs->comp_config) + comp->funcs->comp_config(comp->regs, width, height, + vrefresh); + + if (comp->funcs->comp_power_on) + comp->funcs->comp_power_on(comp->regs); + } +} + +static void mtk_crtc_ddp_hw_fini(struct mtk_drm_crtc *crtc) +{ + struct mtk_crtc_ddp_context *ctx = crtc->ctx; + struct drm_device *drm = crtc->base.dev; + struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0]; + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->base.state); + int i; + + DRM_INFO("mtk_crtc_ddp_hw_fini\n"); + for (i = 0; i < OVL_LAYER_NR; i++) { + state->pending_ovl_enable[i] = false; + ovl->funcs->comp_layer_off(ovl->regs, i); + } + mtk_crtc_ddp_power_off(ctx); + mtk_ddp_clock_off(ctx->mutex_dev); + + /* disp_mtcmos */ + pm_runtime_put_sync(drm->dev); +} + +static void mtk_drm_crtc_enable(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + + if (WARN_ON(mtk_crtc->enabled)) + return; + + mtk_crtc_ddp_hw_init(mtk_crtc); + + drm_crtc_vblank_on(crtc); + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + mtk_crtc->enabled = true; +} + +static void mtk_drm_crtc_disable(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + + DRM_INFO("mtk_drm_crtc_disable %d\n", crtc->base.id); + if (WARN_ON(!mtk_crtc->enabled)) + return; + + mtk_crtc_ddp_hw_fini(mtk_crtc); + mtk_crtc->do_flush = false; + if (state->event) + mtk_drm_crtc_finish_page_flip(mtk_crtc); + drm_crtc_vblank_put(crtc); + drm_crtc_vblank_off(crtc); + + mtk_crtc->enabled = false; +} + +static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + + if (state->base.event) { + state->base.event->pipe = drm_crtc_index(crtc); + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + state->event = state->base.event; + state->base.event = NULL; + } +} + +void mtk_drm_crtc_commit(struct drm_crtc *crtc) +{ + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + unsigned int i; + + for (i = 0; i < OVL_LAYER_NR; i++) + if (state->pending_ovl_dirty[i]) { + state->pending_ovl_config[i] = true; + state->pending_ovl_dirty[i] = false; + } +} + +static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + mtk_drm_crtc_commit(crtc); +} + +static const struct drm_crtc_funcs mtk_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .destroy = mtk_drm_crtc_destroy, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = mtk_drm_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static const struct drm_crtc_helper_funcs mtk_crtc_helper_funcs = { + .mode_fixup = mtk_drm_crtc_mode_fixup, + .mode_set_nofb = mtk_drm_crtc_mode_set_nofb, + .enable = mtk_drm_crtc_enable, + .disable = mtk_drm_crtc_disable, + .atomic_begin = mtk_drm_crtc_atomic_begin, + .atomic_flush = mtk_drm_crtc_atomic_flush, +}; + +struct mtk_drm_crtc *mtk_drm_crtc_create(struct drm_device *drm, + struct drm_plane *primary, struct drm_plane *cursor, int pipe, + void *ctx) +{ + struct mtk_drm_private *priv = drm->dev_private; + struct mtk_drm_crtc *mtk_crtc; + int ret; + + mtk_crtc = devm_kzalloc(drm->dev, sizeof(*mtk_crtc), GFP_KERNEL); + if (!mtk_crtc) + return ERR_PTR(-ENOMEM); + + mtk_crtc->pipe = pipe; + mtk_crtc->ctx = ctx; + mtk_crtc->enabled = false; + + priv->crtc[pipe] = &mtk_crtc->base; + + ret = drm_crtc_init_with_planes(drm, &mtk_crtc->base, primary, cursor, + &mtk_crtc_funcs); + if (ret) + goto err_cleanup_crtc; + + drm_crtc_helper_add(&mtk_crtc->base, &mtk_crtc_helper_funcs); + + return mtk_crtc; + +err_cleanup_crtc: + drm_crtc_cleanup(&mtk_crtc->base); + return ERR_PTR(ret); +} + +void mtk_drm_crtc_plane_config(struct drm_crtc *crtc, unsigned int idx, + bool enable, dma_addr_t addr) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx; + struct drm_plane *plane = &ctx->planes[idx].base; + struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state); + unsigned int pitch, format; + int x, y; + + if (!plane->fb || !plane->state) + return; + + pitch = plane->fb->pitches[0]; + format = plane->fb->pixel_format; + + x = plane->state->crtc_x; + y = plane->state->crtc_y; + + if (x < 0) { + addr -= x * 4; + x = 0; + } + + if (y < 0) { + addr -= y * pitch; + y = 0; + } + + state->pending_ovl_enable[idx] = enable; + state->pending_ovl_addr[idx] = addr; + state->pending_ovl_pitch[idx] = pitch; + state->pending_ovl_format[idx] = format; + state->pending_ovl_x[idx] = x; + state->pending_ovl_y[idx] = y; + state->pending_ovl_size[idx] = ctx->planes[idx].disp_size; + state->pending_ovl_dirty[idx] = true; +} + +static void mtk_crtc_ddp_irq(struct mtk_crtc_ddp_context *ctx) +{ + struct mtk_drm_crtc *mtk_crtc = ctx->crtc; + struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0]; + struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state); + unsigned int i; + + if (state->pending_config) { + state->pending_config = false; + + for (i = 0; i < OVL_LAYER_NR; i++) + ovl->funcs->comp_layer_off(ovl->regs, i); + ovl->funcs->comp_config(ovl->regs, state->pending_width, + state->pending_height, + state->pending_vrefresh); + } + + for (i = 0; i < OVL_LAYER_NR; i++) { + if (state->pending_ovl_config[i]) { + if (!state->pending_ovl_enable[i]) + ovl->funcs->comp_layer_off(ovl->regs, i); + + ovl->funcs->comp_layer_config(ovl->regs, i, + state->pending_ovl_addr[i], + state->pending_ovl_pitch[i], + state->pending_ovl_format[i], + state->pending_ovl_x[i], + state->pending_ovl_y[i], + state->pending_ovl_size[i]); + + if (state->pending_ovl_enable[i]) + ovl->funcs->comp_layer_on(ovl->regs, i); + } + + state->pending_ovl_config[i] = false; + } + + drm_handle_vblank(ctx->drm_dev, ctx->pipe); +} + +static irqreturn_t mtk_crtc_ddp_irq_handler(int irq, void *dev_id) +{ + struct mtk_crtc_ddp_context *ctx = dev_id; + struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0]; + + if (ovl->funcs->comp_clear_vblank) + ovl->funcs->comp_clear_vblank(ovl->regs); + + if (ctx->pipe >= 0 && ctx->drm_dev) + mtk_crtc_ddp_irq(ctx); + + return IRQ_HANDLED; +} + +static int mtk_crtc_ddp_bind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_crtc_ddp_context *ctx = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + struct mtk_drm_private *priv = drm_dev->dev_private; + struct device_node *node; + enum drm_plane_type type; + unsigned int zpos; + int ret; + int i; + + ctx->drm_dev = drm_dev; + ctx->pipe = priv->pipe++; + ctx->config_regs = priv->config_regs; + ctx->mutex_dev = priv->mutex_dev; + ctx->ddp_comp_nr = priv->path_len[ctx->pipe]; + ctx->ddp_comp = devm_kmalloc_array(dev, ctx->ddp_comp_nr, + sizeof(*ctx->ddp_comp), GFP_KERNEL); + + /* priv->path[ctx->pipe] starts with this OVL node */ + priv->comp_node[priv->path[ctx->pipe][0]] = dev->of_node; + + for (i = 0; i < ctx->ddp_comp_nr; i++) { + struct mtk_ddp_comp *comp = &ctx->ddp_comp[i]; + enum mtk_ddp_comp_id comp_id = priv->path[ctx->pipe][i]; + + ret = mtk_ddp_comp_init(priv->comp_node[comp_id], comp, + comp_id); + if (ret) { + dev_err(dev, "Failed to initialize component %i\n", i); + goto unprepare; + } + + if (comp->clk) { + ret = clk_prepare(comp->clk); + if (ret) { + dev_err(dev, "Failed to prepare clock %d\n", i); + ret = -EINVAL; + goto unprepare; + } + } + } + + for (zpos = 0; zpos < OVL_LAYER_NR; zpos++) { + type = (zpos == 0) ? DRM_PLANE_TYPE_PRIMARY : + (zpos == 1) ? DRM_PLANE_TYPE_CURSOR : + DRM_PLANE_TYPE_OVERLAY; + ret = mtk_plane_init(drm_dev, &ctx->planes[zpos], + 1 << ctx->pipe, type, zpos, OVL_LAYER_NR); + if (ret) + goto unprepare; + } + + ctx->crtc = mtk_drm_crtc_create(drm_dev, &ctx->planes[0].base, + &ctx->planes[1].base, ctx->pipe, ctx); + + if (IS_ERR(ctx->crtc)) { + ret = PTR_ERR(ctx->crtc); + goto unprepare; + } + + return 0; + +unprepare: + while (--i >= 0) + clk_unprepare(ctx->ddp_comp[i].clk); + of_node_put(node); + + return ret; +} + +static void mtk_crtc_ddp_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_crtc_ddp_context *ctx = dev_get_drvdata(dev); + int i; + + for (i = 0; i < ctx->ddp_comp_nr; i++) + clk_unprepare(ctx->ddp_comp[i].clk); +} + +static const struct component_ops mtk_crtc_ddp_component_ops = { + .bind = mtk_crtc_ddp_bind, + .unbind = mtk_crtc_ddp_unbind, +}; + +static int mtk_crtc_ddp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_crtc_ddp_context *ctx; + int irq; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, mtk_crtc_ddp_irq_handler, + IRQF_TRIGGER_NONE, dev_name(dev), ctx); + if (ret < 0) { + dev_err(dev, "Failed to request irq %d: %d\n", irq, ret); + return -ENXIO; + } + + platform_set_drvdata(pdev, ctx); + + ret = component_add(dev, &mtk_crtc_ddp_component_ops); + if (ret) + dev_err(dev, "Failed to add component: %d\n", ret); + + return ret; +} + +static int mtk_crtc_ddp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &mtk_crtc_ddp_component_ops); + + return 0; +} + +static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-ovl", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match); + +struct platform_driver mtk_disp_ovl_driver = { + .probe = mtk_crtc_ddp_probe, + .remove = mtk_crtc_ddp_remove, + .driver = { + .name = "mediatek-disp-ovl", + .owner = THIS_MODULE, + .of_match_table = mtk_disp_ovl_driver_dt_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.h b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h new file mode 100644 index 0000000..b696066 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_CRTC_H +#define MTK_DRM_CRTC_H + +#include <drm/drm_crtc.h> +#include "mtk_drm_plane.h" + +#define OVL_LAYER_NR 4 + +struct mtk_crtc_state { + struct drm_crtc_state base; + struct drm_pending_vblank_event *event; + + unsigned int pending_update; + bool pending_needs_vblank; + + bool pending_config; + unsigned int pending_width; + unsigned int pending_height; + unsigned int pending_vrefresh; + + bool pending_ovl_config[OVL_LAYER_NR]; + bool pending_ovl_enable[OVL_LAYER_NR]; + unsigned int pending_ovl_addr[OVL_LAYER_NR]; + unsigned int pending_ovl_pitch[OVL_LAYER_NR]; + unsigned int pending_ovl_format[OVL_LAYER_NR]; + int pending_ovl_x[OVL_LAYER_NR]; + int pending_ovl_y[OVL_LAYER_NR]; + unsigned int pending_ovl_size[OVL_LAYER_NR]; + bool pending_ovl_dirty[OVL_LAYER_NR]; +}; + +static inline struct mtk_crtc_state *to_mtk_crtc_state(struct drm_crtc_state *s) +{ + return container_of(s, struct mtk_crtc_state, base); +} + +int mtk_drm_crtc_enable_vblank(struct drm_device *drm, int pipe); +void mtk_drm_crtc_disable_vblank(struct drm_device *drm, int pipe); +void mtk_drm_crtc_plane_config(struct drm_crtc *crtc, unsigned int idx, + bool enable, dma_addr_t addr); +void mtk_drm_crtc_commit(struct drm_crtc *crtc); + +#endif /* MTK_DRM_CRTC_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c new file mode 100644 index 0000000..9ec2960 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp.h" +#include "mtk_drm_ddp_comp.h" + +#define DISP_REG_CONFIG_DISP_OVL0_MOUT_EN 0x040 +#define DISP_REG_CONFIG_DISP_OVL1_MOUT_EN 0x044 +#define DISP_REG_CONFIG_DISP_OD_MOUT_EN 0x048 +#define DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN 0x04c +#define DISP_REG_CONFIG_DISP_UFOE_MOUT_EN 0x050 +#define DISP_REG_CONFIG_DISP_COLOR0_SEL_IN 0x084 +#define DISP_REG_CONFIG_DISP_COLOR1_SEL_IN 0x088 +#define DISP_REG_CONFIG_DPI_SEL_IN 0x0ac +#define DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN 0x0c8 +#define DISP_REG_CONFIG_MMSYS_CG_CON0 0x100 + +#define DISP_REG_MUTEX_EN(n) (0x20 + 0x20 * n) +#define DISP_REG_MUTEX_RST(n) (0x28 + 0x20 * n) +#define DISP_REG_MUTEX_MOD(n) (0x2c + 0x20 * n) +#define DISP_REG_MUTEX_SOF(n) (0x30 + 0x20 * n) + +#define MUTEX_MOD_OVL0 11 +#define MUTEX_MOD_OVL1 12 +#define MUTEX_MOD_RDMA0 13 +#define MUTEX_MOD_RDMA1 14 +#define MUTEX_MOD_COLOR0 18 +#define MUTEX_MOD_COLOR1 19 +#define MUTEX_MOD_AAL 20 +#define MUTEX_MOD_GAMMA 21 +#define MUTEX_MOD_UFOE 22 +#define MUTEX_MOD_PWM0 23 +#define MUTEX_MOD_OD 25 + +#define MUTEX_SOF_DSI0 1 +#define MUTEX_SOF_DPI0 3 + +#define OVL0_MOUT_EN_COLOR0 0x1 +#define OD_MOUT_EN_RDMA0 0x1 +#define UFOE_MOUT_EN_DSI0 0x1 +#define COLOR0_SEL_IN_OVL0 0x1 +#define OVL1_MOUT_EN_COLOR1 0x1 +#define GAMMA_MOUT_EN_RDMA1 0x1 +#define RDMA1_MOUT_DPI0 0x2 +#define DPI0_SEL_IN_RDMA1 0x1 +#define COLOR1_SEL_IN_OVL1 0x1 + +static const unsigned int mutex_mod[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL] = MUTEX_MOD_AAL, + [DDP_COMPONENT_COLOR0] = MUTEX_MOD_COLOR0, + [DDP_COMPONENT_COLOR1] = MUTEX_MOD_COLOR1, + [DDP_COMPONENT_GAMMA] = MUTEX_MOD_GAMMA, + [DDP_COMPONENT_OD] = MUTEX_MOD_OD, + [DDP_COMPONENT_OVL0] = MUTEX_MOD_OVL0, + [DDP_COMPONENT_OVL1] = MUTEX_MOD_OVL1, + [DDP_COMPONENT_PWM0] = MUTEX_MOD_PWM0, + [DDP_COMPONENT_RDMA0] = MUTEX_MOD_RDMA0, + [DDP_COMPONENT_RDMA1] = MUTEX_MOD_RDMA1, + [DDP_COMPONENT_UFOE] = MUTEX_MOD_UFOE, +}; + +void mtk_ddp_add_comp_to_path(void __iomem *config_regs, unsigned int pipe, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next) +{ + unsigned int addr, value; + + if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) { + addr = DISP_REG_CONFIG_DISP_OVL0_MOUT_EN; + value = OVL0_MOUT_EN_COLOR0; + } else if (cur == DDP_COMPONENT_OD && next == DDP_COMPONENT_RDMA0) { + addr = DISP_REG_CONFIG_DISP_OD_MOUT_EN; + value = OD_MOUT_EN_RDMA0; + } else if (cur == DDP_COMPONENT_UFOE && next == DDP_COMPONENT_DSI0) { + addr = DISP_REG_CONFIG_DISP_UFOE_MOUT_EN; + value = UFOE_MOUT_EN_DSI0; + } else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) { + addr = DISP_REG_CONFIG_DISP_OVL1_MOUT_EN; + value = OVL1_MOUT_EN_COLOR1; + } else if (cur == DDP_COMPONENT_GAMMA && next == DDP_COMPONENT_RDMA1) { + addr = DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN; + value = GAMMA_MOUT_EN_RDMA1; + } else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) { + addr = DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN; + value = RDMA1_MOUT_DPI0; + } else { + value = 0; + } + if (value) { + unsigned int reg = readl(config_regs + addr) | value; + + writel(reg, config_regs + addr); + } + + if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) { + addr = DISP_REG_CONFIG_DISP_COLOR0_SEL_IN; + value = COLOR0_SEL_IN_OVL0; + } else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) { + addr = DISP_REG_CONFIG_DPI_SEL_IN; + value = DPI0_SEL_IN_RDMA1; + } else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) { + addr = DISP_REG_CONFIG_DISP_COLOR1_SEL_IN; + value = COLOR1_SEL_IN_OVL1; + } else { + value = 0; + } + if (value) { + unsigned int reg = readl(config_regs + addr) | value; + + writel(reg, config_regs + addr); + } +} + +void mtk_ddp_add_comp_to_mutex(struct device *dev, unsigned int pipe, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next) +{ + struct mtk_ddp *ddp = dev_get_drvdata(dev); + unsigned int reg; + + reg = readl(ddp->mutex_regs + DISP_REG_MUTEX_MOD(pipe)); + reg |= BIT(mutex_mod[cur]) | BIT(mutex_mod[next]); + writel(reg, ddp->mutex_regs + DISP_REG_MUTEX_MOD(pipe)); + + if (next == DDP_COMPONENT_DPI0) + reg = MUTEX_SOF_DPI0; + else + reg = MUTEX_SOF_DSI0; + + writel(reg, ddp->mutex_regs + DISP_REG_MUTEX_SOF(pipe)); + writel(1, ddp->mutex_regs + DISP_REG_MUTEX_EN(pipe)); +} + +void mtk_ddp_clock_on(struct device *dev) +{ + struct mtk_ddp *ddp = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(ddp->mutex_disp_clk); + if (ret != 0) + DRM_ERROR("clk_prepare_enable(mutex_disp_clk) error!\n"); +} + +void mtk_ddp_clock_off(struct device *dev) +{ + struct mtk_ddp *ddp = dev_get_drvdata(dev); + + clk_disable_unprepare(ddp->mutex_disp_clk); +} + +static int mtk_ddp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_ddp *ddp; + struct resource *regs; + + ddp = devm_kzalloc(dev, sizeof(*ddp), GFP_KERNEL); + if (!ddp) + return -ENOMEM; + + ddp->mutex_disp_clk = devm_clk_get(dev, NULL); + if (IS_ERR(ddp->mutex_disp_clk)) { + dev_err(dev, "Failed to get clock\n"); + return PTR_ERR(ddp->mutex_disp_clk); + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddp->mutex_regs = devm_ioremap_resource(dev, regs); + if (IS_ERR(ddp->mutex_regs)) { + dev_err(dev, "Failed to map mutex registers\n"); + return PTR_ERR(ddp->mutex_regs); + } + + platform_set_drvdata(pdev, ddp); + + return 0; +} + +static int mtk_ddp_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id ddp_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-mutex" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ddp_driver_dt_match); + +struct platform_driver mtk_ddp_driver = { + .probe = mtk_ddp_probe, + .remove = mtk_ddp_remove, + .driver = { + .name = "mediatek-ddp", + .owner = THIS_MODULE, + .of_match_table = ddp_driver_dt_match, + }, +}; + +module_platform_driver(mtk_ddp_driver); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h new file mode 100644 index 0000000..55c2db3 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_DDP_H +#define MTK_DRM_DDP_H + +#include "mtk_drm_ddp_comp.h" + +struct regmap; +struct device; + +struct mtk_ddp { + struct device *dev; + struct drm_device *drm_dev; + + struct clk *mutex_disp_clk; + void __iomem *mutex_regs; +}; + +void mtk_ddp_add_comp_to_path(void __iomem *config_regs, unsigned int pipe, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next); +void mtk_ddp_add_comp_to_mutex(struct device *dev, unsigned int pipe, + enum mtk_ddp_comp_id cur, + enum mtk_ddp_comp_id next); +void mtk_ddp_clock_on(struct device *dev); +void mtk_ddp_clock_off(struct device *dev); + +#endif /* MTK_DRM_DDP_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c new file mode 100644 index 0000000..2f3b32b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Authors: + * YT Shen yt.shen@mediatek.com + * CK Hu ck.hu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <drm/drmP.h> +#include "mtk_drm_ddp_comp.h" + +#define DISP_REG_OVL_INTEN 0x0004 +#define DISP_REG_OVL_INTSTA 0x0008 +#define DISP_REG_OVL_EN 0x000c +#define DISP_REG_OVL_RST 0x0014 +#define DISP_REG_OVL_ROI_SIZE 0x0020 +#define DISP_REG_OVL_ROI_BGCLR 0x0028 +#define DISP_REG_OVL_SRC_CON 0x002c +#define DISP_REG_OVL_CON(n) (0x0030 + 0x20 * n) +#define DISP_REG_OVL_SRC_SIZE(n) (0x0038 + 0x20 * n) +#define DISP_REG_OVL_OFFSET(n) (0x003c + 0x20 * n) +#define DISP_REG_OVL_PITCH(n) (0x0044 + 0x20 * n) +#define DISP_REG_OVL_RDMA_CTRL(n) (0x00c0 + 0x20 * n) +#define DISP_REG_OVL_RDMA_GMC(n) (0x00c8 + 0x20 * n) +#define DISP_REG_OVL_ADDR(n) (0x0f40 + 0x20 * n) + +#define DISP_REG_RDMA_INT_ENABLE 0x0000 +#define DISP_REG_RDMA_INT_STATUS 0x0004 +#define DISP_REG_RDMA_GLOBAL_CON 0x0010 +#define DISP_REG_RDMA_SIZE_CON_0 0x0014 +#define DISP_REG_RDMA_SIZE_CON_1 0x0018 +#define DISP_REG_RDMA_FIFO_CON 0x0040 +#define RDMA_FIFO_UNDERFLOW_EN BIT(31) +#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16) +#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16) + +#define DISP_OD_EN 0x0000 +#define DISP_OD_INTEN 0x0008 +#define DISP_OD_INTSTA 0x000c +#define DISP_OD_CFG 0x0020 +#define DISP_OD_SIZE 0x0030 + +#define DISP_REG_UFO_START 0x0000 + +#define DISP_COLOR_CFG_MAIN 0x0400 +#define DISP_COLOR_START 0x0c00 + +enum OVL_INPUT_FORMAT { + OVL_INFMT_RGB565 = 0, + OVL_INFMT_RGB888 = 1, + OVL_INFMT_RGBA8888 = 2, + OVL_INFMT_ARGB8888 = 3, +}; + +#define OVL_RDMA_MEM_GMC 0x40402020 +#define OVL_AEN BIT(8) +#define OVL_ALPHA 0xff + +#define OD_RELAY_MODE BIT(0) + +#define UFO_BYPASS BIT(2) + +#define COLOR_BYPASS_ALL BIT(7) +#define COLOR_SEQ_SEL BIT(13) + +static void mtk_color_start(void __iomem *color_base) +{ + writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL, + color_base + DISP_COLOR_CFG_MAIN); + writel(0x1, color_base + DISP_COLOR_START); +} + +static void mtk_od_config(void __iomem *od_base, unsigned int w, unsigned int h, + unsigned int vrefresh) +{ + writel(w << 16 | h, od_base + DISP_OD_SIZE); +} + +static void mtk_od_start(void __iomem *od_base) +{ + writel(OD_RELAY_MODE, od_base + DISP_OD_CFG); + writel(1, od_base + DISP_OD_EN); +} + +static void mtk_ovl_enable_vblank(void __iomem *disp_base) +{ + writel(0x2, disp_base + DISP_REG_OVL_INTEN); +} + +static void mtk_ovl_disable_vblank(void __iomem *disp_base) +{ + writel(0x0, disp_base + DISP_REG_OVL_INTEN); +} + +static void mtk_ovl_clear_vblank(void __iomem *disp_base) +{ + writel(0x0, disp_base + DISP_REG_OVL_INTSTA); +} + +static void mtk_ovl_start(void __iomem *ovl_base) +{ + writel(0x1, ovl_base + DISP_REG_OVL_EN); +} + +static void mtk_ovl_config(void __iomem *ovl_base, + unsigned int w, unsigned int h, unsigned int vrefresh) +{ + if (w != 0 && h != 0) + writel(h << 16 | w, ovl_base + DISP_REG_OVL_ROI_SIZE); + writel(0x0, ovl_base + DISP_REG_OVL_ROI_BGCLR); + + writel(0x1, ovl_base + DISP_REG_OVL_RST); + writel(0x0, ovl_base + DISP_REG_OVL_RST); +} + +static bool has_rb_swapped(unsigned int fmt) +{ + switch (fmt) { + case DRM_FORMAT_BGR888: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + return true; + default: + return false; + } +} + +static unsigned int ovl_fmt_convert(unsigned int fmt) +{ + switch (fmt) { + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + return OVL_INFMT_RGB888; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return OVL_INFMT_RGB565; + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + return OVL_INFMT_ARGB8888; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return OVL_INFMT_RGBA8888; + default: + DRM_ERROR("unsupport format[%08x]\n", fmt); + return -EINVAL; + } +} + +static void mtk_ovl_layer_on(void __iomem *ovl_base, unsigned int idx) +{ + unsigned int reg; + + writel(0x1, ovl_base + DISP_REG_OVL_RDMA_CTRL(idx)); + writel(OVL_RDMA_MEM_GMC, ovl_base + DISP_REG_OVL_RDMA_GMC(idx)); + + reg = readl(ovl_base + DISP_REG_OVL_SRC_CON); + reg = reg | (1 << idx); + writel(reg, ovl_base + DISP_REG_OVL_SRC_CON); +} + +static void mtk_ovl_layer_off(void __iomem *ovl_base, unsigned int idx) +{ + unsigned int reg; + + reg = readl(ovl_base + DISP_REG_OVL_SRC_CON); + reg = reg & ~(1 << idx); + writel(reg, ovl_base + DISP_REG_OVL_SRC_CON); + + writel(0x0, ovl_base + DISP_REG_OVL_RDMA_CTRL(idx)); +} + +static void mtk_ovl_layer_config(void __iomem *ovl_base, unsigned int idx, + unsigned int addr, unsigned int pitch, unsigned int fmt, + int x, int y, unsigned int size) +{ + unsigned int reg; + + reg = has_rb_swapped(fmt) << 24 | ovl_fmt_convert(fmt) << 12; + if (idx != 0) + reg |= OVL_AEN | OVL_ALPHA; + + writel(reg, ovl_base + DISP_REG_OVL_CON(idx)); + writel(pitch & 0xFFFF, ovl_base + DISP_REG_OVL_PITCH(idx)); + writel(size, ovl_base + DISP_REG_OVL_SRC_SIZE(idx)); + writel(y << 16 | x, ovl_base + DISP_REG_OVL_OFFSET(idx)); + writel(addr, ovl_base + DISP_REG_OVL_ADDR(idx)); +} + +static void mtk_rdma_start(void __iomem *rdma_base) +{ + unsigned int reg; + + writel(0x4, rdma_base + DISP_REG_RDMA_INT_ENABLE); + reg = readl(rdma_base + DISP_REG_RDMA_GLOBAL_CON); + reg |= 1; + writel(reg, rdma_base + DISP_REG_RDMA_GLOBAL_CON); +} + +static void mtk_rdma_config(void __iomem *rdma_base, + unsigned width, unsigned height, unsigned int vrefresh) +{ + unsigned int threshold; + unsigned int reg; + + reg = readl(rdma_base + DISP_REG_RDMA_SIZE_CON_0); + reg = (reg & ~(0xFFF)) | (width & 0xFFF); + writel(reg, rdma_base + DISP_REG_RDMA_SIZE_CON_0); + + reg = readl(rdma_base + DISP_REG_RDMA_SIZE_CON_1); + reg = (reg & ~(0xFFFFF)) | (height & 0xFFFFF); + writel(reg, rdma_base + DISP_REG_RDMA_SIZE_CON_1); + + /* + * Enable FIFO underflow since DSI and DPI can't be blocked. + * Keep the FIFO pseudo size reset default of 8 KiB. Set the + * output threshold to 6 microseconds with 7/6 overhead to + * account for blanking, and with a pixel depth of 4 bytes: + */ + threshold = width * height * vrefresh * 4 * 7 / 1000000; + reg = RDMA_FIFO_UNDERFLOW_EN | + RDMA_FIFO_PSEUDO_SIZE(SZ_8K) | + RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold); + writel(reg, rdma_base + DISP_REG_RDMA_FIFO_CON); +} + +static void mtk_ufoe_start(void __iomem *ufoe_base) +{ + writel(UFO_BYPASS, ufoe_base + DISP_REG_UFO_START); +} + +static const struct mtk_ddp_comp_funcs ddp_aal = { + .comp_id = DDP_COMPONENT_AAL, +}; + +static const struct mtk_ddp_comp_funcs ddp_color0 = { + .comp_id = DDP_COMPONENT_COLOR0, + .comp_power_on = mtk_color_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_color1 = { + .comp_id = DDP_COMPONENT_COLOR1, + .comp_power_on = mtk_color_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_dpi0 = { + .comp_id = DDP_COMPONENT_DPI0, +}; + +static const struct mtk_ddp_comp_funcs ddp_dsi0 = { + .comp_id = DDP_COMPONENT_DSI0, +}; + +static const struct mtk_ddp_comp_funcs ddp_dsi1 = { + .comp_id = DDP_COMPONENT_DSI1, +}; + +static const struct mtk_ddp_comp_funcs ddp_gamma = { + .comp_id = DDP_COMPONENT_GAMMA, +}; + +static const struct mtk_ddp_comp_funcs ddp_od = { + .comp_id = DDP_COMPONENT_OD, + .comp_config = mtk_od_config, + .comp_power_on = mtk_od_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_ovl0 = { + .comp_id = DDP_COMPONENT_OVL0, + .comp_config = mtk_ovl_config, + .comp_power_on = mtk_ovl_start, + .comp_enable_vblank = mtk_ovl_enable_vblank, + .comp_disable_vblank = mtk_ovl_disable_vblank, + .comp_clear_vblank = mtk_ovl_clear_vblank, + .comp_layer_on = mtk_ovl_layer_on, + .comp_layer_off = mtk_ovl_layer_off, + .comp_layer_config = mtk_ovl_layer_config, +}; + +static const struct mtk_ddp_comp_funcs ddp_ovl1 = { + .comp_id = DDP_COMPONENT_OVL1, + .comp_config = mtk_ovl_config, + .comp_power_on = mtk_ovl_start, + .comp_enable_vblank = mtk_ovl_enable_vblank, + .comp_disable_vblank = mtk_ovl_disable_vblank, + .comp_clear_vblank = mtk_ovl_clear_vblank, + .comp_layer_on = mtk_ovl_layer_on, + .comp_layer_off = mtk_ovl_layer_off, + .comp_layer_config = mtk_ovl_layer_config, +}; + +static const struct mtk_ddp_comp_funcs ddp_pwm0 = { + .comp_id = DDP_COMPONENT_PWM0, +}; + +static const struct mtk_ddp_comp_funcs ddp_rdma0 = { + .comp_id = DDP_COMPONENT_RDMA0, + .comp_config = mtk_rdma_config, + .comp_power_on = mtk_rdma_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_rdma1 = { + .comp_id = DDP_COMPONENT_RDMA1, + .comp_config = mtk_rdma_config, + .comp_power_on = mtk_rdma_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_rdma2 = { + .comp_id = DDP_COMPONENT_RDMA2, + .comp_config = mtk_rdma_config, + .comp_power_on = mtk_rdma_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_ufoe = { + .comp_id = DDP_COMPONENT_UFOE, + .comp_power_on = mtk_ufoe_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_wdma0 = { + .comp_id = DDP_COMPONENT_WDMA0, +}; + +static const struct mtk_ddp_comp_funcs ddp_wdma1 = { + .comp_id = DDP_COMPONENT_WDMA1, +}; + +static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = { + [MTK_DISP_OVL] = "ovl", + [MTK_DISP_RDMA] = "rdma", + [MTK_DISP_WDMA] = "wdma", + [MTK_DISP_COLOR] = "color", + [MTK_DISP_AAL] = "aal", + [MTK_DISP_GAMMA] = "gamma", + [MTK_DISP_UFOE] = "ufoe", + [MTK_DSI] = "dsi", + [MTK_DPI] = "dpi", + [MTK_DISP_PWM] = "pwm", + [MTK_DISP_MUTEX] = "mutex", + [MTK_DISP_OD] = "od", +}; + +struct mtk_ddp_comp_match { + enum mtk_ddp_comp_type type; + int alias_id; + const struct mtk_ddp_comp_funcs *funcs; +}; + +static struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL] = { MTK_DISP_AAL, 0, &ddp_aal }, + [DDP_COMPONENT_COLOR0] = { MTK_DISP_COLOR, 0, &ddp_color0 }, + [DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, &ddp_color1 }, + [DDP_COMPONENT_DPI0] = { MTK_DPI, 0, &ddp_dpi0 }, + [DDP_COMPONENT_DSI0] = { MTK_DSI, 0, &ddp_dsi0 }, + [DDP_COMPONENT_DSI1] = { MTK_DSI, 1, &ddp_dsi1 }, + [DDP_COMPONENT_GAMMA] = { MTK_DISP_GAMMA, 0, &ddp_gamma }, + [DDP_COMPONENT_OD] = { MTK_DISP_OD, 0, &ddp_od }, + [DDP_COMPONENT_OVL0] = { MTK_DISP_OVL, 0, &ddp_ovl0 }, + [DDP_COMPONENT_OVL1] = { MTK_DISP_OVL, 1, &ddp_ovl1 }, + [DDP_COMPONENT_PWM0] = { MTK_DISP_PWM, 0, &ddp_pwm0 }, + [DDP_COMPONENT_RDMA0] = { MTK_DISP_RDMA, 0, &ddp_rdma0 }, + [DDP_COMPONENT_RDMA1] = { MTK_DISP_RDMA, 1, &ddp_rdma1 }, + [DDP_COMPONENT_RDMA2] = { MTK_DISP_RDMA, 2, &ddp_rdma2 }, + [DDP_COMPONENT_UFOE] = { MTK_DISP_UFOE, 0, &ddp_ufoe }, + [DDP_COMPONENT_WDMA0] = { MTK_DISP_WDMA, 0, &ddp_wdma0 }, + [DDP_COMPONENT_WDMA1] = { MTK_DISP_WDMA, 1, &ddp_wdma1 }, +}; + +int mtk_ddp_comp_get_id(struct device_node *node, + enum mtk_ddp_comp_type comp_type) +{ + int id = of_alias_get_id(node, mtk_ddp_comp_stem[comp_type]); + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) { + if (comp_type == mtk_ddp_matches[i].type && + (id < 0 || id == mtk_ddp_matches[i].alias_id)) + return i; + } + + return -EINVAL; +} + +int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp, + enum mtk_ddp_comp_id comp_id) +{ + if (comp_id < 0 || comp_id >= DDP_COMPONENT_ID_MAX) + return -EINVAL; + + comp->funcs = mtk_ddp_matches[comp_id].funcs; + + if (comp_id == DDP_COMPONENT_DPI0 || + comp_id == DDP_COMPONENT_DSI0 || + comp_id == DDP_COMPONENT_PWM0) { + comp->regs = NULL; + comp->clk = NULL; + comp->irq = 0; + return 0; + } + + comp->regs = of_iomap(node, 0); + comp->irq = of_irq_get(node, 0); + comp->clk = of_clk_get(node, 0); + if (IS_ERR(comp->clk)) + comp->clk = NULL; + + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h new file mode 100644 index 0000000..ae3a6e8 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_DDP_COMP_H +#define MTK_DRM_DDP_COMP_H + +#include <linux/io.h> + +struct device_node; + +enum mtk_ddp_comp_type { + MTK_DISP_OVL, + MTK_DISP_RDMA, + MTK_DISP_WDMA, + MTK_DISP_COLOR, + MTK_DISP_AAL, + MTK_DISP_GAMMA, + MTK_DISP_UFOE, + MTK_DSI, + MTK_DPI, + MTK_DISP_PWM, + MTK_DISP_MUTEX, + MTK_DISP_OD, + MTK_DDP_COMP_TYPE_MAX, +}; + +enum mtk_ddp_comp_id { + DDP_COMPONENT_AAL, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_DPI0, + DDP_COMPONENT_DSI0, + DDP_COMPONENT_DSI1, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_OD, + DDP_COMPONENT_OVL0, + DDP_COMPONENT_OVL1, + DDP_COMPONENT_PWM0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_RDMA2, + DDP_COMPONENT_UFOE, + DDP_COMPONENT_WDMA0, + DDP_COMPONENT_WDMA1, + DDP_COMPONENT_ID_MAX, +}; + +struct mtk_ddp_comp_funcs { + enum mtk_ddp_comp_id comp_id; + void (*comp_config)(void __iomem *ovl_base, + unsigned int w, unsigned int h, unsigned int vrefresh); + void (*comp_power_on)(void __iomem *ovl_base); + void (*comp_power_off)(void __iomem *ovl_base); + void (*comp_enable_vblank)(void __iomem *ovl_base); + void (*comp_disable_vblank)(void __iomem *ovl_base); + void (*comp_clear_vblank)(void __iomem *ovl_base); + void (*comp_layer_on)(void __iomem *ovl_base, unsigned int idx); + void (*comp_layer_off)(void __iomem *ovl_base, unsigned int idx); + void (*comp_layer_config)(void __iomem *ovl_base, unsigned int idx, + unsigned int addr, unsigned int pitch, unsigned int fmt, + int x, int y, unsigned int size); +}; + +struct mtk_ddp_comp { + struct clk *clk; + void __iomem *regs; + int irq; + const struct mtk_ddp_comp_funcs *funcs; +}; + +int mtk_ddp_comp_get_id(struct device_node *node, + enum mtk_ddp_comp_type comp_type); +int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp, + enum mtk_ddp_comp_id comp_id); + +#endif /* MTK_DRM_DDP_COMP_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c new file mode 100644 index 0000000..fbca99f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: YT SHEN yt.shen@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem.h> +#include <linux/component.h> +#include <linux/dma-iommu.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <soc/mediatek/smi.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h" + +#define DRIVER_NAME "mediatek" +#define DRIVER_DESC "Mediatek SoC DRM" +#define DRIVER_DATE "20150513" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static void mtk_atomic_schedule(struct mtk_drm_private *private, + struct drm_atomic_state *state) +{ + private->commit.state = state; + schedule_work(&private->commit.work); +} + +static void mtk_atomic_complete(struct mtk_drm_private *private, + struct drm_atomic_state *state) +{ + struct drm_device *drm = private->drm; + + drm_atomic_helper_commit_modeset_disables(drm, state); + drm_atomic_helper_commit_planes(drm, state); + drm_atomic_helper_commit_modeset_enables(drm, state); + drm_atomic_helper_wait_for_vblanks(drm, state); + drm_atomic_helper_cleanup_planes(drm, state); + drm_atomic_state_free(state); +} + +static void mtk_atomic_work(struct work_struct *work) +{ + struct mtk_drm_private *private = container_of(work, + struct mtk_drm_private, commit.work); + + mtk_atomic_complete(private, private->commit.state); +} + +static int mtk_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *state, + bool async) +{ + struct mtk_drm_private *private = drm->dev_private; + int ret; + + ret = drm_atomic_helper_prepare_planes(drm, state); + if (ret) + return ret; + + mutex_lock(&private->commit.lock); + flush_work(&private->commit.work); + + drm_atomic_helper_swap_state(drm, state); + + if (async) + mtk_atomic_schedule(private, state); + else + mtk_atomic_complete(private, state); + + mutex_unlock(&private->commit.lock); + + return 0; +} + +static const struct drm_mode_config_funcs mtk_drm_mode_config_funcs = { + .fb_create = mtk_drm_mode_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = mtk_atomic_commit, +}; + +static const enum mtk_ddp_comp_id mtk_ddp_main[] = { + DDP_COMPONENT_OVL0, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_AAL, + DDP_COMPONENT_OD, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_UFOE, + DDP_COMPONENT_DSI0, + DDP_COMPONENT_PWM0, +}; + +static const enum mtk_ddp_comp_id mtk_ddp_ext[] = { + DDP_COMPONENT_OVL1, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_DPI0, +}; + +static int mtk_drm_kms_init(struct drm_device *drm) +{ + struct mtk_drm_private *private = drm->dev_private; + struct platform_device *pdev; + int ret; + int i; + + pdev = of_find_device_by_node(private->mutex_node); + if (!pdev) { + dev_err(drm->dev, "Waiting for disp-mutex device %s\n", + private->mutex_node->full_name); + of_node_put(private->mutex_node); + return -EPROBE_DEFER; + } + private->mutex_dev = &pdev->dev; + + for (i = 0; i < MAX_CRTC; i++) { + if (!private->larb_node[i]) + break; + + pdev = of_find_device_by_node(private->larb_node[i]); + if (!pdev) { + dev_err(drm->dev, "Waiting for larb device %s\n", + private->larb_node[i]->full_name); + return -EPROBE_DEFER; + } + private->larb_dev[i] = &pdev->dev; + } + + drm_mode_config_init(drm); + + drm->mode_config.min_width = 64; + drm->mode_config.min_height = 64; + + /* + * set max width and height as default value(4096x4096). + * this value would be used to check framebuffer size limitation + * at drm_mode_addfb(). + */ + drm->mode_config.max_width = 4096; + drm->mode_config.max_height = 4096; + drm->mode_config.funcs = &mtk_drm_mode_config_funcs; + + /* + * We currently support two fixed data streams, + * OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0 + * and OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0. + */ + private->path_len[0] = ARRAY_SIZE(mtk_ddp_main); + private->path[0] = mtk_ddp_main; + private->path_len[1] = ARRAY_SIZE(mtk_ddp_ext); + private->path[1] = mtk_ddp_ext; + + ret = component_bind_all(drm->dev, drm); + if (ret) + goto err_crtc; + + /* + * We don't use the drm_irq_install() helpers provided by the DRM + * core, so we need to set this manually in order to allow the + * DRM_IOCTL_WAIT_VBLANK to operate correctly. + */ + drm->irq_enabled = true; + ret = drm_vblank_init(drm, MAX_CRTC); + if (ret < 0) + goto err_unbind; + + for (i = 0; i < MAX_CRTC; i++) { + if (!private->larb_dev[i]) + break; + + ret = mtk_smi_larb_get(private->larb_dev[i]); + if (ret) { + DRM_ERROR("Failed to get larb: %d\n", ret); + goto err_larb_get; + } + } + + drm_kms_helper_poll_init(drm); + drm_mode_config_reset(drm); + + return 0; + +err_larb_get: + for (i = i - 1; i >= 0; i--) + mtk_smi_larb_put(private->larb_dev[i]); + drm_kms_helper_poll_fini(drm); + drm_vblank_cleanup(drm); +err_unbind: + component_unbind_all(drm->dev, drm); +err_crtc: + drm_mode_config_cleanup(drm); + + return ret; +} + +static void mtk_drm_kms_deinit(struct drm_device *drm) +{ + drm_kms_helper_poll_fini(drm); + + drm_vblank_cleanup(drm); + drm_mode_config_cleanup(drm); +} + +static int mtk_drm_unload(struct drm_device *drm) +{ + mtk_drm_kms_deinit(drm); + drm->dev_private = NULL; + + return 0; +} + +static const struct vm_operations_struct mtk_drm_gem_vm_ops = { + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations mtk_drm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .mmap = mtk_drm_gem_mmap, + .poll = drm_poll, + .read = drm_read, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif +}; + +static struct drm_driver mtk_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM, + .unload = mtk_drm_unload, + .set_busid = drm_platform_set_busid, + + .get_vblank_counter = drm_vblank_count, + .enable_vblank = mtk_drm_crtc_enable_vblank, + .disable_vblank = mtk_drm_crtc_disable_vblank, + + .gem_free_object = mtk_drm_gem_free_object, + .gem_vm_ops = &mtk_drm_gem_vm_ops, + .dumb_create = mtk_drm_gem_dumb_create, + .dumb_map_offset = mtk_drm_gem_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + + .fops = &mtk_drm_fops, + + .set_busid = drm_platform_set_busid, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int mtk_drm_bind(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&mtk_drm_driver, dev); + if (!drm) + return -ENOMEM; + + drm_dev_set_unique(drm, dev_name(dev)); + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto err_free; + + drm->dev_private = private; + private->drm = drm; + + ret = mtk_drm_kms_init(drm); + if (ret < 0) + goto err_unregister; + + return 0; + +err_unregister: + drm_dev_unregister(drm); +err_free: + drm_dev_unref(drm); + return ret; +} + +static void mtk_drm_unbind(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + + drm_put_dev(private->drm); + private->drm = NULL; +} + +static const struct component_master_ops mtk_drm_ops = { + .bind = mtk_drm_bind, + .unbind = mtk_drm_unbind, +}; + +static const struct of_device_id mtk_ddp_comp_dt_ids[] = { + { .compatible = "mediatek,mt8173-disp-ovl", .data = (void *)MTK_DISP_OVL }, + { .compatible = "mediatek,mt8173-disp-rdma", .data = (void *)MTK_DISP_RDMA }, + { .compatible = "mediatek,mt8173-disp-wdma", .data = (void *)MTK_DISP_WDMA }, + { .compatible = "mediatek,mt8173-disp-color", .data = (void *)MTK_DISP_COLOR }, + { .compatible = "mediatek,mt8173-disp-aal", .data = (void *)MTK_DISP_AAL}, + { .compatible = "mediatek,mt8173-disp-gamma", .data = (void *)MTK_DISP_GAMMA, }, + { .compatible = "mediatek,mt8173-disp-ufoe", .data = (void *)MTK_DISP_UFOE }, + { .compatible = "mediatek,mt8173-dsi", .data = (void *)MTK_DSI }, + { .compatible = "mediatek,mt8173-dpi", .data = (void *)MTK_DPI }, + { .compatible = "mediatek,mt8173-disp-mutex", .data = (void *)MTK_DISP_MUTEX }, + { .compatible = "mediatek,mt8173-disp-od", .data = (void *)MTK_DISP_OD }, + { } +}; + +static int mtk_drm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_drm_private *private; + struct resource *mem; + struct device_node *node; + struct component_match *match = NULL; + int ret; + int num_larbs = 0; + + private = devm_kzalloc(dev, sizeof(*private), GFP_KERNEL); + if (!private) + return -ENOMEM; + + mutex_init(&private->commit.lock); + INIT_WORK(&private->commit.work, mtk_atomic_work); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + private->config_regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(private->config_regs)) { + ret = PTR_ERR(private->config_regs); + dev_err(dev, "Failed to ioremap mmsys-config resource: %d\n", + ret); + return ret; + } + + /* Iterate over sibling DISP function blocks */ + for_each_child_of_node(dev->of_node->parent, node) { + const struct of_device_id *of_id; + enum mtk_ddp_comp_type comp_type; + int comp_id; + + of_id = of_match_node(mtk_ddp_comp_dt_ids, node); + if (!of_id) + continue; + + comp_type = (enum mtk_ddp_comp_type)of_id->data; + + if (comp_type == MTK_DISP_MUTEX) { + private->mutex_node = of_node_get(node); + continue; + } + + if (!of_device_is_available(node)) { + dev_dbg(dev, "Skipping disabled component %s\n", + node->full_name); + continue; + } + + comp_id = mtk_ddp_comp_get_id(node, comp_type); + if (comp_id < 0) { + dev_info(dev, "Skipping unknown component %s\n", + node->full_name); + continue; + } + + /* + * Currently only the OVL, DSI, and DPI blocks have separate + * component platform drivers. + */ + if (comp_type == MTK_DISP_OVL || + comp_type == MTK_DSI || + comp_type == MTK_DPI) { + dev_info(dev, "Adding component match for %s\n", + node->full_name); + component_match_add(dev, &match, compare_of, node); + } + + private->comp_node[comp_id] = of_node_get(node); + + if (comp_type == MTK_DISP_OVL) { + struct device_node *larb_node; + + larb_node = of_parse_phandle(node, "mediatek,larb", 0); + if (larb_node && num_larbs < MAX_CRTC) + private->larb_node[num_larbs++] = larb_node; + } + } + + if (!private->mutex_node) { + dev_err(dev, "Failed to find disp-mutex node\n"); + return -ENODEV; + } + + pm_runtime_enable(dev); + + platform_set_drvdata(pdev, private); + + return component_master_add_with_match(dev, &mtk_drm_ops, match); +} + +static int mtk_drm_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &mtk_drm_ops); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_drm_sys_suspend(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + struct drm_connector *conn; + int i; + + drm_kms_helper_poll_disable(drm); + + drm_modeset_lock_all(drm); + list_for_each_entry(conn, &drm->mode_config.connector_list, head) { + int old_dpms = conn->dpms; + + if (conn->funcs->dpms) + conn->funcs->dpms(conn, DRM_MODE_DPMS_OFF); + + /* Set the old mode back to the connector for resume */ + conn->dpms = old_dpms; + } + drm_modeset_unlock_all(drm); + + for (i = 0; i < MAX_CRTC; i++) { + if (!private->larb_dev[i]) + break; + + mtk_smi_larb_put(private->larb_dev[i]); + } + + DRM_INFO("mtk_drm_sys_suspend\n"); + return 0; +} + +static int mtk_drm_sys_resume(struct device *dev) +{ + struct mtk_drm_private *private = dev_get_drvdata(dev); + struct drm_device *drm = private->drm; + struct drm_connector *conn; + int ret; + int i; + + for (i = 0; i < MAX_CRTC; i++) { + if (!private->larb_dev[i]) + break; + + ret = mtk_smi_larb_get(private->larb_dev[i]); + if (ret) { + DRM_ERROR("mtk_smi_larb_get fail %d\n", ret); + return ret; + } + } + + drm_modeset_lock_all(drm); + list_for_each_entry(conn, &drm->mode_config.connector_list, head) { + int desired_mode = conn->dpms; + + /* + * at suspend time, we save dpms to connector->dpms, + * restore the old_dpms, and at current time, the connector + * dpms status must be DRM_MODE_DPMS_OFF. + */ + conn->dpms = DRM_MODE_DPMS_OFF; + + /* + * If the connector has been disconnected during suspend, + * disconnect it from the encoder and leave it off. We'll notify + * userspace at the end. + */ + if (conn->funcs->dpms) + conn->funcs->dpms(conn, desired_mode); + } + drm_modeset_unlock_all(drm); + + drm_kms_helper_poll_enable(drm); + + DRM_INFO("mtk_drm_sys_resume\n"); + return 0; +} + +SIMPLE_DEV_PM_OPS(mtk_drm_pm_ops, mtk_drm_sys_suspend, mtk_drm_sys_resume); +#endif + +static const struct of_device_id mtk_drm_of_ids[] = { + { .compatible = "mediatek,mt8173-mmsys", }, + { } +}; + +static struct platform_driver mtk_drm_platform_driver = { + .probe = mtk_drm_probe, + .remove = mtk_drm_remove, + .driver = { + .name = "mediatek-drm", + .of_match_table = mtk_drm_of_ids, +#ifdef CONFIG_PM_SLEEP + .pm = &mtk_drm_pm_ops, +#endif + }, +}; + +static int __init mtk_drm_init(void) +{ + int ret; + + ret = platform_driver_register(&mtk_drm_platform_driver); + if (ret < 0) { + pr_err("Failed to register DRM platform driver: %d\n", ret); + goto err; + } + + ret = platform_driver_register(&mtk_disp_ovl_driver); + if (ret < 0) { + pr_err("Failed to register OVL platform driver: %d\n", ret); + goto drm_err; + } + + return 0; + +drm_err: + platform_driver_unregister(&mtk_drm_platform_driver); +err: + return ret; +} + +static void __exit mtk_drm_exit(void) +{ + platform_driver_unregister(&mtk_disp_ovl_driver); + platform_driver_unregister(&mtk_drm_platform_driver); +} + +module_init(mtk_drm_init); +module_exit(mtk_drm_exit); + +MODULE_AUTHOR("YT SHEN yt.shen@mediatek.com"); +MODULE_DESCRIPTION("Mediatek SoC DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h new file mode 100644 index 0000000..5e5128e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_DRV_H +#define MTK_DRM_DRV_H + +#include <linux/io.h> +#include "mtk_drm_ddp_comp.h" + +#define MAX_CRTC 2 +#define MAX_CONNECTOR 2 + +struct device; +struct device_node; +struct drm_crtc; +struct drm_device; +struct drm_fb_helper; +struct drm_property; +struct regmap; + +struct mtk_drm_private { + struct drm_fb_helper *fb_helper; + struct drm_device *drm; + + /* + * created crtc object would be contained at this array and + * this array is used to be aware of which crtc did it request vblank. + */ + struct drm_crtc *crtc[MAX_CRTC]; + struct drm_property *plane_zpos_property; + unsigned int pipe; + + struct device_node *larb_node[MAX_CRTC]; + struct device *larb_dev[MAX_CRTC]; + struct device_node *mutex_node; + struct device *mutex_dev; + void __iomem *config_regs; + unsigned int path_len[MAX_CRTC]; + const enum mtk_ddp_comp_id *path[MAX_CRTC]; + struct device_node *comp_node[DDP_COMPONENT_ID_MAX]; + + struct { + struct drm_atomic_state *state; + struct work_struct work; + struct mutex lock; + } commit; +}; + +extern struct platform_driver mtk_disp_ovl_driver; + +#endif /* MTK_DRM_DRV_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c new file mode 100644 index 0000000..dfa931b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h" + +/* + * mtk specific framebuffer structure. + * + * @fb: drm framebuffer object. + * @gem_obj: array of gem objects. + */ +struct mtk_drm_fb { + struct drm_framebuffer base; + struct drm_gem_object *gem_obj[MAX_FB_OBJ]; +}; + +#define to_mtk_fb(x) container_of(x, struct mtk_drm_fb, base) + +struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane) +{ + struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); + + if (plane >= MAX_FB_OBJ) + return NULL; + + return mtk_fb->gem_obj[plane]; +} + +static int mtk_drm_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); + + return drm_gem_handle_create(file_priv, mtk_fb->gem_obj[0], handle); +} + +static void mtk_drm_fb_destroy(struct drm_framebuffer *fb) +{ + unsigned int i; + struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb); + struct drm_gem_object *gem; + int nr = drm_format_num_planes(fb->pixel_format); + + drm_framebuffer_cleanup(fb); + + for (i = 0; i < nr; i++) { + gem = mtk_fb->gem_obj[i]; + drm_gem_object_unreference_unlocked(gem); + } + + kfree(mtk_fb); +} + +static const struct drm_framebuffer_funcs mtk_drm_fb_funcs = { + .create_handle = mtk_drm_fb_create_handle, + .destroy = mtk_drm_fb_destroy, +}; + +static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode, + struct drm_gem_object **obj) +{ + struct mtk_drm_fb *mtk_fb; + unsigned int i; + int ret; + + mtk_fb = kzalloc(sizeof(*mtk_fb), GFP_KERNEL); + if (!mtk_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(&mtk_fb->base, mode); + + for (i = 0; i < drm_format_num_planes(mode->pixel_format); i++) + mtk_fb->gem_obj[i] = obj[i]; + + ret = drm_framebuffer_init(dev, &mtk_fb->base, &mtk_drm_fb_funcs); + if (ret) { + DRM_ERROR("failed to initialize framebuffer\n"); + kfree(mtk_fb); + return ERR_PTR(ret); + } + + return mtk_fb; +} + +struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, + struct drm_file *file, + struct drm_mode_fb_cmd2 *cmd) +{ + unsigned int hsub, vsub, i; + struct mtk_drm_fb *mtk_fb; + struct drm_gem_object *gem[MAX_FB_OBJ]; + int err; + + hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format); + vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format); + for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) { + unsigned int width = cmd->width / (i ? hsub : 1); + unsigned int height = cmd->height / (i ? vsub : 1); + unsigned int size, bpp; + + gem[i] = drm_gem_object_lookup(dev, file, cmd->handles[i]); + if (!gem[i]) { + err = -ENOENT; + goto unreference; + } + + bpp = drm_format_plane_cpp(cmd->pixel_format, i); + size = (height - 1) * cmd->pitches[i] + width * bpp; + size += cmd->offsets[i]; + + if (gem[i]->size < size) { + drm_gem_object_unreference_unlocked(gem[i]); + err = -EINVAL; + goto unreference; + } + } + + mtk_fb = mtk_drm_framebuffer_init(dev, cmd, gem); + if (IS_ERR(mtk_fb)) { + err = PTR_ERR(mtk_fb); + goto unreference; + } + + return &mtk_fb->base; + +unreference: + while (i--) + drm_gem_object_unreference_unlocked(gem[i]); + + return ERR_PTR(err); +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h new file mode 100644 index 0000000..9ce7307 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MTK_DRM_FB_H +#define MTK_DRM_FB_H + +#define MAX_FB_OBJ 3 + +struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane); +struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, + struct drm_file *file, + struct drm_mode_fb_cmd2 *cmd); + +void mtk_drm_mode_output_poll_changed(struct drm_device *dev); +int mtk_fbdev_create(struct drm_device *dev); +void mtk_fbdev_destroy(struct drm_device *dev); + +#endif /* MTK_DRM_FB_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.c b/drivers/gpu/drm/mediatek/mtk_drm_gem.c new file mode 100644 index 0000000..34fb94c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_gem.h> + +#include "mtk_drm_gem.h" + +struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev, + unsigned long size) +{ + struct mtk_drm_gem_obj *mtk_gem_obj; + int ret; + + size = round_up(size, PAGE_SIZE); + + mtk_gem_obj = kzalloc(sizeof(*mtk_gem_obj), GFP_KERNEL); + if (!mtk_gem_obj) + return ERR_PTR(-ENOMEM); + + ret = drm_gem_object_init(dev, &mtk_gem_obj->base, size); + if (ret < 0) { + DRM_ERROR("failed to initialize gem object\n"); + kfree(mtk_gem_obj); + return ERR_PTR(ret); + } + + return mtk_gem_obj; +} + +struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev, + unsigned long size, bool alloc_kmap) +{ + struct mtk_drm_gem_obj *mtk_gem; + struct drm_gem_object *obj; + int ret; + + mtk_gem = mtk_drm_gem_init(dev, size); + if (IS_ERR(mtk_gem)) + return ERR_CAST(mtk_gem); + + obj = &mtk_gem->base; + + init_dma_attrs(&mtk_gem->dma_attrs); + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &mtk_gem->dma_attrs); + + if (!alloc_kmap) + dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &mtk_gem->dma_attrs); + + mtk_gem->cookie = dma_alloc_attrs(dev->dev, obj->size, + (dma_addr_t *)&mtk_gem->dma_addr, GFP_KERNEL, + &mtk_gem->dma_attrs); + if (!mtk_gem->cookie) { + DRM_ERROR("failed to allocate %zx byte dma buffer", obj->size); + ret = -ENOMEM; + goto err_gem_free; + } + + if (alloc_kmap) + mtk_gem->kvaddr = mtk_gem->cookie; + + DRM_INFO("cookie = %p dma_addr = %llx\n", + mtk_gem->cookie, mtk_gem->dma_addr); + + return mtk_gem; + +err_gem_free: + drm_gem_object_release(obj); + kfree(mtk_gem); + return ERR_PTR(ret); +} + +void mtk_drm_gem_free_object(struct drm_gem_object *obj) +{ + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + + drm_gem_free_mmap_offset(obj); + + /* release file pointer to gem object. */ + drm_gem_object_release(obj); + + dma_free_attrs(obj->dev->dev, obj->size, mtk_gem->cookie, + mtk_gem->dma_addr, &mtk_gem->dma_attrs); + + kfree(mtk_gem); +} + +int mtk_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct mtk_drm_gem_obj *mtk_gem; + int ret; + + args->pitch = args->width * DIV_ROUND_UP(args->bpp, 8); + args->size = args->pitch * args->height; + + mtk_gem = mtk_drm_gem_create(dev, args->size, false); + if (IS_ERR(mtk_gem)) + return PTR_ERR(mtk_gem); + + /* + * allocate a id of idr table where the obj is registered + * and handle has the id what user can see. + */ + ret = drm_gem_handle_create(file_priv, &mtk_gem->base, &args->handle); + if (ret) + goto err_handle_create; + + /* drop reference from allocate - handle holds it now. */ + drm_gem_object_unreference_unlocked(&mtk_gem->base); + + return 0; + +err_handle_create: + mtk_drm_gem_free_object(&mtk_gem->base); + return ret; +} + +int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret; + + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + return -EINVAL; + } + + ret = drm_gem_create_mmap_offset(obj); + if (ret) + goto out; + + *offset = drm_vma_node_offset_addr(&obj->vma_node); + DRM_DEBUG_KMS("offset = 0x%llx\n", *offset); + +out: + drm_gem_object_unreference_unlocked(obj); + return ret; +} + +static int mtk_drm_gem_object_mmap(struct drm_gem_object *obj, + struct vm_area_struct *vma) + +{ + int ret; + struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj); + struct drm_device *drm = obj->dev; + + /* + * dma_alloc_attrs() allocated a struct page table for rk_obj, so clear + * VM_PFNMAP flag that was set by drm_gem_mmap_obj()/drm_gem_mmap(). + */ + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_pgoff = 0; + + ret = dma_mmap_attrs(drm->dev, vma, mtk_gem->cookie, mtk_gem->dma_addr, + obj->size, &mtk_gem->dma_attrs); + if (ret) + drm_gem_vm_close(vma); + + return ret; +} + +int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_gem_object *obj; + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + + obj = vma->vm_private_data; + + return mtk_drm_gem_object_mmap(obj, vma); +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.h b/drivers/gpu/drm/mediatek/mtk_drm_gem.h new file mode 100644 index 0000000..fb7953e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MTK_DRM_GEM_H_ +#define _MTK_DRM_GEM_H_ + +#include <drm/drm_gem.h> + +/* + * mtk drm buffer structure. + * + * @base: a gem object. + * - a new handle to this gem object would be created + * by drm_gem_handle_create(). + * @cookie: the return value of dma_alloc_attrs(), keep it for dma_free_attrs() + * @kvaddr: kernel virtual address of gem buffer. + * @dma_addr: dma address of gem buffer. + * @dma_attrs: dma attributes of gem buffer. + * + * P.S. this object would be transferred to user as kms_bo.handle so + * user can access the buffer through kms_bo.handle. + */ +struct mtk_drm_gem_obj { + struct drm_gem_object base; + void __iomem *cookie; + void __iomem *kvaddr; + dma_addr_t dma_addr; + struct dma_attrs dma_attrs; +}; + +#define to_mtk_gem_obj(x) container_of(x, struct mtk_drm_gem_obj, base) + +struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev, + unsigned long size); +void mtk_drm_gem_free_object(struct drm_gem_object *gem); +struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev, + unsigned long size, bool alloc_kmap); +int mtk_drm_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, struct drm_mode_create_dumb *args); +int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, uint64_t *offset); +int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int mtk_drm_gem_mmap_buf(struct drm_gem_object *obj, + struct vm_area_struct *vma); + +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c new file mode 100644 index 0000000..3a8843c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: CK Hu ck.hu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/dma-buf.h> +#include <linux/reservation.h> + +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h" + +static const uint32_t formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB565, +}; + +static const struct drm_plane_funcs mtk_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int mtk_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + struct drm_crtc_state *crtc_state; + bool visible; + int ret; + struct drm_rect dest = { + .x1 = state->crtc_x, + .y1 = state->crtc_y, + .x2 = state->crtc_x + state->crtc_w, + .y2 = state->crtc_y + state->crtc_h, + }; + struct drm_rect src = { + /* 16.16 fixed point */ + .x1 = state->src_x, + .y1 = state->src_y, + .x2 = state->src_x + state->src_w, + .y2 = state->src_y + state->src_h, + }; + struct drm_rect clip = { 0, }; + + if (!fb) + return 0; + + if (!mtk_fb_get_gem_obj(fb, 0)) { + DRM_DEBUG_KMS("buffer is null\n"); + return -EFAULT; + } + + if (!state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + clip.x2 = crtc_state->mode.hdisplay; + clip.y2 = crtc_state->mode.vdisplay; + + ret = drm_plane_helper_check_update(plane, state->crtc, fb, + &src, &dest, &clip, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + true, true, &visible); + if (ret) + return ret; + + return 0; +} + +static void mtk_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = plane->state; + struct drm_gem_object *gem_obj; + struct drm_crtc *crtc = state->crtc; + struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane); + struct drm_rect dest = { + .x1 = state->crtc_x, + .y1 = state->crtc_y, + .x2 = state->crtc_x + state->crtc_w, + .y2 = state->crtc_y + state->crtc_h, + }; + struct drm_rect clip = { 0, }; + + if (!crtc) + return; + + clip.x2 = state->crtc->state->mode.hdisplay; + clip.y2 = state->crtc->state->mode.vdisplay; + drm_rect_intersect(&dest, &clip); + mtk_plane->disp_size = (dest.y2 - dest.y1) << 16 | (dest.x2 - dest.x1); + + plane->fb = state->fb; + + gem_obj = mtk_fb_get_gem_obj(state->fb, 0); + mtk_plane->flip_obj = to_mtk_gem_obj(gem_obj); + mtk_plane->crtc = crtc; + + if (mtk_plane->flip_obj) + mtk_drm_crtc_plane_config(crtc, mtk_plane->idx, true, + mtk_plane->flip_obj->dma_addr); +} + +static void mtk_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane); + struct drm_crtc *crtc = old_state->crtc; + + if (!crtc) + return; + + mtk_drm_crtc_plane_config(crtc, mtk_plane->idx, false, 0); + + mtk_drm_crtc_commit(crtc); +} + +static const struct drm_plane_helper_funcs mtk_plane_helper_funcs = { + .atomic_check = mtk_plane_atomic_check, + .atomic_update = mtk_plane_atomic_update, + .atomic_disable = mtk_plane_atomic_disable, +}; + +int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane, + unsigned long possible_crtcs, enum drm_plane_type type, + unsigned int zpos, unsigned int max_plane) +{ + int err; + + err = drm_universal_plane_init(dev, &mtk_plane->base, possible_crtcs, + &mtk_plane_funcs, formats, ARRAY_SIZE(formats), type); + + if (err) { + DRM_ERROR("failed to initialize plane\n"); + return err; + } + + drm_plane_helper_add(&mtk_plane->base, &mtk_plane_helper_funcs); + mtk_plane->idx = zpos; + + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.h b/drivers/gpu/drm/mediatek/mtk_drm_plane.h new file mode 100644 index 0000000..e092c34 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: CK Hu ck.hu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MTK_DRM_PLANE_H_ +#define _MTK_DRM_PLANE_H_ + +#include <drm/drm_crtc.h> + +struct mtk_drm_gem_obj; + +struct mtk_drm_plane { + struct drm_plane base; + struct drm_crtc *crtc; + unsigned int idx; + unsigned int disp_size; + + struct mtk_drm_gem_obj *flip_obj; +}; + +static inline struct mtk_drm_plane *to_mtk_plane(struct drm_plane *plane) +{ + return container_of(plane, struct mtk_drm_plane, base); +} + +void mtk_plane_finish_page_flip(struct mtk_drm_plane *mtk_plane); +int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane, + unsigned long possible_crtcs, enum drm_plane_type type, + unsigned int zpos, unsigned int max_plane); + +#endif
Hi Philipp,
A bunch of review comments inline.
On Wed, Nov 4, 2015 at 7:44 PM, Philipp Zabel p.zabel@pengutronix.de wrote:
From: CK Hu ck.hu@mediatek.com
This patch adds an initial DRM driver for the Mediatek MT8173 DISP subsystem. It currently supports two fixed output streams from the OVL0/OVL1 sources to the DSI0/DPI0 sinks, respectively.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: YT Shen yt.shen@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de
Changes since v4:
- Add mtk_crtc_state to keep pending state
- Move drm pending vblank event into mtk_crtc_state
- Make mtk_drm_crtc private
- Use drm_dev_alloc and drm_dev_register directly instead of drm_platform_init
- Drop unnecessary locking in mtk_drm_gem_dump_map_offset
- Remove currently unused mtk_drm_gem_mmap_buf
- Stop referencing plane framebuffers manually
- Set RDMA FIFO output threshold depending on frame width/height/rate
drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/mediatek/Kconfig | 16 + drivers/gpu/drm/mediatek/Makefile | 10 + drivers/gpu/drm/mediatek/mtk_drm_crtc.c | 590 ++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.h | 56 +++ drivers/gpu/drm/mediatek/mtk_drm_ddp.c | 218 ++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.h | 39 ++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 424 ++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 86 ++++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 572 +++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.h | 61 +++ drivers/gpu/drm/mediatek/mtk_drm_fb.c | 151 +++++++ drivers/gpu/drm/mediatek/mtk_drm_fb.h | 29 ++ drivers/gpu/drm/mediatek/mtk_drm_gem.c | 189 +++++++++ drivers/gpu/drm/mediatek/mtk_drm_gem.h | 56 +++ drivers/gpu/drm/mediatek/mtk_drm_plane.c | 167 ++++++++ drivers/gpu/drm/mediatek/mtk_drm_plane.h | 41 ++ 18 files changed, 2708 insertions(+) create mode 100644 drivers/gpu/drm/mediatek/Kconfig create mode 100644 drivers/gpu/drm/mediatek/Makefile create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_crtc.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_drv.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_fb.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_gem.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_plane.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1a0a8df..9e9987b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -264,3 +264,5 @@ source "drivers/gpu/drm/sti/Kconfig" source "drivers/gpu/drm/amd/amdkfd/Kconfig"
source "drivers/gpu/drm/imx/Kconfig"
+source "drivers/gpu/drm/mediatek/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 45e7719..af6b592 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-$(CONFIG_DRM_STI) += sti/ obj-$(CONFIG_DRM_IMX) += imx/ +obj-$(CONFIG_DRM_MEDIATEK) += mediatek/ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig new file mode 100644 index 0000000..5343cf1 --- /dev/null +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -0,0 +1,16 @@ +config DRM_MEDIATEK
tristate "DRM Support for Mediatek SoCs"
depends on DRM
depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST)
select MTK_SMI
select DRM_PANEL
select DRM_MIPI_DSI
select DRM_PANEL_SIMPLE
select DRM_KMS_HELPER
select IOMMU_DMA
help
Choose this option if you have a Mediatek SoCs.
The module will be called mediatek-drm
This driver provides kernel mode setting and
buffer management to userspace.
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile new file mode 100644 index 0000000..ba6d3fc --- /dev/null +++ b/drivers/gpu/drm/mediatek/Makefile @@ -0,0 +1,10 @@ +mediatek-drm-y := mtk_drm_drv.o \
mtk_drm_crtc.o \
mtk_drm_ddp.o \
mtk_drm_ddp_comp.o \
mtk_drm_fb.o \
mtk_drm_gem.o \
mtk_drm_plane.o
+obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.c b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c new file mode 100644 index 0000000..4493714 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,590 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/dma-buf.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reservation.h>
+#include "mtk_drm_drv.h" +#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h"
+struct mtk_crtc_ddp_context;
+/*
- MediaTek specific crtc structure.
- @base: crtc object.
- @pipe: a crtc index created at load() with a new crtc object creation
and the crtc object would be set to private->crtc array
to get a crtc object corresponding to this pipe from private->crtc
array when irq interrupt occurred. the reason of using this pipe is that
drm framework doesn't support multiple irq yet.
we can refer to the crtc to current hardware interrupt occurred through
this pipe value.
- @enabled: crtc enabled
- @ctx: mtk crtc context object
- */
+struct mtk_drm_crtc {
struct drm_crtc base;
unsigned int pipe;
There is one pipe too many :-) I think this one is not used."
bool enabled;
struct mtk_crtc_ddp_context *ctx;
I think you should be able to just embed the "mtk_crtc_ddp_context" right into mtk_drm_crtc. Or maybe just get rid of mtk_crtc_ddp_context completely and just use mtk_drm_crtc eveywhere.
bool do_flush;
+};
+struct mtk_crtc_ddp_context {
struct device *dev;
struct drm_device *drm_dev;
struct mtk_drm_crtc *crtc;
struct mtk_drm_plane planes[OVL_LAYER_NR];
int pipe;
void __iomem *config_regs;
struct device *mutex_dev;
u32 ddp_comp_nr;
struct mtk_ddp_comp *ddp_comp;
+};
+static inline struct mtk_drm_crtc *to_mtk_crtc(struct drm_crtc *c) +{
return container_of(c, struct mtk_drm_crtc, base);
+}
+void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc) +{
struct drm_crtc *crtc = &mtk_crtc->base;
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
struct drm_device *dev = mtk_crtc->base.dev;
drm_send_vblank_event(dev, state->event->pipe, state->event);
drm_crtc_vblank_put(crtc);
state->event = NULL;
+}
+static void mtk_drm_crtc_destroy(struct drm_crtc *crtc) +{
drm_crtc_cleanup(crtc);
+}
+struct drm_crtc_state *mtk_drm_crtc_duplicate_state(struct drm_crtc *crtc) +{
struct mtk_crtc_state *crtc_state;
crtc_state = kzalloc(sizeof(*crtc_state), GFP_KERNEL);
if (!crtc_state)
return NULL;
__drm_atomic_helper_crtc_duplicate_state(crtc, &crtc_state->base);
crtc_state->base.crtc = crtc;
return &crtc_state->base;
+}
+static bool mtk_drm_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
+{
/* drm framework doesn't check NULL */
return true;
+}
+static void mtk_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) +{
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
state->pending_width = crtc->mode.hdisplay;
state->pending_height = crtc->mode.vdisplay;
state->pending_vrefresh = crtc->mode.vrefresh;
state->pending_config = true;
+}
+int mtk_drm_crtc_enable_vblank(struct drm_device *drm, int pipe) +{
struct mtk_drm_private *priv = drm->dev_private;
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]);
struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx;
struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
if (ovl->funcs->comp_enable_vblank)
ovl->funcs->comp_enable_vblank(ovl->regs);
return 0;
+}
+void mtk_drm_crtc_disable_vblank(struct drm_device *drm, int pipe) +{
struct mtk_drm_private *priv = drm->dev_private;
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]);
struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx;
struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
if (ovl->funcs->comp_disable_vblank)
ovl->funcs->comp_disable_vblank(ovl->regs);
+}
+static void mtk_crtc_ddp_power_on(struct mtk_crtc_ddp_context *ctx) +{
int ret;
int i;
DRM_INFO("mtk_crtc_ddp_power_on\n");
for (i = 0; i < ctx->ddp_comp_nr; i++) {
ret = clk_enable(ctx->ddp_comp[i].clk);
if (ret)
DRM_ERROR("clk_enable(ctx->ddp_comp_clk[%d])\n", i);
}
+}
+static void mtk_crtc_ddp_power_off(struct mtk_crtc_ddp_context *ctx) +{
int i;
DRM_INFO("mtk_crtc_ddp_power_off\n");
for (i = 0; i < ctx->ddp_comp_nr; i++)
clk_disable(ctx->ddp_comp[i].clk);
+}
+static void mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *crtc) +{
struct mtk_crtc_ddp_context *ctx = crtc->ctx;
struct drm_device *drm = crtc->base.dev;
unsigned int width, height, vrefresh;
int ret;
int i;
if (ctx->crtc->base.state) {
width = crtc->base.state->adjusted_mode.hdisplay;
height = crtc->base.state->adjusted_mode.vdisplay;
vrefresh = crtc->base.state->adjusted_mode.vrefresh;
} else {
width = 1920;
height = 1080;
vrefresh = 60;
}
DRM_INFO("mtk_crtc_ddp_hw_init\n");
/* disp_mtcmos */
ret = pm_runtime_get_sync(drm->dev);
if (ret < 0)
DRM_ERROR("Failed to enable power domain: %d\n", ret);
mtk_ddp_clock_on(ctx->mutex_dev);
mtk_crtc_ddp_power_on(ctx);
get_sync(), clock_on() and ddp_power_on() really can fail; we are just ignoring errors here. Since they can fail, shouldn't they be moved out of the atomic "->enable()" path into the ->check() path that is allowed to fail?
DRM_INFO("mediatek_ddp_ddp_path_setup\n");
for (i = 0; i < (ctx->ddp_comp_nr - 1); i++) {
nit: the inner () are not necessary.
mtk_ddp_add_comp_to_path(ctx->config_regs, ctx->pipe,
ctx->ddp_comp[i].funcs->comp_id,
ctx->ddp_comp[i+1].funcs->comp_id);
mtk_ddp_add_comp_to_mutex(ctx->mutex_dev, ctx->pipe,
ctx->ddp_comp[i].funcs->comp_id,
ctx->ddp_comp[i+1].funcs->comp_id);
}
Do you really have to do this here in the enable path? This looks like something that should be done in bind()?
Perhaps all we really need here is to walk the path and write to DISP_REG_MUTEX_EN at the end of mtk_ddp_add_comp_to_mutex(). By the way, where are those bits cleared in the disable path?
DRM_INFO("ddp_disp_path_power_on %dx%d\n", width, height);
for (i = 0; i < ctx->ddp_comp_nr; i++) {
struct mtk_ddp_comp *comp = &ctx->ddp_comp[i];
if (comp->funcs->comp_config)
comp->funcs->comp_config(comp->regs, width, height,
vrefresh);
if (comp->funcs->comp_power_on)
comp->funcs->comp_power_on(comp->regs);
}
+}
+static void mtk_crtc_ddp_hw_fini(struct mtk_drm_crtc *crtc) +{
struct mtk_crtc_ddp_context *ctx = crtc->ctx;
struct drm_device *drm = crtc->base.dev;
struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->base.state);
int i;
DRM_INFO("mtk_crtc_ddp_hw_fini\n");
for (i = 0; i < OVL_LAYER_NR; i++) {
state->pending_ovl_enable[i] = false;
ovl->funcs->comp_layer_off(ovl->regs, i);
}
mtk_crtc_ddp_power_off(ctx);
mtk_ddp_clock_off(ctx->mutex_dev);
/* disp_mtcmos */
pm_runtime_put_sync(drm->dev);
Why sync?
+}
+static void mtk_drm_crtc_enable(struct drm_crtc *crtc) +{
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
if (WARN_ON(mtk_crtc->enabled))
return;
mtk_crtc_ddp_hw_init(mtk_crtc);
drm_crtc_vblank_on(crtc);
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
mtk_crtc->enabled = true;
+}
+static void mtk_drm_crtc_disable(struct drm_crtc *crtc) +{
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
DRM_INFO("mtk_drm_crtc_disable %d\n", crtc->base.id);
if (WARN_ON(!mtk_crtc->enabled))
return;
mtk_crtc_ddp_hw_fini(mtk_crtc);
mtk_crtc->do_flush = false;
if (state->event)
mtk_drm_crtc_finish_page_flip(mtk_crtc);
It is a bit awkward to send the page flip event before the actual page flip has completed (in this case, for a page flip that you are canceling by disabling the crtc). Would it be better to wait for vblank here in crtc_disable instead?
drm_crtc_vblank_put(crtc);
drm_crtc_vblank_off(crtc);
mtk_crtc->enabled = false;
+}
+static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
+{
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
if (state->base.event) {
state->base.event->pipe = drm_crtc_index(crtc);
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
state->event = state->base.event;
state->base.event = NULL;
Hmm. Why are we "stealing" event from drm_crtc_state and handing it over to the wrapper mtk_crtc_state? Won't we still be able to retrieve state->base.event later when we need it in the mtk_drm_crtc_finish_page_flip?
}
+}
+void mtk_drm_crtc_commit(struct drm_crtc *crtc) +{
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
unsigned int i;
for (i = 0; i < OVL_LAYER_NR; i++)
if (state->pending_ovl_dirty[i]) {
state->pending_ovl_config[i] = true;
state->pending_ovl_dirty[i] = false;
}
+}
+static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
+{
mtk_drm_crtc_commit(crtc);
+}
+static const struct drm_crtc_funcs mtk_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.destroy = mtk_drm_crtc_destroy,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = mtk_drm_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+static const struct drm_crtc_helper_funcs mtk_crtc_helper_funcs = {
.mode_fixup = mtk_drm_crtc_mode_fixup,
.mode_set_nofb = mtk_drm_crtc_mode_set_nofb,
.enable = mtk_drm_crtc_enable,
.disable = mtk_drm_crtc_disable,
.atomic_begin = mtk_drm_crtc_atomic_begin,
.atomic_flush = mtk_drm_crtc_atomic_flush,
+};
+struct mtk_drm_crtc *mtk_drm_crtc_create(struct drm_device *drm,
struct drm_plane *primary, struct drm_plane *cursor, int pipe,
void *ctx)
+{
struct mtk_drm_private *priv = drm->dev_private;
struct mtk_drm_crtc *mtk_crtc;
int ret;
mtk_crtc = devm_kzalloc(drm->dev, sizeof(*mtk_crtc), GFP_KERNEL);
if (!mtk_crtc)
return ERR_PTR(-ENOMEM);
mtk_crtc->pipe = pipe;
mtk_crtc->ctx = ctx;
mtk_crtc->enabled = false;
priv->crtc[pipe] = &mtk_crtc->base;
ret = drm_crtc_init_with_planes(drm, &mtk_crtc->base, primary, cursor,
&mtk_crtc_funcs);
if (ret)
goto err_cleanup_crtc;
drm_crtc_helper_add(&mtk_crtc->base, &mtk_crtc_helper_funcs);
return mtk_crtc;
+err_cleanup_crtc:
drm_crtc_cleanup(&mtk_crtc->base);
return ERR_PTR(ret);
+}
+void mtk_drm_crtc_plane_config(struct drm_crtc *crtc, unsigned int idx,
bool enable, dma_addr_t addr)
+{
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx;
struct drm_plane *plane = &ctx->planes[idx].base;
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
unsigned int pitch, format;
int x, y;
if (!plane->fb || !plane->state)
return;
pitch = plane->fb->pitches[0];
format = plane->fb->pixel_format;
x = plane->state->crtc_x;
y = plane->state->crtc_y;
if (x < 0) {
addr -= x * 4;
x = 0;
}
if (y < 0) {
addr -= y * pitch;
y = 0;
}
state->pending_ovl_enable[idx] = enable;
state->pending_ovl_addr[idx] = addr;
state->pending_ovl_pitch[idx] = pitch;
state->pending_ovl_format[idx] = format;
state->pending_ovl_x[idx] = x;
state->pending_ovl_y[idx] = y;
state->pending_ovl_size[idx] = ctx->planes[idx].disp_size;
state->pending_ovl_dirty[idx] = true;
+}
+static void mtk_crtc_ddp_irq(struct mtk_crtc_ddp_context *ctx)
==> So, this is the part of the driver that makes me the most nervous. Why are we writing the actual hardware registers in the *vblank interrupt handler*?! Every other display controller that I've ever seen has shadow registers that are used to stage a page flip at the next vblank. Are you sure this hardware doesn't support this?
In any case, how do we get that first interrupt in which to apply the register?
+{
struct mtk_drm_crtc *mtk_crtc = ctx->crtc;
struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
ddp_comp[0] here looks a bit like black magic. "The 0th component is always the ovl". Perhaps make an explicitly named "ovl" field for mtk_crtc_ddp_context.
struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state);
unsigned int i;
if (state->pending_config) {
state->pending_config = false;
for (i = 0; i < OVL_LAYER_NR; i++)
ovl->funcs->comp_layer_off(ovl->regs, i);
Why do you need to turn off all layers before setting mode?
ovl->funcs->comp_config(ovl->regs, state->pending_width,
state->pending_height,
state->pending_vrefresh);
}
for (i = 0; i < OVL_LAYER_NR; i++) {
if (state->pending_ovl_config[i]) {
if (!state->pending_ovl_enable[i])
ovl->funcs->comp_layer_off(ovl->regs, i);
ovl->funcs->comp_layer_config(ovl->regs, i,
state->pending_ovl_addr[i],
state->pending_ovl_pitch[i],
state->pending_ovl_format[i],
state->pending_ovl_x[i],
state->pending_ovl_y[i],
state->pending_ovl_size[i]);
if (state->pending_ovl_enable[i])
ovl->funcs->comp_layer_on(ovl->regs, i);
}
I'd move this all all with an mtk_plane function, and let each mtk_plane store its own pending state.
state->pending_ovl_config[i] = false;
}
drm_handle_vblank(ctx->drm_dev, ctx->pipe);
+}
+static irqreturn_t mtk_crtc_ddp_irq_handler(int irq, void *dev_id) +{
struct mtk_crtc_ddp_context *ctx = dev_id;
struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
if (ovl->funcs->comp_clear_vblank)
ovl->funcs->comp_clear_vblank(ovl->regs);
if (ctx->pipe >= 0 && ctx->drm_dev)
mtk_crtc_ddp_irq(ctx);
return IRQ_HANDLED;
+}
+static int mtk_crtc_ddp_bind(struct device *dev, struct device *master,
void *data)
+{
struct mtk_crtc_ddp_context *ctx = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
struct mtk_drm_private *priv = drm_dev->dev_private;
struct device_node *node;
enum drm_plane_type type;
unsigned int zpos;
int ret;
int i;
ctx->drm_dev = drm_dev;
ctx->pipe = priv->pipe++;
ctx->config_regs = priv->config_regs;
ctx->mutex_dev = priv->mutex_dev;
ctx->ddp_comp_nr = priv->path_len[ctx->pipe];
ctx->ddp_comp = devm_kmalloc_array(dev, ctx->ddp_comp_nr,
sizeof(*ctx->ddp_comp), GFP_KERNEL);
/* priv->path[ctx->pipe] starts with this OVL node */
priv->comp_node[priv->path[ctx->pipe][0]] = dev->of_node;
for (i = 0; i < ctx->ddp_comp_nr; i++) {
struct mtk_ddp_comp *comp = &ctx->ddp_comp[i];
enum mtk_ddp_comp_id comp_id = priv->path[ctx->pipe][i];
ret = mtk_ddp_comp_init(priv->comp_node[comp_id], comp,
comp_id);
if (ret) {
dev_err(dev, "Failed to initialize component %i\n", i);
goto unprepare;
}
if (comp->clk) {
ret = clk_prepare(comp->clk);
if (ret) {
dev_err(dev, "Failed to prepare clock %d\n", i);
ret = -EINVAL;
goto unprepare;
}
}
}
for (zpos = 0; zpos < OVL_LAYER_NR; zpos++) {
type = (zpos == 0) ? DRM_PLANE_TYPE_PRIMARY :
(zpos == 1) ? DRM_PLANE_TYPE_CURSOR :
DRM_PLANE_TYPE_OVERLAY;
ret = mtk_plane_init(drm_dev, &ctx->planes[zpos],
1 << ctx->pipe, type, zpos, OVL_LAYER_NR);
if (ret)
goto unprepare;
}
I think you should just let mtk_drm_crtc_create() create & init its own planes. Currently, its overlay planes are unassigned. Furthermore, the crtc context doesn't really need an array of planes at all. It only really uses the array to know which planes to update during the vblank irq. But, that information is actually already present in the atomic state to be applied itself.
ctx->crtc = mtk_drm_crtc_create(drm_dev, &ctx->planes[0].base,
&ctx->planes[1].base, ctx->pipe, ctx);
if (IS_ERR(ctx->crtc)) {
ret = PTR_ERR(ctx->crtc);
goto unprepare;
}
return 0;
+unprepare:
while (--i >= 0)
clk_unprepare(ctx->ddp_comp[i].clk);
of_node_put(node);
return ret;
+}
+static void mtk_crtc_ddp_unbind(struct device *dev, struct device *master,
void *data)
+{
struct mtk_crtc_ddp_context *ctx = dev_get_drvdata(dev);
int i;
for (i = 0; i < ctx->ddp_comp_nr; i++)
clk_unprepare(ctx->ddp_comp[i].clk);
+}
+static const struct component_ops mtk_crtc_ddp_component_ops = {
.bind = mtk_crtc_ddp_bind,
.unbind = mtk_crtc_ddp_unbind,
+};
+static int mtk_crtc_ddp_probe(struct platform_device *pdev) +{
struct device *dev = &pdev->dev;
struct mtk_crtc_ddp_context *ctx;
int irq;
int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(dev, irq, mtk_crtc_ddp_irq_handler,
IRQF_TRIGGER_NONE, dev_name(dev), ctx);
if (ret < 0) {
dev_err(dev, "Failed to request irq %d: %d\n", irq, ret);
return -ENXIO;
}
platform_set_drvdata(pdev, ctx);
ret = component_add(dev, &mtk_crtc_ddp_component_ops);
if (ret)
dev_err(dev, "Failed to add component: %d\n", ret);
return ret;
+}
+static int mtk_crtc_ddp_remove(struct platform_device *pdev) +{
component_del(&pdev->dev, &mtk_crtc_ddp_component_ops);
return 0;
+}
+static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
{ .compatible = "mediatek,mt8173-disp-ovl", },
{},
+}; +MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match);
+struct platform_driver mtk_disp_ovl_driver = {
.probe = mtk_crtc_ddp_probe,
.remove = mtk_crtc_ddp_remove,
.driver = {
.name = "mediatek-disp-ovl",
.owner = THIS_MODULE,
.of_match_table = mtk_disp_ovl_driver_dt_match,
},
+}; diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.h b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h new file mode 100644 index 0000000..b696066 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,56 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef MTK_DRM_CRTC_H +#define MTK_DRM_CRTC_H
+#include <drm/drm_crtc.h> +#include "mtk_drm_plane.h"
+#define OVL_LAYER_NR 4
+struct mtk_crtc_state {
struct drm_crtc_state base;
struct drm_pending_vblank_event *event;
unsigned int pending_update;
bool pending_needs_vblank;
bool pending_config;
unsigned int pending_width;
unsigned int pending_height;
unsigned int pending_vrefresh;
bool pending_ovl_config[OVL_LAYER_NR];
bool pending_ovl_enable[OVL_LAYER_NR];
unsigned int pending_ovl_addr[OVL_LAYER_NR];
unsigned int pending_ovl_pitch[OVL_LAYER_NR];
unsigned int pending_ovl_format[OVL_LAYER_NR];
int pending_ovl_x[OVL_LAYER_NR];
int pending_ovl_y[OVL_LAYER_NR];
unsigned int pending_ovl_size[OVL_LAYER_NR];
bool pending_ovl_dirty[OVL_LAYER_NR];
All of these OVL_LAYER_NR length arrays look like mtk_plane_state to me. I think we want to do something similar and wrap drm_plane_state.
+};
+static inline struct mtk_crtc_state *to_mtk_crtc_state(struct drm_crtc_state *s) +{
return container_of(s, struct mtk_crtc_state, base);
+}
+int mtk_drm_crtc_enable_vblank(struct drm_device *drm, int pipe); +void mtk_drm_crtc_disable_vblank(struct drm_device *drm, int pipe); +void mtk_drm_crtc_plane_config(struct drm_crtc *crtc, unsigned int idx,
bool enable, dma_addr_t addr);
+void mtk_drm_crtc_commit(struct drm_crtc *crtc);
+#endif /* MTK_DRM_CRTC_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c new file mode 100644 index 0000000..9ec2960 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,218 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <drm/drmP.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h>
+#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp.h" +#include "mtk_drm_ddp_comp.h"
+#define DISP_REG_CONFIG_DISP_OVL0_MOUT_EN 0x040 +#define DISP_REG_CONFIG_DISP_OVL1_MOUT_EN 0x044 +#define DISP_REG_CONFIG_DISP_OD_MOUT_EN 0x048 +#define DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN 0x04c +#define DISP_REG_CONFIG_DISP_UFOE_MOUT_EN 0x050 +#define DISP_REG_CONFIG_DISP_COLOR0_SEL_IN 0x084 +#define DISP_REG_CONFIG_DISP_COLOR1_SEL_IN 0x088 +#define DISP_REG_CONFIG_DPI_SEL_IN 0x0ac +#define DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN 0x0c8 +#define DISP_REG_CONFIG_MMSYS_CG_CON0 0x100
+#define DISP_REG_MUTEX_EN(n) (0x20 + 0x20 * n) +#define DISP_REG_MUTEX_RST(n) (0x28 + 0x20 * n) +#define DISP_REG_MUTEX_MOD(n) (0x2c + 0x20 * n) +#define DISP_REG_MUTEX_SOF(n) (0x30 + 0x20 * n)
The n should be in parens here.
+#define MUTEX_MOD_OVL0 11 +#define MUTEX_MOD_OVL1 12 +#define MUTEX_MOD_RDMA0 13 +#define MUTEX_MOD_RDMA1 14 +#define MUTEX_MOD_COLOR0 18 +#define MUTEX_MOD_COLOR1 19 +#define MUTEX_MOD_AAL 20 +#define MUTEX_MOD_GAMMA 21 +#define MUTEX_MOD_UFOE 22 +#define MUTEX_MOD_PWM0 23 +#define MUTEX_MOD_OD 25
+#define MUTEX_SOF_DSI0 1 +#define MUTEX_SOF_DPI0 3
+#define OVL0_MOUT_EN_COLOR0 0x1 +#define OD_MOUT_EN_RDMA0 0x1 +#define UFOE_MOUT_EN_DSI0 0x1 +#define COLOR0_SEL_IN_OVL0 0x1 +#define OVL1_MOUT_EN_COLOR1 0x1 +#define GAMMA_MOUT_EN_RDMA1 0x1 +#define RDMA1_MOUT_DPI0 0x2 +#define DPI0_SEL_IN_RDMA1 0x1 +#define COLOR1_SEL_IN_OVL1 0x1
+static const unsigned int mutex_mod[DDP_COMPONENT_ID_MAX] = {
[DDP_COMPONENT_AAL] = MUTEX_MOD_AAL,
[DDP_COMPONENT_COLOR0] = MUTEX_MOD_COLOR0,
[DDP_COMPONENT_COLOR1] = MUTEX_MOD_COLOR1,
[DDP_COMPONENT_GAMMA] = MUTEX_MOD_GAMMA,
[DDP_COMPONENT_OD] = MUTEX_MOD_OD,
[DDP_COMPONENT_OVL0] = MUTEX_MOD_OVL0,
[DDP_COMPONENT_OVL1] = MUTEX_MOD_OVL1,
[DDP_COMPONENT_PWM0] = MUTEX_MOD_PWM0,
[DDP_COMPONENT_RDMA0] = MUTEX_MOD_RDMA0,
[DDP_COMPONENT_RDMA1] = MUTEX_MOD_RDMA1,
[DDP_COMPONENT_UFOE] = MUTEX_MOD_UFOE,
+};
+void mtk_ddp_add_comp_to_path(void __iomem *config_regs, unsigned int pipe,
enum mtk_ddp_comp_id cur,
enum mtk_ddp_comp_id next)
Why pass pipe if we don't use it?
+{
unsigned int addr, value;
if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) {
addr = DISP_REG_CONFIG_DISP_OVL0_MOUT_EN;
value = OVL0_MOUT_EN_COLOR0;
} else if (cur == DDP_COMPONENT_OD && next == DDP_COMPONENT_RDMA0) {
addr = DISP_REG_CONFIG_DISP_OD_MOUT_EN;
value = OD_MOUT_EN_RDMA0;
} else if (cur == DDP_COMPONENT_UFOE && next == DDP_COMPONENT_DSI0) {
addr = DISP_REG_CONFIG_DISP_UFOE_MOUT_EN;
value = UFOE_MOUT_EN_DSI0;
} else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) {
addr = DISP_REG_CONFIG_DISP_OVL1_MOUT_EN;
value = OVL1_MOUT_EN_COLOR1;
} else if (cur == DDP_COMPONENT_GAMMA && next == DDP_COMPONENT_RDMA1) {
addr = DISP_REG_CONFIG_DISP_GAMMA_MOUT_EN;
value = GAMMA_MOUT_EN_RDMA1;
} else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) {
addr = DISP_REG_CONFIG_DISP_RDMA1_MOUT_EN;
value = RDMA1_MOUT_DPI0;
} else {
value = 0;
}
if (value) {
unsigned int reg = readl(config_regs + addr) | value;
writel(reg, config_regs + addr);
Almost all of the writel() in this patch can really be writel_relaxed(). The only ones that need writel() are when you really need a barrier.
}
if (cur == DDP_COMPONENT_OVL0 && next == DDP_COMPONENT_COLOR0) {
addr = DISP_REG_CONFIG_DISP_COLOR0_SEL_IN;
value = COLOR0_SEL_IN_OVL0;
} else if (cur == DDP_COMPONENT_RDMA1 && next == DDP_COMPONENT_DPI0) {
addr = DISP_REG_CONFIG_DPI_SEL_IN;
value = DPI0_SEL_IN_RDMA1;
} else if (cur == DDP_COMPONENT_OVL1 && next == DDP_COMPONENT_COLOR1) {
addr = DISP_REG_CONFIG_DISP_COLOR1_SEL_IN;
value = COLOR1_SEL_IN_OVL1;
} else {
value = 0;
}
if (value) {
unsigned int reg = readl(config_regs + addr) | value;
writel(reg, config_regs + addr);
}
+}
+void mtk_ddp_add_comp_to_mutex(struct device *dev, unsigned int pipe,
enum mtk_ddp_comp_id cur,
enum mtk_ddp_comp_id next)
+{
struct mtk_ddp *ddp = dev_get_drvdata(dev);
unsigned int reg;
reg = readl(ddp->mutex_regs + DISP_REG_MUTEX_MOD(pipe));
reg |= BIT(mutex_mod[cur]) | BIT(mutex_mod[next]);
writel(reg, ddp->mutex_regs + DISP_REG_MUTEX_MOD(pipe));
if (next == DDP_COMPONENT_DPI0)
reg = MUTEX_SOF_DPI0;
else
reg = MUTEX_SOF_DSI0;
writel(reg, ddp->mutex_regs + DISP_REG_MUTEX_SOF(pipe));
writel(1, ddp->mutex_regs + DISP_REG_MUTEX_EN(pipe));
+}
+void mtk_ddp_clock_on(struct device *dev) +{
struct mtk_ddp *ddp = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(ddp->mutex_disp_clk);
if (ret != 0)
DRM_ERROR("clk_prepare_enable(mutex_disp_clk) error!\n");
+}
+void mtk_ddp_clock_off(struct device *dev) +{
struct mtk_ddp *ddp = dev_get_drvdata(dev);
clk_disable_unprepare(ddp->mutex_disp_clk);
+}
+static int mtk_ddp_probe(struct platform_device *pdev) +{
struct device *dev = &pdev->dev;
struct mtk_ddp *ddp;
struct resource *regs;
ddp = devm_kzalloc(dev, sizeof(*ddp), GFP_KERNEL);
if (!ddp)
return -ENOMEM;
ddp->mutex_disp_clk = devm_clk_get(dev, NULL);
if (IS_ERR(ddp->mutex_disp_clk)) {
dev_err(dev, "Failed to get clock\n");
return PTR_ERR(ddp->mutex_disp_clk);
}
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ddp->mutex_regs = devm_ioremap_resource(dev, regs);
if (IS_ERR(ddp->mutex_regs)) {
dev_err(dev, "Failed to map mutex registers\n");
return PTR_ERR(ddp->mutex_regs);
}
platform_set_drvdata(pdev, ddp);
return 0;
+}
+static int mtk_ddp_remove(struct platform_device *pdev) +{
return 0;
+}
+static const struct of_device_id ddp_driver_dt_match[] = {
{ .compatible = "mediatek,mt8173-disp-mutex" },
{},
+}; +MODULE_DEVICE_TABLE(of, ddp_driver_dt_match);
+struct platform_driver mtk_ddp_driver = {
.probe = mtk_ddp_probe,
.remove = mtk_ddp_remove,
.driver = {
.name = "mediatek-ddp",
.owner = THIS_MODULE,
.of_match_table = ddp_driver_dt_match,
},
+};
+module_platform_driver(mtk_ddp_driver); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h new file mode 100644 index 0000000..55c2db3 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.h @@ -0,0 +1,39 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef MTK_DRM_DDP_H +#define MTK_DRM_DDP_H
+#include "mtk_drm_ddp_comp.h"
+struct regmap; +struct device;
+struct mtk_ddp {
struct device *dev;
struct drm_device *drm_dev;
struct clk *mutex_disp_clk;
void __iomem *mutex_regs;
+};
+void mtk_ddp_add_comp_to_path(void __iomem *config_regs, unsigned int pipe,
enum mtk_ddp_comp_id cur,
enum mtk_ddp_comp_id next);
+void mtk_ddp_add_comp_to_mutex(struct device *dev, unsigned int pipe,
enum mtk_ddp_comp_id cur,
enum mtk_ddp_comp_id next);
+void mtk_ddp_clock_on(struct device *dev); +void mtk_ddp_clock_off(struct device *dev);
+#endif /* MTK_DRM_DDP_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c new file mode 100644 index 0000000..2f3b32b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,424 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- Authors:
YT Shen <yt.shen@mediatek.com>
CK Hu <ck.hu@mediatek.com>
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <drm/drmP.h> +#include "mtk_drm_ddp_comp.h"
+#define DISP_REG_OVL_INTEN 0x0004 +#define DISP_REG_OVL_INTSTA 0x0008 +#define DISP_REG_OVL_EN 0x000c +#define DISP_REG_OVL_RST 0x0014 +#define DISP_REG_OVL_ROI_SIZE 0x0020 +#define DISP_REG_OVL_ROI_BGCLR 0x0028 +#define DISP_REG_OVL_SRC_CON 0x002c +#define DISP_REG_OVL_CON(n) (0x0030 + 0x20 * n) +#define DISP_REG_OVL_SRC_SIZE(n) (0x0038 + 0x20 * n) +#define DISP_REG_OVL_OFFSET(n) (0x003c + 0x20 * n) +#define DISP_REG_OVL_PITCH(n) (0x0044 + 0x20 * n) +#define DISP_REG_OVL_RDMA_CTRL(n) (0x00c0 + 0x20 * n) +#define DISP_REG_OVL_RDMA_GMC(n) (0x00c8 + 0x20 * n) +#define DISP_REG_OVL_ADDR(n) (0x0f40 + 0x20 * n)
+#define DISP_REG_RDMA_INT_ENABLE 0x0000 +#define DISP_REG_RDMA_INT_STATUS 0x0004 +#define DISP_REG_RDMA_GLOBAL_CON 0x0010 +#define DISP_REG_RDMA_SIZE_CON_0 0x0014 +#define DISP_REG_RDMA_SIZE_CON_1 0x0018 +#define DISP_REG_RDMA_FIFO_CON 0x0040 +#define RDMA_FIFO_UNDERFLOW_EN BIT(31) +#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16) +#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16)
+#define DISP_OD_EN 0x0000 +#define DISP_OD_INTEN 0x0008 +#define DISP_OD_INTSTA 0x000c +#define DISP_OD_CFG 0x0020 +#define DISP_OD_SIZE 0x0030
+#define DISP_REG_UFO_START 0x0000
+#define DISP_COLOR_CFG_MAIN 0x0400 +#define DISP_COLOR_START 0x0c00
+enum OVL_INPUT_FORMAT {
OVL_INFMT_RGB565 = 0,
OVL_INFMT_RGB888 = 1,
OVL_INFMT_RGBA8888 = 2,
OVL_INFMT_ARGB8888 = 3,
+};
+#define OVL_RDMA_MEM_GMC 0x40402020 +#define OVL_AEN BIT(8) +#define OVL_ALPHA 0xff
+#define OD_RELAY_MODE BIT(0)
+#define UFO_BYPASS BIT(2)
+#define COLOR_BYPASS_ALL BIT(7) +#define COLOR_SEQ_SEL BIT(13)
+static void mtk_color_start(void __iomem *color_base) +{
writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL,
color_base + DISP_COLOR_CFG_MAIN);
writel(0x1, color_base + DISP_COLOR_START);
+}
+static void mtk_od_config(void __iomem *od_base, unsigned int w, unsigned int h,
unsigned int vrefresh)
+{
writel(w << 16 | h, od_base + DISP_OD_SIZE);
+}
+static void mtk_od_start(void __iomem *od_base) +{
writel(OD_RELAY_MODE, od_base + DISP_OD_CFG);
writel(1, od_base + DISP_OD_EN);
+}
+static void mtk_ovl_enable_vblank(void __iomem *disp_base) +{
writel(0x2, disp_base + DISP_REG_OVL_INTEN);
+}
+static void mtk_ovl_disable_vblank(void __iomem *disp_base) +{
writel(0x0, disp_base + DISP_REG_OVL_INTEN);
+}
+static void mtk_ovl_clear_vblank(void __iomem *disp_base) +{
writel(0x0, disp_base + DISP_REG_OVL_INTSTA);
+}
+static void mtk_ovl_start(void __iomem *ovl_base) +{
writel(0x1, ovl_base + DISP_REG_OVL_EN);
+}
+static void mtk_ovl_config(void __iomem *ovl_base,
unsigned int w, unsigned int h, unsigned int vrefresh)
+{
if (w != 0 && h != 0)
writel(h << 16 | w, ovl_base + DISP_REG_OVL_ROI_SIZE);
Should you just write 0 to DISP_REG_OVL_ROI_SIZE if w or h are 0?
writel(0x0, ovl_base + DISP_REG_OVL_ROI_BGCLR);
writel(0x1, ovl_base + DISP_REG_OVL_RST);
writel(0x0, ovl_base + DISP_REG_OVL_RST);
+}
+static bool has_rb_swapped(unsigned int fmt) +{
switch (fmt) {
case DRM_FORMAT_BGR888:
case DRM_FORMAT_BGR565:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_BGRA8888:
case DRM_FORMAT_BGRX8888:
return true;
default:
return false;
}
+}
+static unsigned int ovl_fmt_convert(unsigned int fmt)
return int so the error code stays negative?
+{
switch (fmt) {
case DRM_FORMAT_RGB888:
case DRM_FORMAT_BGR888:
return OVL_INFMT_RGB888;
case DRM_FORMAT_RGB565:
case DRM_FORMAT_BGR565:
return OVL_INFMT_RGB565;
case DRM_FORMAT_RGBX8888:
case DRM_FORMAT_RGBA8888:
case DRM_FORMAT_BGRX8888:
case DRM_FORMAT_BGRA8888:
return OVL_INFMT_ARGB8888;
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
return OVL_INFMT_RGBA8888;
default:
DRM_ERROR("unsupport format[%08x]\n", fmt);
return -EINVAL;
}
+}
+static void mtk_ovl_layer_on(void __iomem *ovl_base, unsigned int idx) +{
unsigned int reg;
writel(0x1, ovl_base + DISP_REG_OVL_RDMA_CTRL(idx));
writel(OVL_RDMA_MEM_GMC, ovl_base + DISP_REG_OVL_RDMA_GMC(idx));
reg = readl(ovl_base + DISP_REG_OVL_SRC_CON);
reg = reg | (1 << idx);
writel(reg, ovl_base + DISP_REG_OVL_SRC_CON);
+}
+static void mtk_ovl_layer_off(void __iomem *ovl_base, unsigned int idx) +{
unsigned int reg;
reg = readl(ovl_base + DISP_REG_OVL_SRC_CON);
reg = reg & ~(1 << idx);
writel(reg, ovl_base + DISP_REG_OVL_SRC_CON);
writel(0x0, ovl_base + DISP_REG_OVL_RDMA_CTRL(idx));
+}
+static void mtk_ovl_layer_config(void __iomem *ovl_base, unsigned int idx,
unsigned int addr, unsigned int pitch, unsigned int fmt,
int x, int y, unsigned int size)
+{
unsigned int reg;
reg = has_rb_swapped(fmt) << 24 | ovl_fmt_convert(fmt) << 12;
ovl_fmt_convert() can return < 0 for unsupported fmt. But, perhaps we can remove that check, since it should actually already be guaranteed by the drm core that a plane is only ever asked to display an FB with a format that the plane advertises.
if (idx != 0)
reg |= OVL_AEN | OVL_ALPHA;
writel(reg, ovl_base + DISP_REG_OVL_CON(idx));
writel(pitch & 0xFFFF, ovl_base + DISP_REG_OVL_PITCH(idx));
writel(size, ovl_base + DISP_REG_OVL_SRC_SIZE(idx));
writel(y << 16 | x, ovl_base + DISP_REG_OVL_OFFSET(idx));
writel(addr, ovl_base + DISP_REG_OVL_ADDR(idx));
+}
+static void mtk_rdma_start(void __iomem *rdma_base) +{
unsigned int reg;
writel(0x4, rdma_base + DISP_REG_RDMA_INT_ENABLE);
reg = readl(rdma_base + DISP_REG_RDMA_GLOBAL_CON);
reg |= 1;
writel(reg, rdma_base + DISP_REG_RDMA_GLOBAL_CON);
+}
+static void mtk_rdma_config(void __iomem *rdma_base,
unsigned width, unsigned height, unsigned int vrefresh)
+{
unsigned int threshold;
unsigned int reg;
reg = readl(rdma_base + DISP_REG_RDMA_SIZE_CON_0);
reg = (reg & ~(0xFFF)) | (width & 0xFFF);
writel(reg, rdma_base + DISP_REG_RDMA_SIZE_CON_0);
reg = readl(rdma_base + DISP_REG_RDMA_SIZE_CON_1);
reg = (reg & ~(0xFFFFF)) | (height & 0xFFFFF);
writel(reg, rdma_base + DISP_REG_RDMA_SIZE_CON_1);
/*
* Enable FIFO underflow since DSI and DPI can't be blocked.
* Keep the FIFO pseudo size reset default of 8 KiB. Set the
* output threshold to 6 microseconds with 7/6 overhead to
* account for blanking, and with a pixel depth of 4 bytes:
*/
threshold = width * height * vrefresh * 4 * 7 / 1000000;
reg = RDMA_FIFO_UNDERFLOW_EN |
RDMA_FIFO_PSEUDO_SIZE(SZ_8K) |
RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold);
writel(reg, rdma_base + DISP_REG_RDMA_FIFO_CON);
+}
+static void mtk_ufoe_start(void __iomem *ufoe_base) +{
writel(UFO_BYPASS, ufoe_base + DISP_REG_UFO_START);
+}
+static const struct mtk_ddp_comp_funcs ddp_aal = {
.comp_id = DDP_COMPONENT_AAL,
+};
+static const struct mtk_ddp_comp_funcs ddp_color0 = {
.comp_id = DDP_COMPONENT_COLOR0,
.comp_power_on = mtk_color_start,
For consistency, can you name all of the "*_start()" functions "*_power_on", or change the .comp_power_on to .comp_start. Actually, just drop the "comp_" from all of the fields of mtk_ddp_comp_funcs, since it is redundant.
+};
+static const struct mtk_ddp_comp_funcs ddp_color1 = {
.comp_id = DDP_COMPONENT_COLOR1,
.comp_power_on = mtk_color_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_dpi0 = {
.comp_id = DDP_COMPONENT_DPI0,
+};
+static const struct mtk_ddp_comp_funcs ddp_dsi0 = {
.comp_id = DDP_COMPONENT_DSI0,
+};
+static const struct mtk_ddp_comp_funcs ddp_dsi1 = {
.comp_id = DDP_COMPONENT_DSI1,
+};
+static const struct mtk_ddp_comp_funcs ddp_gamma = {
.comp_id = DDP_COMPONENT_GAMMA,
+};
+static const struct mtk_ddp_comp_funcs ddp_od = {
.comp_id = DDP_COMPONENT_OD,
.comp_config = mtk_od_config,
.comp_power_on = mtk_od_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_ovl0 = {
.comp_id = DDP_COMPONENT_OVL0,
.comp_config = mtk_ovl_config,
.comp_power_on = mtk_ovl_start,
.comp_enable_vblank = mtk_ovl_enable_vblank,
.comp_disable_vblank = mtk_ovl_disable_vblank,
.comp_clear_vblank = mtk_ovl_clear_vblank,
.comp_layer_on = mtk_ovl_layer_on,
.comp_layer_off = mtk_ovl_layer_off,
.comp_layer_config = mtk_ovl_layer_config,
+};
+static const struct mtk_ddp_comp_funcs ddp_ovl1 = {
.comp_id = DDP_COMPONENT_OVL1,
.comp_config = mtk_ovl_config,
.comp_power_on = mtk_ovl_start,
.comp_enable_vblank = mtk_ovl_enable_vblank,
.comp_disable_vblank = mtk_ovl_disable_vblank,
.comp_clear_vblank = mtk_ovl_clear_vblank,
.comp_layer_on = mtk_ovl_layer_on,
.comp_layer_off = mtk_ovl_layer_off,
.comp_layer_config = mtk_ovl_layer_config,
+};
The callback duplication here suggests the component model can be refined a bit. I think you probably want a single one of these:
static const struct mtk_ddp_comp_funcs ddp_ovl = { .config = mtk_ovl_config, .start = mtk_ovl_start, .enable_vblank = mtk_ovl_enable_vblank, .disable_vblank = mtk_ovl_disable_vblank, .clear_vblank = mtk_ovl_clear_vblank, .layer_on = mtk_ovl_layer_on, .layer_off = mtk_ovl_layer_off, .layer_config = mtk_ovl_layer_config, };
and then to use it for both ovl's. Maybe something like this:
static const struct mtk_ddp_comp_desc ddp_ovl0 = { .id = DDP_COMPONENT_OVL0, .ops = ddp_ovl };
static const struct mtk_ddp_comp_desc ddp_ovl1 = { .id = DDP_COMPONENT_OVL1, .ops = ddp_ovl };
+static const struct mtk_ddp_comp_funcs ddp_pwm0 = {
.comp_id = DDP_COMPONENT_PWM0,
+};
+static const struct mtk_ddp_comp_funcs ddp_rdma0 = {
.comp_id = DDP_COMPONENT_RDMA0,
.comp_config = mtk_rdma_config,
.comp_power_on = mtk_rdma_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_rdma1 = {
.comp_id = DDP_COMPONENT_RDMA1,
.comp_config = mtk_rdma_config,
.comp_power_on = mtk_rdma_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_rdma2 = {
.comp_id = DDP_COMPONENT_RDMA2,
.comp_config = mtk_rdma_config,
.comp_power_on = mtk_rdma_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_ufoe = {
.comp_id = DDP_COMPONENT_UFOE,
.comp_power_on = mtk_ufoe_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_wdma0 = {
.comp_id = DDP_COMPONENT_WDMA0,
+};
+static const struct mtk_ddp_comp_funcs ddp_wdma1 = {
.comp_id = DDP_COMPONENT_WDMA1,
+};
+static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = {
[MTK_DISP_OVL] = "ovl",
[MTK_DISP_RDMA] = "rdma",
[MTK_DISP_WDMA] = "wdma",
[MTK_DISP_COLOR] = "color",
[MTK_DISP_AAL] = "aal",
[MTK_DISP_GAMMA] = "gamma",
[MTK_DISP_UFOE] = "ufoe",
[MTK_DSI] = "dsi",
[MTK_DPI] = "dpi",
[MTK_DISP_PWM] = "pwm",
[MTK_DISP_MUTEX] = "mutex",
[MTK_DISP_OD] = "od",
+};
+struct mtk_ddp_comp_match {
enum mtk_ddp_comp_type type;
int alias_id;
const struct mtk_ddp_comp_funcs *funcs;
+};
+static struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_ID_MAX] = {
[DDP_COMPONENT_AAL] = { MTK_DISP_AAL, 0, &ddp_aal },
[DDP_COMPONENT_COLOR0] = { MTK_DISP_COLOR, 0, &ddp_color0 },
[DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, &ddp_color1 },
[DDP_COMPONENT_DPI0] = { MTK_DPI, 0, &ddp_dpi0 },
[DDP_COMPONENT_DSI0] = { MTK_DSI, 0, &ddp_dsi0 },
[DDP_COMPONENT_DSI1] = { MTK_DSI, 1, &ddp_dsi1 },
[DDP_COMPONENT_GAMMA] = { MTK_DISP_GAMMA, 0, &ddp_gamma },
[DDP_COMPONENT_OD] = { MTK_DISP_OD, 0, &ddp_od },
[DDP_COMPONENT_OVL0] = { MTK_DISP_OVL, 0, &ddp_ovl0 },
[DDP_COMPONENT_OVL1] = { MTK_DISP_OVL, 1, &ddp_ovl1 },
[DDP_COMPONENT_PWM0] = { MTK_DISP_PWM, 0, &ddp_pwm0 },
[DDP_COMPONENT_RDMA0] = { MTK_DISP_RDMA, 0, &ddp_rdma0 },
[DDP_COMPONENT_RDMA1] = { MTK_DISP_RDMA, 1, &ddp_rdma1 },
[DDP_COMPONENT_RDMA2] = { MTK_DISP_RDMA, 2, &ddp_rdma2 },
[DDP_COMPONENT_UFOE] = { MTK_DISP_UFOE, 0, &ddp_ufoe },
[DDP_COMPONENT_WDMA0] = { MTK_DISP_WDMA, 0, &ddp_wdma0 },
[DDP_COMPONENT_WDMA1] = { MTK_DISP_WDMA, 1, &ddp_wdma1 },
+};
+int mtk_ddp_comp_get_id(struct device_node *node,
enum mtk_ddp_comp_type comp_type)
+{
int id = of_alias_get_id(node, mtk_ddp_comp_stem[comp_type]);
int i;
for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) {
if (comp_type == mtk_ddp_matches[i].type &&
(id < 0 || id == mtk_ddp_matches[i].alias_id))
return i;
}
return -EINVAL;
+}
+int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp,
enum mtk_ddp_comp_id comp_id)
+{
if (comp_id < 0 || comp_id >= DDP_COMPONENT_ID_MAX)
return -EINVAL;
comp->funcs = mtk_ddp_matches[comp_id].funcs;
if (comp_id == DDP_COMPONENT_DPI0 ||
comp_id == DDP_COMPONENT_DSI0 ||
comp_id == DDP_COMPONENT_PWM0) {
comp->regs = NULL;
comp->clk = NULL;
comp->irq = 0;
return 0;
}
comp->regs = of_iomap(node, 0);
comp->irq = of_irq_get(node, 0);
comp->clk = of_clk_get(node, 0);
if (IS_ERR(comp->clk))
comp->clk = NULL;
return 0;
+} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h new file mode 100644 index 0000000..ae3a6e8 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h @@ -0,0 +1,86 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef MTK_DRM_DDP_COMP_H +#define MTK_DRM_DDP_COMP_H
+#include <linux/io.h>
+struct device_node;
+enum mtk_ddp_comp_type {
MTK_DISP_OVL,
MTK_DISP_RDMA,
MTK_DISP_WDMA,
MTK_DISP_COLOR,
MTK_DISP_AAL,
MTK_DISP_GAMMA,
MTK_DISP_UFOE,
MTK_DSI,
MTK_DPI,
MTK_DISP_PWM,
MTK_DISP_MUTEX,
MTK_DISP_OD,
MTK_DDP_COMP_TYPE_MAX,
+};
+enum mtk_ddp_comp_id {
DDP_COMPONENT_AAL,
DDP_COMPONENT_COLOR0,
DDP_COMPONENT_COLOR1,
DDP_COMPONENT_DPI0,
DDP_COMPONENT_DSI0,
DDP_COMPONENT_DSI1,
DDP_COMPONENT_GAMMA,
DDP_COMPONENT_OD,
DDP_COMPONENT_OVL0,
DDP_COMPONENT_OVL1,
DDP_COMPONENT_PWM0,
DDP_COMPONENT_RDMA0,
DDP_COMPONENT_RDMA1,
DDP_COMPONENT_RDMA2,
DDP_COMPONENT_UFOE,
DDP_COMPONENT_WDMA0,
DDP_COMPONENT_WDMA1,
DDP_COMPONENT_ID_MAX,
+};
+struct mtk_ddp_comp_funcs {
enum mtk_ddp_comp_id comp_id;
void (*comp_config)(void __iomem *ovl_base,
unsigned int w, unsigned int h, unsigned int vrefresh);
void (*comp_power_on)(void __iomem *ovl_base);
void (*comp_power_off)(void __iomem *ovl_base);
void (*comp_enable_vblank)(void __iomem *ovl_base);
void (*comp_disable_vblank)(void __iomem *ovl_base);
void (*comp_clear_vblank)(void __iomem *ovl_base);
void (*comp_layer_on)(void __iomem *ovl_base, unsigned int idx);
void (*comp_layer_off)(void __iomem *ovl_base, unsigned int idx);
void (*comp_layer_config)(void __iomem *ovl_base, unsigned int idx,
unsigned int addr, unsigned int pitch, unsigned int fmt,
int x, int y, unsigned int size);
+};
+struct mtk_ddp_comp {
struct clk *clk;
void __iomem *regs;
int irq;
const struct mtk_ddp_comp_funcs *funcs;
+};
+int mtk_ddp_comp_get_id(struct device_node *node,
enum mtk_ddp_comp_type comp_type);
+int mtk_ddp_comp_init(struct device_node *node, struct mtk_ddp_comp *comp,
enum mtk_ddp_comp_id comp_id);
+#endif /* MTK_DRM_DDP_COMP_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c new file mode 100644 index 0000000..fbca99f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,572 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- Author: YT SHEN yt.shen@mediatek.com
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem.h> +#include <linux/component.h> +#include <linux/dma-iommu.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <soc/mediatek/smi.h>
+#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h"
+#define DRIVER_NAME "mediatek" +#define DRIVER_DESC "Mediatek SoC DRM" +#define DRIVER_DATE "20150513" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0
+static void mtk_atomic_schedule(struct mtk_drm_private *private,
struct drm_atomic_state *state)
+{
private->commit.state = state;
schedule_work(&private->commit.work);
+}
+static void mtk_atomic_complete(struct mtk_drm_private *private,
struct drm_atomic_state *state)
+{
struct drm_device *drm = private->drm;
drm_atomic_helper_commit_modeset_disables(drm, state);
drm_atomic_helper_commit_planes(drm, state);
drm_atomic_helper_commit_modeset_enables(drm, state);
drm_atomic_helper_wait_for_vblanks(drm, state);
Why wait for vblank here? Are you sure we waited long enough here? This will return after the very next vblank. However, what guarantees that the new state was really transferred to the hardware in time?
drm_atomic_helper_cleanup_planes(drm, state);
drm_atomic_state_free(state);
+}
+static void mtk_atomic_work(struct work_struct *work) +{
struct mtk_drm_private *private = container_of(work,
struct mtk_drm_private, commit.work);
mtk_atomic_complete(private, private->commit.state);
+}
+static int mtk_atomic_commit(struct drm_device *drm,
struct drm_atomic_state *state,
bool async)
+{
struct mtk_drm_private *private = drm->dev_private;
int ret;
ret = drm_atomic_helper_prepare_planes(drm, state);
if (ret)
return ret;
mutex_lock(&private->commit.lock);
flush_work(&private->commit.work);
drm_atomic_helper_swap_state(drm, state);
if (async)
mtk_atomic_schedule(private, state);
else
mtk_atomic_complete(private, state);
mutex_unlock(&private->commit.lock);
What does this lock do? AFAICT, it only ensures that the second half of mtk_atomic_commit() cannot execute twice at the same time. However, I expect simultaneous calls to atomic_commit() should already be prohibited by the drm core?
return 0;
+}
+static const struct drm_mode_config_funcs mtk_drm_mode_config_funcs = {
.fb_create = mtk_drm_mode_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = mtk_atomic_commit,
+};
+static const enum mtk_ddp_comp_id mtk_ddp_main[] = {
DDP_COMPONENT_OVL0,
DDP_COMPONENT_COLOR0,
DDP_COMPONENT_AAL,
DDP_COMPONENT_OD,
DDP_COMPONENT_RDMA0,
DDP_COMPONENT_UFOE,
DDP_COMPONENT_DSI0,
DDP_COMPONENT_PWM0,
+};
+static const enum mtk_ddp_comp_id mtk_ddp_ext[] = {
DDP_COMPONENT_OVL1,
DDP_COMPONENT_COLOR1,
DDP_COMPONENT_GAMMA,
DDP_COMPONENT_RDMA1,
DDP_COMPONENT_DPI0,
+};
+static int mtk_drm_kms_init(struct drm_device *drm) +{
struct mtk_drm_private *private = drm->dev_private;
struct platform_device *pdev;
int ret;
int i;
pdev = of_find_device_by_node(private->mutex_node);
if (!pdev) {
dev_err(drm->dev, "Waiting for disp-mutex device %s\n",
private->mutex_node->full_name);
of_node_put(private->mutex_node);
return -EPROBE_DEFER;
}
private->mutex_dev = &pdev->dev;
for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_node[i])
break;
pdev = of_find_device_by_node(private->larb_node[i]);
if (!pdev) {
dev_err(drm->dev, "Waiting for larb device %s\n",
private->larb_node[i]->full_name);
Nit: dev_warn() perhaps?
return -EPROBE_DEFER;
}
private->larb_dev[i] = &pdev->dev;
}
drm_mode_config_init(drm);
drm->mode_config.min_width = 64;
drm->mode_config.min_height = 64;
/*
* set max width and height as default value(4096x4096).
* this value would be used to check framebuffer size limitation
* at drm_mode_addfb().
*/
drm->mode_config.max_width = 4096;
drm->mode_config.max_height = 4096;
drm->mode_config.funcs = &mtk_drm_mode_config_funcs;
/*
* We currently support two fixed data streams,
* OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0
* and OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0.
*/
private->path_len[0] = ARRAY_SIZE(mtk_ddp_main);
private->path[0] = mtk_ddp_main;
private->path_len[1] = ARRAY_SIZE(mtk_ddp_ext);
private->path[1] = mtk_ddp_ext;
ret = component_bind_all(drm->dev, drm);
if (ret)
goto err_crtc;
/*
* We don't use the drm_irq_install() helpers provided by the DRM
* core, so we need to set this manually in order to allow the
* DRM_IOCTL_WAIT_VBLANK to operate correctly.
*/
drm->irq_enabled = true;
ret = drm_vblank_init(drm, MAX_CRTC);
if (ret < 0)
goto err_unbind;
for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_dev[i])
break;
ret = mtk_smi_larb_get(private->larb_dev[i]);
Hmm. this looks like it should be done by a crtc function, and, only for CRTCs that are actually going to be used.
if (ret) {
DRM_ERROR("Failed to get larb: %d\n", ret);
goto err_larb_get;
}
}
drm_kms_helper_poll_init(drm);
drm_mode_config_reset(drm);
return 0;
+err_larb_get:
for (i = i - 1; i >= 0; i--)
mtk_smi_larb_put(private->larb_dev[i]);
drm_kms_helper_poll_fini(drm);
Not drm_kms_helper_poll_fini().
drm_vblank_cleanup(drm);
+err_unbind:
component_unbind_all(drm->dev, drm);
+err_crtc:
drm_mode_config_cleanup(drm);
return ret;
+}
+static void mtk_drm_kms_deinit(struct drm_device *drm) +{
put the larbs?
drm_kms_helper_poll_fini(drm);
drm_vblank_cleanup(drm);
unbind_all ?
drm_mode_config_cleanup(drm);
+}
+static int mtk_drm_unload(struct drm_device *drm) +{
mtk_drm_kms_deinit(drm);
drm->dev_private = NULL;
return 0;
+}
+static const struct vm_operations_struct mtk_drm_gem_vm_ops = {
.open = drm_gem_vm_open,
.close = drm_gem_vm_close,
+};
+static const struct file_operations mtk_drm_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.mmap = mtk_drm_gem_mmap,
.poll = drm_poll,
.read = drm_read,
+#ifdef CONFIG_COMPAT
.compat_ioctl = drm_compat_ioctl,
+#endif +};
+static struct drm_driver mtk_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
| DRIVER_ATOMIC ?
.unload = mtk_drm_unload,
.set_busid = drm_platform_set_busid,
.get_vblank_counter = drm_vblank_count,
.enable_vblank = mtk_drm_crtc_enable_vblank,
.disable_vblank = mtk_drm_crtc_disable_vblank,
.gem_free_object = mtk_drm_gem_free_object,
.gem_vm_ops = &mtk_drm_gem_vm_ops,
.dumb_create = mtk_drm_gem_dumb_create,
.dumb_map_offset = mtk_drm_gem_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
.fops = &mtk_drm_fops,
.set_busid = drm_platform_set_busid,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
+};
+static int compare_of(struct device *dev, void *data) +{
return dev->of_node == data;
+}
+static int mtk_drm_bind(struct device *dev) +{
struct mtk_drm_private *private = dev_get_drvdata(dev);
struct drm_device *drm;
int ret;
drm = drm_dev_alloc(&mtk_drm_driver, dev);
if (!drm)
return -ENOMEM;
drm_dev_set_unique(drm, dev_name(dev));
ret = drm_dev_register(drm, 0);
if (ret < 0)
goto err_free;
drm->dev_private = private;
private->drm = drm;
ret = mtk_drm_kms_init(drm);
if (ret < 0)
goto err_unregister;
return 0;
+err_unregister:
drm_dev_unregister(drm);
+err_free:
drm_dev_unref(drm);
return ret;
+}
+static void mtk_drm_unbind(struct device *dev) +{
struct mtk_drm_private *private = dev_get_drvdata(dev);
drm_put_dev(private->drm);
private->drm = NULL;
+}
+static const struct component_master_ops mtk_drm_ops = {
.bind = mtk_drm_bind,
.unbind = mtk_drm_unbind,
+};
+static const struct of_device_id mtk_ddp_comp_dt_ids[] = {
{ .compatible = "mediatek,mt8173-disp-ovl", .data = (void *)MTK_DISP_OVL },
{ .compatible = "mediatek,mt8173-disp-rdma", .data = (void *)MTK_DISP_RDMA },
{ .compatible = "mediatek,mt8173-disp-wdma", .data = (void *)MTK_DISP_WDMA },
{ .compatible = "mediatek,mt8173-disp-color", .data = (void *)MTK_DISP_COLOR },
{ .compatible = "mediatek,mt8173-disp-aal", .data = (void *)MTK_DISP_AAL},
{ .compatible = "mediatek,mt8173-disp-gamma", .data = (void *)MTK_DISP_GAMMA, },
{ .compatible = "mediatek,mt8173-disp-ufoe", .data = (void *)MTK_DISP_UFOE },
{ .compatible = "mediatek,mt8173-dsi", .data = (void *)MTK_DSI },
{ .compatible = "mediatek,mt8173-dpi", .data = (void *)MTK_DPI },
{ .compatible = "mediatek,mt8173-disp-mutex", .data = (void *)MTK_DISP_MUTEX },
{ .compatible = "mediatek,mt8173-disp-od", .data = (void *)MTK_DISP_OD },
{ }
+};
+static int mtk_drm_probe(struct platform_device *pdev) +{
struct device *dev = &pdev->dev;
struct mtk_drm_private *private;
struct resource *mem;
struct device_node *node;
struct component_match *match = NULL;
int ret;
int num_larbs = 0;
private = devm_kzalloc(dev, sizeof(*private), GFP_KERNEL);
if (!private)
return -ENOMEM;
mutex_init(&private->commit.lock);
INIT_WORK(&private->commit.work, mtk_atomic_work);
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
private->config_regs = devm_ioremap_resource(dev, mem);
if (IS_ERR(private->config_regs)) {
ret = PTR_ERR(private->config_regs);
dev_err(dev, "Failed to ioremap mmsys-config resource: %d\n",
ret);
return ret;
}
/* Iterate over sibling DISP function blocks */
for_each_child_of_node(dev->of_node->parent, node) {
const struct of_device_id *of_id;
enum mtk_ddp_comp_type comp_type;
int comp_id;
of_id = of_match_node(mtk_ddp_comp_dt_ids, node);
if (!of_id)
continue;
comp_type = (enum mtk_ddp_comp_type)of_id->data;
if (comp_type == MTK_DISP_MUTEX) {
private->mutex_node = of_node_get(node);
continue;
}
I'd move the MUTEX check after "is_available()", to catch the odd case where the MUTEX node is disabled (it will still be NULL, and fail below).
if (!of_device_is_available(node)) {
dev_dbg(dev, "Skipping disabled component %s\n",
node->full_name);
continue;
}
comp_id = mtk_ddp_comp_get_id(node, comp_type);
if (comp_id < 0) {
dev_info(dev, "Skipping unknown component %s\n",
node->full_name);
dev_warn()
continue;
}
/*
* Currently only the OVL, DSI, and DPI blocks have separate
* component platform drivers.
*/
if (comp_type == MTK_DISP_OVL ||
comp_type == MTK_DSI ||
comp_type == MTK_DPI) {
dev_info(dev, "Adding component match for %s\n",
node->full_name);
component_match_add(dev, &match, compare_of, node);
}
private->comp_node[comp_id] = of_node_get(node);
if (comp_type == MTK_DISP_OVL) {
struct device_node *larb_node;
larb_node = of_parse_phandle(node, "mediatek,larb", 0);
if (larb_node && num_larbs < MAX_CRTC)
private->larb_node[num_larbs++] = larb_node;
It feels like the larb_nodes should be handled directly by the ovl driver.
}
}
if (!private->mutex_node) {
dev_err(dev, "Failed to find disp-mutex node\n");
Cleanup: of_node_put() any nodes already in comp_node[] Anything to clean after component_match_add()? of_node_put() larb_nodes?
return -ENODEV;
}
pm_runtime_enable(dev);
platform_set_drvdata(pdev, private);
return component_master_add_with_match(dev, &mtk_drm_ops, match);
+}
+static int mtk_drm_remove(struct platform_device *pdev) +{
component_master_del(&pdev->dev, &mtk_drm_ops);
pm_runtime_disable(&pdev->dev);
Cleanup: of_node_put() any nodes already in comp_node[] Anything to clean after component_match_add()? of_node_put() larb_nodes?
return 0;
+}
+#ifdef CONFIG_PM_SLEEP +static int mtk_drm_sys_suspend(struct device *dev) +{
struct mtk_drm_private *private = dev_get_drvdata(dev);
struct drm_device *drm = private->drm;
struct drm_connector *conn;
int i;
drm_kms_helper_poll_disable(drm);
drm_modeset_lock_all(drm);
list_for_each_entry(conn, &drm->mode_config.connector_list, head) {
int old_dpms = conn->dpms;
if (conn->funcs->dpms)
conn->funcs->dpms(conn, DRM_MODE_DPMS_OFF);
/* Set the old mode back to the connector for resume */
conn->dpms = old_dpms;
}
drm_modeset_unlock_all(drm);
for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_dev[i])
break;
mtk_smi_larb_put(private->larb_dev[i]);
}
DRM_INFO("mtk_drm_sys_suspend\n");
return 0;
+}
+static int mtk_drm_sys_resume(struct device *dev) +{
struct mtk_drm_private *private = dev_get_drvdata(dev);
struct drm_device *drm = private->drm;
struct drm_connector *conn;
int ret;
int i;
for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_dev[i])
break;
ret = mtk_smi_larb_get(private->larb_dev[i]);
if (ret) {
DRM_ERROR("mtk_smi_larb_get fail %d\n", ret);
return ret;
}
}
drm_modeset_lock_all(drm);
list_for_each_entry(conn, &drm->mode_config.connector_list, head) {
int desired_mode = conn->dpms;
/*
* at suspend time, we save dpms to connector->dpms,
* restore the old_dpms, and at current time, the connector
* dpms status must be DRM_MODE_DPMS_OFF.
*/
conn->dpms = DRM_MODE_DPMS_OFF;
/*
* If the connector has been disconnected during suspend,
* disconnect it from the encoder and leave it off. We'll notify
* userspace at the end.
*/
if (conn->funcs->dpms)
conn->funcs->dpms(conn, desired_mode);
}
drm_modeset_unlock_all(drm);
drm_kms_helper_poll_enable(drm);
DRM_INFO("mtk_drm_sys_resume\n");
return 0;
+}
+SIMPLE_DEV_PM_OPS(mtk_drm_pm_ops, mtk_drm_sys_suspend, mtk_drm_sys_resume); +#endif
+static const struct of_device_id mtk_drm_of_ids[] = {
{ .compatible = "mediatek,mt8173-mmsys", },
{ }
+};
+static struct platform_driver mtk_drm_platform_driver = {
.probe = mtk_drm_probe,
.remove = mtk_drm_remove,
.driver = {
.name = "mediatek-drm",
.of_match_table = mtk_drm_of_ids,
+#ifdef CONFIG_PM_SLEEP
.pm = &mtk_drm_pm_ops,
+#endif
},
+};
+static int __init mtk_drm_init(void) +{
int ret;
ret = platform_driver_register(&mtk_drm_platform_driver);
if (ret < 0) {
pr_err("Failed to register DRM platform driver: %d\n", ret);
goto err;
}
ret = platform_driver_register(&mtk_disp_ovl_driver);
if (ret < 0) {
pr_err("Failed to register OVL platform driver: %d\n", ret);
goto drm_err;
}
return 0;
+drm_err:
platform_driver_unregister(&mtk_drm_platform_driver);
+err:
return ret;
+}
+static void __exit mtk_drm_exit(void) +{
platform_driver_unregister(&mtk_disp_ovl_driver);
platform_driver_unregister(&mtk_drm_platform_driver);
+}
+module_init(mtk_drm_init); +module_exit(mtk_drm_exit);
+MODULE_AUTHOR("YT SHEN yt.shen@mediatek.com"); +MODULE_DESCRIPTION("Mediatek SoC DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h new file mode 100644 index 0000000..5e5128e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,61 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef MTK_DRM_DRV_H +#define MTK_DRM_DRV_H
+#include <linux/io.h> +#include "mtk_drm_ddp_comp.h"
+#define MAX_CRTC 2 +#define MAX_CONNECTOR 2
+struct device; +struct device_node; +struct drm_crtc; +struct drm_device; +struct drm_fb_helper; +struct drm_property; +struct regmap;
+struct mtk_drm_private {
struct drm_fb_helper *fb_helper;
Not used?
struct drm_device *drm;
/*
* created crtc object would be contained at this array and
* this array is used to be aware of which crtc did it request vblank.
I don't understand this comment.
*/
struct drm_crtc *crtc[MAX_CRTC];
struct drm_property *plane_zpos_property;
Not used.
unsigned int pipe;
what pipe is this?
struct device_node *larb_node[MAX_CRTC];
struct device *larb_dev[MAX_CRTC];
struct device_node *mutex_node;
struct device *mutex_dev;
void __iomem *config_regs;
unsigned int path_len[MAX_CRTC];
const enum mtk_ddp_comp_id *path[MAX_CRTC];
Having 5 arrays of length MAX_CRTC suggests you really want one to combine these fields into a struct, and have an MAX_CRTC array of the struct. In fact, this struct sounds like it should be "mtk_crtc", which wraps a drm_crtc.
struct device_node *comp_node[DDP_COMPONENT_ID_MAX];
struct {
struct drm_atomic_state *state;
struct work_struct work;
struct mutex lock;
} commit;
+};
+extern struct platform_driver mtk_disp_ovl_driver;
+#endif /* MTK_DRM_DRV_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c new file mode 100644 index 0000000..dfa931b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c @@ -0,0 +1,151 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem.h>
+#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h"
+/*
- mtk specific framebuffer structure.
- @fb: drm framebuffer object.
- @gem_obj: array of gem objects.
- */
+struct mtk_drm_fb {
struct drm_framebuffer base;
struct drm_gem_object *gem_obj[MAX_FB_OBJ];
Nit: The display controller driver does not handle multi-buffer formats. So, maybe just add the array later when it does. Arguably, for now it is just adding complexity with no benefit.
+};
+#define to_mtk_fb(x) container_of(x, struct mtk_drm_fb, base)
+struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb,
unsigned int plane)
+{
struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
if (plane >= MAX_FB_OBJ)
return NULL;
return mtk_fb->gem_obj[plane];
+}
+static int mtk_drm_fb_create_handle(struct drm_framebuffer *fb,
struct drm_file *file_priv,
unsigned int *handle)
+{
struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
return drm_gem_handle_create(file_priv, mtk_fb->gem_obj[0], handle);
+}
+static void mtk_drm_fb_destroy(struct drm_framebuffer *fb) +{
unsigned int i;
struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
struct drm_gem_object *gem;
int nr = drm_format_num_planes(fb->pixel_format);
drm_framebuffer_cleanup(fb);
for (i = 0; i < nr; i++) {
gem = mtk_fb->gem_obj[i];
drm_gem_object_unreference_unlocked(gem);
}
kfree(mtk_fb);
+}
+static const struct drm_framebuffer_funcs mtk_drm_fb_funcs = {
.create_handle = mtk_drm_fb_create_handle,
.destroy = mtk_drm_fb_destroy,
+};
+static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev,
struct drm_mode_fb_cmd2 *mode,
struct drm_gem_object **obj)
+{
struct mtk_drm_fb *mtk_fb;
unsigned int i;
int ret;
mtk_fb = kzalloc(sizeof(*mtk_fb), GFP_KERNEL);
if (!mtk_fb)
return ERR_PTR(-ENOMEM);
drm_helper_mode_fill_fb_struct(&mtk_fb->base, mode);
for (i = 0; i < drm_format_num_planes(mode->pixel_format); i++)
mtk_fb->gem_obj[i] = obj[i];
ret = drm_framebuffer_init(dev, &mtk_fb->base, &mtk_drm_fb_funcs);
if (ret) {
DRM_ERROR("failed to initialize framebuffer\n");
kfree(mtk_fb);
return ERR_PTR(ret);
}
return mtk_fb;
+}
+struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd)
+{
unsigned int hsub, vsub, i;
struct mtk_drm_fb *mtk_fb;
struct drm_gem_object *gem[MAX_FB_OBJ];
int err;
hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format);
vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format);
nit: blank line here would help readability.
for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) {
unsigned int width = cmd->width / (i ? hsub : 1);
unsigned int height = cmd->height / (i ? vsub : 1);
unsigned int size, bpp;
gem[i] = drm_gem_object_lookup(dev, file, cmd->handles[i]);
if (!gem[i]) {
err = -ENOENT;
goto unreference;
}
bpp = drm_format_plane_cpp(cmd->pixel_format, i);
size = (height - 1) * cmd->pitches[i] + width * bpp;
size += cmd->offsets[i];
if (gem[i]->size < size) {
drm_gem_object_unreference_unlocked(gem[i]);
err = -EINVAL;
goto unreference;
}
}
mtk_fb = mtk_drm_framebuffer_init(dev, cmd, gem);
if (IS_ERR(mtk_fb)) {
err = PTR_ERR(mtk_fb);
goto unreference;
}
return &mtk_fb->base;
+unreference:
while (i--)
drm_gem_object_unreference_unlocked(gem[i]);
return ERR_PTR(err);
+} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h new file mode 100644 index 0000000..9ce7307 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h @@ -0,0 +1,29 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef MTK_DRM_FB_H +#define MTK_DRM_FB_H
+#define MAX_FB_OBJ 3
+struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb,
unsigned int plane);
+struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd);
+void mtk_drm_mode_output_poll_changed(struct drm_device *dev); +int mtk_fbdev_create(struct drm_device *dev); +void mtk_fbdev_destroy(struct drm_device *dev);
+#endif /* MTK_DRM_FB_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.c b/drivers/gpu/drm/mediatek/mtk_drm_gem.c new file mode 100644 index 0000000..34fb94c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c @@ -0,0 +1,189 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <drm/drmP.h> +#include <drm/drm_gem.h>
+#include "mtk_drm_gem.h"
+struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev,
unsigned long size)
static? But actually, just folding this directly into mtk_drm_gem_create() would make the code flow clearer, since this function has no corresponding _fini().
+{
struct mtk_drm_gem_obj *mtk_gem_obj;
int ret;
size = round_up(size, PAGE_SIZE);
mtk_gem_obj = kzalloc(sizeof(*mtk_gem_obj), GFP_KERNEL);
if (!mtk_gem_obj)
return ERR_PTR(-ENOMEM);
ret = drm_gem_object_init(dev, &mtk_gem_obj->base, size);
if (ret < 0) {
DRM_ERROR("failed to initialize gem object\n");
kfree(mtk_gem_obj);
return ERR_PTR(ret);
}
return mtk_gem_obj;
+}
+struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev,
unsigned long size, bool alloc_kmap)
static?
+{
struct mtk_drm_gem_obj *mtk_gem;
struct drm_gem_object *obj;
int ret;
mtk_gem = mtk_drm_gem_init(dev, size);
if (IS_ERR(mtk_gem))
return ERR_CAST(mtk_gem);
obj = &mtk_gem->base;
init_dma_attrs(&mtk_gem->dma_attrs);
dma_set_attr(DMA_ATTR_WRITE_COMBINE, &mtk_gem->dma_attrs);
if (!alloc_kmap)
dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &mtk_gem->dma_attrs);
mtk_gem->cookie = dma_alloc_attrs(dev->dev, obj->size,
(dma_addr_t *)&mtk_gem->dma_addr, GFP_KERNEL,
&mtk_gem->dma_attrs);
if (!mtk_gem->cookie) {
DRM_ERROR("failed to allocate %zx byte dma buffer", obj->size);
ret = -ENOMEM;
goto err_gem_free;
}
if (alloc_kmap)
mtk_gem->kvaddr = mtk_gem->cookie;
DRM_INFO("cookie = %p dma_addr = %llx\n",
mtk_gem->cookie, mtk_gem->dma_addr);
return mtk_gem;
+err_gem_free:
drm_gem_object_release(obj);
kfree(mtk_gem);
return ERR_PTR(ret);
+}
+void mtk_drm_gem_free_object(struct drm_gem_object *obj) +{
struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
drm_gem_free_mmap_offset(obj);
/* release file pointer to gem object. */
drm_gem_object_release(obj);
drm_gem_object_release calls drm_gem_free_mmap_offset().
dma_free_attrs(obj->dev->dev, obj->size, mtk_gem->cookie,
mtk_gem->dma_addr, &mtk_gem->dma_attrs);
I think you want to do this before calling drm_gem_object_release.
kfree(mtk_gem);
+}
+int mtk_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
struct drm_mode_create_dumb *args)
+{
struct mtk_drm_gem_obj *mtk_gem;
int ret;
args->pitch = args->width * DIV_ROUND_UP(args->bpp, 8);
args->size = args->pitch * args->height;
mtk_gem = mtk_drm_gem_create(dev, args->size, false);
if (IS_ERR(mtk_gem))
return PTR_ERR(mtk_gem);
/*
* allocate a id of idr table where the obj is registered
* and handle has the id what user can see.
*/
ret = drm_gem_handle_create(file_priv, &mtk_gem->base, &args->handle);
if (ret)
goto err_handle_create;
/* drop reference from allocate - handle holds it now. */
drm_gem_object_unreference_unlocked(&mtk_gem->base);
return 0;
+err_handle_create:
mtk_drm_gem_free_object(&mtk_gem->base);
return ret;
+}
+int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv,
struct drm_device *dev, uint32_t handle,
uint64_t *offset)
+{
struct drm_gem_object *obj;
int ret;
obj = drm_gem_object_lookup(dev, file_priv, handle);
if (!obj) {
DRM_ERROR("failed to lookup gem object.\n");
return -EINVAL;
}
ret = drm_gem_create_mmap_offset(obj);
if (ret)
goto out;
*offset = drm_vma_node_offset_addr(&obj->vma_node);
DRM_DEBUG_KMS("offset = 0x%llx\n", *offset);
+out:
drm_gem_object_unreference_unlocked(obj);
return ret;
+}
+static int mtk_drm_gem_object_mmap(struct drm_gem_object *obj,
struct vm_area_struct *vma)
+{
int ret;
struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
struct drm_device *drm = obj->dev;
/*
* dma_alloc_attrs() allocated a struct page table for rk_obj, so clear
* VM_PFNMAP flag that was set by drm_gem_mmap_obj()/drm_gem_mmap().
*/
vma->vm_flags &= ~VM_PFNMAP;
vma->vm_pgoff = 0;
ret = dma_mmap_attrs(drm->dev, vma, mtk_gem->cookie, mtk_gem->dma_addr,
obj->size, &mtk_gem->dma_attrs);
if (ret)
drm_gem_vm_close(vma);
return ret;
+}
+int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{
struct drm_gem_object *obj;
int ret;
ret = drm_gem_mmap(filp, vma);
if (ret)
return ret;
obj = vma->vm_private_data;
return mtk_drm_gem_object_mmap(obj, vma);
+} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.h b/drivers/gpu/drm/mediatek/mtk_drm_gem.h new file mode 100644 index 0000000..fb7953e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h @@ -0,0 +1,56 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef _MTK_DRM_GEM_H_ +#define _MTK_DRM_GEM_H_
+#include <drm/drm_gem.h>
+/*
- mtk drm buffer structure.
- @base: a gem object.
- a new handle to this gem object would be created
by drm_gem_handle_create().
- @cookie: the return value of dma_alloc_attrs(), keep it for dma_free_attrs()
- @kvaddr: kernel virtual address of gem buffer.
- @dma_addr: dma address of gem buffer.
- @dma_attrs: dma attributes of gem buffer.
- P.S. this object would be transferred to user as kms_bo.handle so
user can access the buffer through kms_bo.handle.
- */
+struct mtk_drm_gem_obj {
struct drm_gem_object base;
void __iomem *cookie;
void __iomem *kvaddr;
Is kvaddr really __iomem?
dma_addr_t dma_addr;
struct dma_attrs dma_attrs;
+};
+#define to_mtk_gem_obj(x) container_of(x, struct mtk_drm_gem_obj, base)
+struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev,
unsigned long size);
+void mtk_drm_gem_free_object(struct drm_gem_object *gem); +struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev,
unsigned long size, bool alloc_kmap);
+int mtk_drm_gem_dumb_create(struct drm_file *file_priv,
struct drm_device *dev, struct drm_mode_create_dumb *args);
+int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv,
struct drm_device *dev, uint32_t handle, uint64_t *offset);
+int mtk_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int mtk_drm_gem_mmap_buf(struct drm_gem_object *obj,
struct vm_area_struct *vma);
+#endif diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c new file mode 100644 index 0000000..3a8843c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,167 @@ +/*
- Copyright (c) 2015 MediaTek Inc.
- Author: CK Hu ck.hu@mediatek.com
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/dma-buf.h> +#include <linux/reservation.h>
+#include "mtk_drm_crtc.h" +#include "mtk_drm_ddp_comp.h" +#include "mtk_drm_drv.h" +#include "mtk_drm_fb.h" +#include "mtk_drm_gem.h" +#include "mtk_drm_plane.h"
+static const uint32_t formats[] = {
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_RGB565,
+};
+static const struct drm_plane_funcs mtk_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+static int mtk_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
+{
struct drm_framebuffer *fb = state->fb;
struct drm_crtc_state *crtc_state;
bool visible;
int ret;
struct drm_rect dest = {
.x1 = state->crtc_x,
.y1 = state->crtc_y,
.x2 = state->crtc_x + state->crtc_w,
.y2 = state->crtc_y + state->crtc_h,
};
struct drm_rect src = {
/* 16.16 fixed point */
.x1 = state->src_x,
.y1 = state->src_y,
.x2 = state->src_x + state->src_w,
.y2 = state->src_y + state->src_h,
};
struct drm_rect clip = { 0, };
if (!fb)
return 0;
if (!mtk_fb_get_gem_obj(fb, 0)) {
DRM_DEBUG_KMS("buffer is null\n");
return -EFAULT;
}
if (!state->crtc)
return 0;
crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
clip.x2 = crtc_state->mode.hdisplay;
clip.y2 = crtc_state->mode.vdisplay;
ret = drm_plane_helper_check_update(plane, state->crtc, fb,
&src, &dest, &clip,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING,
true, true, &visible);
if (ret)
return ret;
return 0;
+}
+static void mtk_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
+{
struct drm_plane_state *state = plane->state;
struct drm_gem_object *gem_obj;
struct drm_crtc *crtc = state->crtc;
struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
struct drm_rect dest = {
.x1 = state->crtc_x,
.y1 = state->crtc_y,
.x2 = state->crtc_x + state->crtc_w,
.y2 = state->crtc_y + state->crtc_h,
};
struct drm_rect clip = { 0, };
if (!crtc)
return;
clip.x2 = state->crtc->state->mode.hdisplay;
clip.y2 = state->crtc->state->mode.vdisplay;
drm_rect_intersect(&dest, &clip);
mtk_plane->disp_size = (dest.y2 - dest.y1) << 16 | (dest.x2 - dest.x1);
plane->fb = state->fb;
This doesn't look right. The drm core should be doing this for us. Why do you need this here?
I really think we want to be using a "struct mtk_plane_state" which wraps a drm_plane_state and accumulates our ovl specific changes. During init, when creating crtcs & planes, we should tell each plane its id, and which ovl it should use. The mtk_plane code can then provide a function for the vblank irq to apply mtk_plane's pending state.
gem_obj = mtk_fb_get_gem_obj(state->fb, 0);
mtk_plane->flip_obj = to_mtk_gem_obj(gem_obj);
We do not need to store "flip_obj". We only use it twice, +3 and +5 lines down from here. Also, this doesn't look safe. What if state->fb is NULL? It works because to_mtk_gem_obj(NULL) == NULL, but it relies on an internal implementation detail - gem_obj.base is at offset 0.
mtk_plane->crtc = crtc;
This isn't used, either.
if (mtk_plane->flip_obj)
mtk_drm_crtc_plane_config(crtc, mtk_plane->idx, true,
mtk_plane->flip_obj->dma_addr);
+}
+static void mtk_plane_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
+{
struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
struct drm_crtc *crtc = old_state->crtc;
if (!crtc)
return;
mtk_drm_crtc_plane_config(crtc, mtk_plane->idx, false, 0);
mtk_drm_crtc_commit(crtc);
Why this here? Won't this happen during mtk_drm_crtc_atomic_flush?
+}
+static const struct drm_plane_helper_funcs mtk_plane_helper_funcs = {
.atomic_check = mtk_plane_atomic_check,
.atomic_update = mtk_plane_atomic_update,
.atomic_disable = mtk_plane_atomic_disable,
+};
+int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane,
unsigned long possible_crtcs, enum drm_plane_type type,
unsigned int zpos, unsigned int max_plane)
max_plane is not used.
Ok... enough for today :-).
Thanks! -Dan
Hi Daniel,
Am Donnerstag, den 05.11.2015, 02:49 +0800 schrieb Daniel Kurtz:
Hi Philipp,
A bunch of review comments inline.
A bunch indeed. Thank you for the feedback.
On Wed, Nov 4, 2015 at 7:44 PM, Philipp Zabel p.zabel@pengutronix.de wrote:
[...]
+struct mtk_drm_crtc {
struct drm_crtc base;
unsigned int pipe;
There is one pipe too many :-) I think this one is not used."
bool enabled;
struct mtk_crtc_ddp_context *ctx;
I think you should be able to just embed the "mtk_crtc_ddp_context" right into mtk_drm_crtc. Or maybe just get rid of mtk_crtc_ddp_context completely and just use mtk_drm_crtc eveywhere.
I'll combine them and get rid of the superfluous pipe.
[...]
+static void mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *crtc) +{
[...]
/* disp_mtcmos */
ret = pm_runtime_get_sync(drm->dev);
if (ret < 0)
DRM_ERROR("Failed to enable power domain: %d\n", ret);
mtk_ddp_clock_on(ctx->mutex_dev);
mtk_crtc_ddp_power_on(ctx);
get_sync(), clock_on() and ddp_power_on() really can fail; we are just ignoring errors here. Since they can fail, shouldn't they be moved out of the atomic "->enable()" path into the ->check() path that is allowed to fail?
You suggest I move them into atomic_check?
To me it sounds like this should not be called from check, but from the drm_mode_config_funcs atomic_commit callback, right after calling drm_atomic_helper_prepare_planes. But there is no prepare equivalent to drm_atomic_helper_commit_modeset_enables.
DRM_INFO("mediatek_ddp_ddp_path_setup\n");
for (i = 0; i < (ctx->ddp_comp_nr - 1); i++) {
nit: the inner () are not necessary.
Will remove those.
mtk_ddp_add_comp_to_path(ctx->config_regs, ctx->pipe,
ctx->ddp_comp[i].funcs->comp_id,
ctx->ddp_comp[i+1].funcs->comp_id);
mtk_ddp_add_comp_to_mutex(ctx->mutex_dev, ctx->pipe,
ctx->ddp_comp[i].funcs->comp_id,
ctx->ddp_comp[i+1].funcs->comp_id);
}
Do you really have to do this here in the enable path? This looks like something that should be done in bind()?
Perhaps all we really need here is to walk the path and write to DISP_REG_MUTEX_EN at the end of mtk_ddp_add_comp_to_mutex(). By the way, where are those bits cleared in the disable path?
Disabling or changing the path is not implemented, the currently static setup could well be moved into bind().
[...]
+static void mtk_crtc_ddp_hw_fini(struct mtk_drm_crtc *crtc) +{
[...]
pm_runtime_put_sync(drm->dev);
Why sync?
I can think of no reason, will use the async version here.
[...]
+static void mtk_drm_crtc_disable(struct drm_crtc *crtc) +{
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
DRM_INFO("mtk_drm_crtc_disable %d\n", crtc->base.id);
if (WARN_ON(!mtk_crtc->enabled))
return;
mtk_crtc_ddp_hw_fini(mtk_crtc);
mtk_crtc->do_flush = false;
if (state->event)
mtk_drm_crtc_finish_page_flip(mtk_crtc);
It is a bit awkward to send the page flip event before the actual page flip has completed (in this case, for a page flip that you are canceling by disabling the crtc). Would it be better to wait for vblank here in crtc_disable instead?
Yes, I will wait for vblank here instead.
[...]
+static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
+{
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
if (state->base.event) {
state->base.event->pipe = drm_crtc_index(crtc);
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
state->event = state->base.event;
state->base.event = NULL;
Hmm. Why are we "stealing" event from drm_crtc_state and handing it over to the wrapper mtk_crtc_state? Won't we still be able to retrieve state->base.event later when we need it in the mtk_drm_crtc_finish_page_flip?
Hmm, good point. I'll try that.
[...]
+static void mtk_crtc_ddp_irq(struct mtk_crtc_ddp_context *ctx)
==> So, this is the part of the driver that makes me the most nervous. Why are we writing the actual hardware registers in the *vblank interrupt handler*?! Every other display controller that I've ever seen has shadow registers that are used to stage a page flip at the next vblank. Are you sure this hardware doesn't support this?
In any case, how do we get that first interrupt in which to apply the register?
I'll try to rework this using the DISP_MUTEX to trigger register updates on SOF.
+{
struct mtk_drm_crtc *mtk_crtc = ctx->crtc;
struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
ddp_comp[0] here looks a bit like black magic. "The 0th component is always the ovl". Perhaps make an explicitly named "ovl" field for mtk_crtc_ddp_context.
Ok.
struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state);
unsigned int i;
if (state->pending_config) {
state->pending_config = false;
for (i = 0; i < OVL_LAYER_NR; i++)
ovl->funcs->comp_layer_off(ovl->regs, i);
Why do you need to turn off all layers before setting mode?
I am not sure if this is necessary. It seems to work without.
ovl->funcs->comp_config(ovl->regs, state->pending_width,
state->pending_height,
state->pending_vrefresh);
}
for (i = 0; i < OVL_LAYER_NR; i++) {
if (state->pending_ovl_config[i]) {
if (!state->pending_ovl_enable[i])
ovl->funcs->comp_layer_off(ovl->regs, i);
ovl->funcs->comp_layer_config(ovl->regs, i,
state->pending_ovl_addr[i],
state->pending_ovl_pitch[i],
state->pending_ovl_format[i],
state->pending_ovl_x[i],
state->pending_ovl_y[i],
state->pending_ovl_size[i]);
if (state->pending_ovl_enable[i])
ovl->funcs->comp_layer_on(ovl->regs, i);
}
I'd move this all all with an mtk_plane function, and let each mtk_plane store its own pending state.
Ok.
[...]
+static int mtk_crtc_ddp_bind(struct device *dev, struct device *master,
void *data)
+{
[...]
for (zpos = 0; zpos < OVL_LAYER_NR; zpos++) {
type = (zpos == 0) ? DRM_PLANE_TYPE_PRIMARY :
(zpos == 1) ? DRM_PLANE_TYPE_CURSOR :
DRM_PLANE_TYPE_OVERLAY;
ret = mtk_plane_init(drm_dev, &ctx->planes[zpos],
1 << ctx->pipe, type, zpos, OVL_LAYER_NR);
if (ret)
goto unprepare;
}
I think you should just let mtk_drm_crtc_create() create & init its own planes. Currently, its overlay planes are unassigned. Furthermore, the crtc context doesn't really need an array of planes at all. It only really uses the array to know which planes to update during the vblank irq. But, that information is actually already present in the atomic state to be applied itself.
[...]
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.h b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h new file mode 100644 index 0000000..b696066 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,56 @@
[...]
+struct mtk_crtc_state {
struct drm_crtc_state base;
struct drm_pending_vblank_event *event;
unsigned int pending_update;
bool pending_needs_vblank;
bool pending_config;
unsigned int pending_width;
unsigned int pending_height;
unsigned int pending_vrefresh;
bool pending_ovl_config[OVL_LAYER_NR];
bool pending_ovl_enable[OVL_LAYER_NR];
unsigned int pending_ovl_addr[OVL_LAYER_NR];
unsigned int pending_ovl_pitch[OVL_LAYER_NR];
unsigned int pending_ovl_format[OVL_LAYER_NR];
int pending_ovl_x[OVL_LAYER_NR];
int pending_ovl_y[OVL_LAYER_NR];
unsigned int pending_ovl_size[OVL_LAYER_NR];
bool pending_ovl_dirty[OVL_LAYER_NR];
All of these OVL_LAYER_NR length arrays look like mtk_plane_state to me. I think we want to do something similar and wrap drm_plane_state.
[...]
+++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,218 @@
[...]
+#define DISP_REG_MUTEX_EN(n) (0x20 + 0x20 * n) +#define DISP_REG_MUTEX_RST(n) (0x28 + 0x20 * n) +#define DISP_REG_MUTEX_MOD(n) (0x2c + 0x20 * n) +#define DISP_REG_MUTEX_SOF(n) (0x30 + 0x20 * n)
The n should be in parens here.
Ok.
[...]
+void mtk_ddp_add_comp_to_path(void __iomem *config_regs, unsigned int pipe,
enum mtk_ddp_comp_id cur,
enum mtk_ddp_comp_id next)
Why pass pipe if we don't use it?
Leftover from before the split of mtk_ddp_add_to_path/mutex.
[...]
if (value) {
unsigned int reg = readl(config_regs + addr) | value;
writel(reg, config_regs + addr);
Almost all of the writel() in this patch can really be writel_relaxed(). The only ones that need writel() are when you really need a barrier.
Ok.
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c new file mode 100644 index 0000000..2f3b32b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,424 @@
[...]
+static void mtk_ovl_config(void __iomem *ovl_base,
unsigned int w, unsigned int h, unsigned int vrefresh)
+{
if (w != 0 && h != 0)
writel(h << 16 | w, ovl_base + DISP_REG_OVL_ROI_SIZE);
Should you just write 0 to DISP_REG_OVL_ROI_SIZE if w or h are 0?
According to the docs, width and height must be at least 1. But of course I should check this earlier.
[...]
+static unsigned int ovl_fmt_convert(unsigned int fmt)
return int so the error code stays negative?
I'll just return RGB888 on error.
[...]
+static void mtk_ovl_layer_config(void __iomem *ovl_base, unsigned int idx,
unsigned int addr, unsigned int pitch, unsigned int fmt,
int x, int y, unsigned int size)
+{
unsigned int reg;
reg = has_rb_swapped(fmt) << 24 | ovl_fmt_convert(fmt) << 12;
ovl_fmt_convert() can return < 0 for unsupported fmt. But, perhaps we can remove that check, since it should actually already be guaranteed by the drm core that a plane is only ever asked to display an FB with a format that the plane advertises.
Exactly. If something unsupported gets through here, it's a bug.
[...]
+static const struct mtk_ddp_comp_funcs ddp_color0 = {
.comp_id = DDP_COMPONENT_COLOR0,
.comp_power_on = mtk_color_start,
For consistency, can you name all of the "*_start()" functions "*_power_on", or change the .comp_power_on to .comp_start. Actually, just drop the "comp_" from all of the fields of mtk_ddp_comp_funcs, since it is redundant.
I'd change them all to .enable/.disable.
+static const struct mtk_ddp_comp_funcs ddp_ovl1 = {
.comp_id = DDP_COMPONENT_OVL1,
.comp_config = mtk_ovl_config,
.comp_power_on = mtk_ovl_start,
.comp_enable_vblank = mtk_ovl_enable_vblank,
.comp_disable_vblank = mtk_ovl_disable_vblank,
.comp_clear_vblank = mtk_ovl_clear_vblank,
.comp_layer_on = mtk_ovl_layer_on,
.comp_layer_off = mtk_ovl_layer_off,
.comp_layer_config = mtk_ovl_layer_config,
+};
The callback duplication here suggests the component model can be refined a bit. I think you probably want a single one of these:
static const struct mtk_ddp_comp_funcs ddp_ovl = { .config = mtk_ovl_config, .start = mtk_ovl_start, .enable_vblank = mtk_ovl_enable_vblank, .disable_vblank = mtk_ovl_disable_vblank, .clear_vblank = mtk_ovl_clear_vblank, .layer_on = mtk_ovl_layer_on, .layer_off = mtk_ovl_layer_off, .layer_config = mtk_ovl_layer_config, };
Yes, the next version will drop .comp_id from the funcs.
and then to use it for both ovl's. Maybe something like this:
static const struct mtk_ddp_comp_desc ddp_ovl0 = { .id = DDP_COMPONENT_OVL0, .ops = ddp_ovl };
static const struct mtk_ddp_comp_desc ddp_ovl1 = { .id = DDP_COMPONENT_OVL1, .ops = ddp_ovl };
These will not be necessary, I'll store the id in mtk_ddp_comp.
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h new file mode 100644 index 0000000..ae3a6e8 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h @@ -0,0 +1,86 @@
[...]
+static void mtk_atomic_complete(struct mtk_drm_private *private,
struct drm_atomic_state *state)
+{
struct drm_device *drm = private->drm;
drm_atomic_helper_commit_modeset_disables(drm, state);
drm_atomic_helper_commit_planes(drm, state);
drm_atomic_helper_commit_modeset_enables(drm, state);
drm_atomic_helper_wait_for_vblanks(drm, state);
Why wait for vblank here? Are you sure we waited long enough here? This will return after the very next vblank. However, what guarantees that the new state was really transferred to the hardware in time?
I'll check out how to use the DISP_MUTEX for this. It has an interrupt to signal when the register settings were applied.
[...]
+static int mtk_atomic_commit(struct drm_device *drm,
struct drm_atomic_state *state,
bool async)
+{
struct mtk_drm_private *private = drm->dev_private;
int ret;
ret = drm_atomic_helper_prepare_planes(drm, state);
if (ret)
return ret;
mutex_lock(&private->commit.lock);
flush_work(&private->commit.work);
drm_atomic_helper_swap_state(drm, state);
if (async)
mtk_atomic_schedule(private, state);
else
mtk_atomic_complete(private, state);
mutex_unlock(&private->commit.lock);
What does this lock do? AFAICT, it only ensures that the second half of mtk_atomic_commit() cannot execute twice at the same time. However, I expect simultaneous calls to atomic_commit() should already be prohibited by the drm core?
I don't see it. DRM_IOCTL_MODE_ATOMIC is unlocked, and drm_mode_atomic_ioctl only calls drm_modeset_acquire_init but doesn't get any lock before calling drm_atomic_(async_)commit.
[...]
+static int mtk_drm_kms_init(struct drm_device *drm) +{
struct mtk_drm_private *private = drm->dev_private;
struct platform_device *pdev;
int ret;
int i;
pdev = of_find_device_by_node(private->mutex_node);
if (!pdev) {
dev_err(drm->dev, "Waiting for disp-mutex device %s\n",
private->mutex_node->full_name);
of_node_put(private->mutex_node);
return -EPROBE_DEFER;
}
private->mutex_dev = &pdev->dev;
for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_node[i])
break;
pdev = of_find_device_by_node(private->larb_node[i]);
if (!pdev) {
dev_err(drm->dev, "Waiting for larb device %s\n",
private->larb_node[i]->full_name);
Nit: dev_warn() perhaps?
Ok.
[...]
for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_dev[i])
break;
ret = mtk_smi_larb_get(private->larb_dev[i]);
Hmm. this looks like it should be done by a crtc function, and, only for CRTCs that are actually going to be used.
Yes, I suppose these should only be enabled while the respective DMA components (OVL, RDMA, WDMA) are active. I think the correct place for this would be in the component's .enable (or a new .prepare if necessary).
if (ret) {
DRM_ERROR("Failed to get larb: %d\n", ret);
goto err_larb_get;
}
}
drm_kms_helper_poll_init(drm);
drm_mode_config_reset(drm);
return 0;
+err_larb_get:
for (i = i - 1; i >= 0; i--)
mtk_smi_larb_put(private->larb_dev[i]);
drm_kms_helper_poll_fini(drm);
Not drm_kms_helper_poll_fini().
Ok.
[...]
+static void mtk_drm_kms_deinit(struct drm_device *drm) +{
put the larbs?
Ok, unless I move them elsewhere.
drm_kms_helper_poll_fini(drm);
drm_vblank_cleanup(drm);
unbind_all ?
Ok.
[...]
+static struct drm_driver mtk_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
| DRIVER_ATOMIC ?
Yes.
[...]
+static int mtk_drm_probe(struct platform_device *pdev) +{
[...]
/* Iterate over sibling DISP function blocks */
for_each_child_of_node(dev->of_node->parent, node) {
const struct of_device_id *of_id;
enum mtk_ddp_comp_type comp_type;
int comp_id;
of_id = of_match_node(mtk_ddp_comp_dt_ids, node);
if (!of_id)
continue;
comp_type = (enum mtk_ddp_comp_type)of_id->data;
if (comp_type == MTK_DISP_MUTEX) {
private->mutex_node = of_node_get(node);
continue;
}
I'd move the MUTEX check after "is_available()", to catch the odd case where the MUTEX node is disabled (it will still be NULL, and fail below).
Ok.
if (!of_device_is_available(node)) {
dev_dbg(dev, "Skipping disabled component %s\n",
node->full_name);
continue;
}
comp_id = mtk_ddp_comp_get_id(node, comp_type);
if (comp_id < 0) {
dev_info(dev, "Skipping unknown component %s\n",
node->full_name);
dev_warn()
Ok.
[...]
if (comp_type == MTK_DISP_OVL) {
struct device_node *larb_node;
larb_node = of_parse_phandle(node, "mediatek,larb", 0);
if (larb_node && num_larbs < MAX_CRTC)
private->larb_node[num_larbs++] = larb_node;
It feels like the larb_nodes should be handled directly by the ovl driver.
Yes. And the rdma/wdma, probably. For that I'd like to split the ovl driver from the crtc implementation.
}
}
if (!private->mutex_node) {
dev_err(dev, "Failed to find disp-mutex node\n");
Cleanup: of_node_put() any nodes already in comp_node[] Anything to clean after component_match_add()? of_node_put() larb_nodes?
[...]
+static int mtk_drm_remove(struct platform_device *pdev) +{
component_master_del(&pdev->dev, &mtk_drm_ops);
pm_runtime_disable(&pdev->dev);
Cleanup: of_node_put() any nodes already in comp_node[]
Ok.
Anything to clean after component_match_add()?
No, that just calls devm_kmalloc internally.
of_node_put() larb_nodes?
Ok.
[...]
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h new file mode 100644 index 0000000..5e5128e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,61 @@
[...]
+struct mtk_drm_private {
struct drm_fb_helper *fb_helper;
Not used?
Will drop, this belongs into a separate fb_helper patch (not part of this series).
struct drm_device *drm;
/*
* created crtc object would be contained at this array and
* this array is used to be aware of which crtc did it request vblank.
I don't understand this comment.
I'll just drop it. This array of pointers is needed to translate from pipe number to struct mtk_drm_crtc in crtc_enable/disable_vblank.
*/
struct drm_crtc *crtc[MAX_CRTC];
struct drm_property *plane_zpos_property;
Not used.
Will drop, this belongs into a separate zpos property patch (not part of this series).
unsigned int pipe;
what pipe is this?
That should be renamed to num_pipes.
struct device_node *larb_node[MAX_CRTC];
struct device *larb_dev[MAX_CRTC];
struct device_node *mutex_node;
struct device *mutex_dev;
void __iomem *config_regs;
unsigned int path_len[MAX_CRTC];
const enum mtk_ddp_comp_id *path[MAX_CRTC];
Having 5 arrays of length MAX_CRTC suggests you really want one to combine these fields into a struct, and have an MAX_CRTC array of the struct. In fact, this struct sounds like it should be "mtk_crtc", which wraps a drm_crtc.
The larb_node/dev should be moved into DMA component drivers, but a static path indeed currently maps directly to a crtc.
[...]
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c new file mode 100644 index 0000000..dfa931b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c @@ -0,0 +1,151 @@
[...]
+struct mtk_drm_fb {
struct drm_framebuffer base;
struct drm_gem_object *gem_obj[MAX_FB_OBJ];
Nit: The display controller driver does not handle multi-buffer formats. So, maybe just add the array later when it does. Arguably, for now it is just adding complexity with no benefit.
Ok.
[...]
+struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
struct drm_file *file,
struct drm_mode_fb_cmd2 *cmd)
+{
unsigned int hsub, vsub, i;
struct mtk_drm_fb *mtk_fb;
struct drm_gem_object *gem[MAX_FB_OBJ];
int err;
hsub = drm_format_horz_chroma_subsampling(cmd->pixel_format);
vsub = drm_format_vert_chroma_subsampling(cmd->pixel_format);
nit: blank line here would help readability.
for (i = 0; i < drm_format_num_planes(cmd->pixel_format); i++) {
Ok.
[...]
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h new file mode 100644 index 0000000..9ce7307 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h @@ -0,0 +1,29 @@ +struct mtk_drm_gem_obj *mtk_drm_gem_init(struct drm_device *dev,
unsigned long size)
static? But actually, just folding this directly into mtk_drm_gem_create() would make the code flow clearer, since this function has no corresponding _fini().
Yes.
[...]
+struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev,
unsigned long size, bool alloc_kmap)
static?
It is referenced from mtk_drm_drv.c
[...]
+void mtk_drm_gem_free_object(struct drm_gem_object *obj) +{
struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
drm_gem_free_mmap_offset(obj);
/* release file pointer to gem object. */
drm_gem_object_release(obj);
drm_gem_object_release calls drm_gem_free_mmap_offset().
Ok.
dma_free_attrs(obj->dev->dev, obj->size, mtk_gem->cookie,
mtk_gem->dma_addr, &mtk_gem->dma_attrs);
I think you want to do this before calling drm_gem_object_release.
Why?
[...]
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_gem.h b/drivers/gpu/drm/mediatek/mtk_drm_gem.h new file mode 100644 index 0000000..fb7953e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.h @@ -0,0 +1,56 @@
[...]
+struct mtk_drm_gem_obj {
struct drm_gem_object base;
void __iomem *cookie;
void __iomem *kvaddr;
Is kvaddr really __iomem?
No, and neither is cookie.
[...]
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_plane.c b/drivers/gpu/drm/mediatek/mtk_drm_plane.c new file mode 100644 index 0000000..3a8843c --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,167 @@
[...]
+static void mtk_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
+{
struct drm_plane_state *state = plane->state;
struct drm_gem_object *gem_obj;
struct drm_crtc *crtc = state->crtc;
struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
struct drm_rect dest = {
.x1 = state->crtc_x,
.y1 = state->crtc_y,
.x2 = state->crtc_x + state->crtc_w,
.y2 = state->crtc_y + state->crtc_h,
};
struct drm_rect clip = { 0, };
if (!crtc)
return;
clip.x2 = state->crtc->state->mode.hdisplay;
clip.y2 = state->crtc->state->mode.vdisplay;
drm_rect_intersect(&dest, &clip);
mtk_plane->disp_size = (dest.y2 - dest.y1) << 16 | (dest.x2 - dest.x1);
plane->fb = state->fb;
This doesn't look right. The drm core should be doing this for us. Why do you need this here?
I really think we want to be using a "struct mtk_plane_state" which wraps a drm_plane_state and accumulates our ovl specific changes. During init, when creating crtcs & planes, we should tell each plane its id, and which ovl it should use. The mtk_plane code can then provide a function for the vblank irq to apply mtk_plane's pending state.
I agree.
gem_obj = mtk_fb_get_gem_obj(state->fb, 0);
mtk_plane->flip_obj = to_mtk_gem_obj(gem_obj);
We do not need to store "flip_obj". We only use it twice, +3 and +5 lines down from here. Also, this doesn't look safe. What if state->fb is NULL?
Since we implement atomic_disable, atomic_update should never be called with state->fb == NULL.
It works because to_mtk_gem_obj(NULL) == NULL, but it relies on an internal implementation detail - gem_obj.base is at offset 0.
mtk_plane->crtc = crtc;
This isn't used, either.
Will drop.
[...]
+static void mtk_plane_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
+{
struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
struct drm_crtc *crtc = old_state->crtc;
if (!crtc)
return;
mtk_drm_crtc_plane_config(crtc, mtk_plane->idx, false, 0);
mtk_drm_crtc_commit(crtc);
Why this here? Won't this happen during mtk_drm_crtc_atomic_flush?
Because that just marks a configuration change in pending_ovl_config, which you already suggested rightfully belongs in mtk_plane_state. I'll fix that.
[...]
+int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane,
unsigned long possible_crtcs, enum drm_plane_type type,
unsigned int zpos, unsigned int max_plane)
max_plane is not used.
Ok... enough for today :-).
This is also part of the zpos property patch, I'll drop it.
regards Philipp
From: CK Hu ck.hu@mediatek.com
This patch add a drm encoder/connector driver for the MIPI DSI function block of the Mediatek display subsystem and a phy driver for the MIPI TX D-PHY control module.
Signed-off-by: Jitao Shi jitao.shi@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- drivers/gpu/drm/mediatek/Makefile | 4 +- drivers/gpu/drm/mediatek/mtk_drm_drv.c | 19 + drivers/gpu/drm/mediatek/mtk_drm_drv.h | 2 + drivers/gpu/drm/mediatek/mtk_dsi.c | 787 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_dsi.h | 54 +++ drivers/gpu/drm/mediatek/mtk_mipi_tx.c | 375 ++++++++++++++++ drivers/gpu/drm/mediatek/mtk_mipi_tx.h | 21 + 7 files changed, 1261 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_dsi.h create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.c create mode 100644 drivers/gpu/drm/mediatek/mtk_mipi_tx.h
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile index ba6d3fc..0d4aeeb 100644 --- a/drivers/gpu/drm/mediatek/Makefile +++ b/drivers/gpu/drm/mediatek/Makefile @@ -4,7 +4,9 @@ mediatek-drm-y := mtk_drm_drv.o \ mtk_drm_ddp_comp.o \ mtk_drm_fb.o \ mtk_drm_gem.o \ - mtk_drm_plane.o + mtk_drm_plane.o \ + mtk_dsi.o \ + mtk_mipi_tx.o
obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index fbca99f..cbaf208 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -550,8 +550,25 @@ static int __init mtk_drm_init(void) goto drm_err; }
+ ret = platform_driver_register(&mtk_dsi_driver); + if (ret < 0) { + pr_err("Failed to register DSI platform driver: %d\n!", ret); + goto disp_ovl_err; + } + + ret = platform_driver_register(&mtk_mipi_tx_driver); + if (ret < 0) { + pr_err("Failed to register MIPI TX platform driver: %d\n!", + ret); + goto dsi_err; + } + return 0;
+dsi_err: + platform_driver_unregister(&mtk_dsi_driver); +disp_ovl_err: + platform_driver_unregister(&mtk_disp_ovl_driver); drm_err: platform_driver_unregister(&mtk_drm_platform_driver); err: @@ -560,6 +577,8 @@ err:
static void __exit mtk_drm_exit(void) { + platform_driver_unregister(&mtk_mipi_tx_driver); + platform_driver_unregister(&mtk_dsi_driver); platform_driver_unregister(&mtk_disp_ovl_driver); platform_driver_unregister(&mtk_drm_platform_driver); } diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h index 5e5128e..9c81abe 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -57,5 +57,7 @@ struct mtk_drm_private { };
extern struct platform_driver mtk_disp_ovl_driver; +extern struct platform_driver mtk_dsi_driver; +extern struct platform_driver mtk_mipi_tx_driver;
#endif /* MTK_DRM_DRV_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c new file mode 100644 index 0000000..fe3c450 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -0,0 +1,787 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <video/videomode.h> + +#include "mtk_dsi.h" +#include "mtk_mipi_tx.h" + +#define DSI_VIDEO_FIFO_DEPTH (1920 / 4) +#define DSI_HOST_FIFO_DEPTH 64 + +#define DSI_START 0x00 + +#define DSI_CON_CTRL 0x10 +#define DSI_RESET BIT(0) +#define DSI_EN BIT(1) + +#define DSI_MODE_CTRL 0x14 +#define MODE (3) +#define CMD_MODE 0 +#define SYNC_PULSE_MODE 1 +#define SYNC_EVENT_MODE 2 +#define BURST_MODE 3 +#define FRM_MODE BIT(16) +#define MIX_MODE BIT(17) + +#define DSI_TXRX_CTRL 0x18 +#define VC_NUM (2 << 0) +#define LANE_NUM (0xf << 2) +#define DIS_EOT BIT(6) +#define NULL_EN BIT(7) +#define TE_FREERUN BIT(8) +#define EXT_TE_EN BIT(9) +#define EXT_TE_EDGE BIT(10) +#define MAX_RTN_SIZE (0xf << 12) +#define HSTX_CKLP_EN BIT(16) + +#define DSI_PSCTRL 0x1c +#define DSI_PS_WC 0x3fff +#define DSI_PS_SEL (3 << 16) +#define PACKED_PS_16BIT_RGB565 (0 << 16) +#define LOOSELY_PS_18BIT_RGB666 (1 << 16) +#define PACKED_PS_18BIT_RGB666 (2 << 16) +#define PACKED_PS_24BIT_RGB888 (3 << 16) + +#define DSI_VSA_NL 0x20 +#define DSI_VBP_NL 0x24 +#define DSI_VFP_NL 0x28 +#define DSI_VACT_NL 0x2C +#define DSI_HSA_WC 0x50 +#define DSI_HBP_WC 0x54 +#define DSI_HFP_WC 0x58 + +#define DSI_HSTX_CKL_WC 0x64 + +#define DSI_PHY_LCCON 0x104 +#define LC_HS_TX_EN BIT(0) +#define LC_ULPM_EN BIT(1) +#define LC_WAKEUP_EN BIT(2) + +#define DSI_PHY_LD0CON 0x108 +#define LD0_HS_TX_EN BIT(0) +#define LD0_ULPM_EN BIT(1) +#define LD0_WAKEUP_EN BIT(2) + +#define DSI_PHY_TIMECON0 0x110 +#define LPX (0xff << 0) +#define HS_PRPR (0xff << 8) +#define HS_ZERO (0xff << 16) +#define HS_TRAIL (0xff << 24) + +#define DSI_PHY_TIMECON1 0x114 +#define TA_GO (0xff << 0) +#define TA_SURE (0xff << 8) +#define TA_GET (0xff << 16) +#define DA_HS_EXIT (0xff << 24) + +#define DSI_PHY_TIMECON2 0x118 +#define CONT_DET (0xff << 0) +#define CLK_ZERO (0xff << 16) +#define CLK_TRAIL (0xff << 24) + +#define DSI_PHY_TIMECON3 0x11c +#define CLK_HS_PRPR (0xff << 0) +#define CLK_HS_POST (0xff << 8) +#define CLK_HS_EXIT (0xff << 16) + +#define NS_TO_CYCLE(n, c) ((n) / c + (((n) % c) ? 1 : 0)) + +static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data) +{ + u32 temp = readl(dsi->dsi_reg_base + offset); + + writel((temp & ~mask) | (data & mask), dsi->dsi_reg_base + offset); +} + +static void dsi_phy_timconfig(struct mtk_dsi *dsi) +{ + u32 timcon0, timcon1, timcon2, timcon3; + unsigned int ui, cycle_time; + unsigned int lpx; + + ui = 1000 / dsi->data_rate + 0x01; + cycle_time = 8000 / dsi->data_rate + 0x01; + lpx = 5; + + timcon0 = (8 << 24) | (0xa << 16) | (0x6 << 8) | lpx; + timcon1 = (7 << 24) | (5 * lpx << 16) | ((3 * lpx) / 2) << 8 | + (4 * lpx); + timcon2 = ((NS_TO_CYCLE(0x64, cycle_time) + 0xa) << 24) | + (NS_TO_CYCLE(0x150, cycle_time) << 16); + timcon3 = (2 * lpx) << 16 | NS_TO_CYCLE(80 + 52 * ui, cycle_time) << 8 | + NS_TO_CYCLE(0x40, cycle_time); + + writel(timcon0, dsi->dsi_reg_base + DSI_PHY_TIMECON0); + writel(timcon1, dsi->dsi_reg_base + DSI_PHY_TIMECON1); + writel(timcon2, dsi->dsi_reg_base + DSI_PHY_TIMECON2); + writel(timcon3, dsi->dsi_reg_base + DSI_PHY_TIMECON3); +} + +static void mtk_dsi_clk_enable(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN); +} + +static void mtk_dsi_clk_disable(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, 0); +} + +static void mtk_dsi_reset(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, DSI_RESET); + mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_RESET, 0); +} + +static int mtk_dsi_poweron(struct mtk_dsi *dsi) +{ + int ret; + struct drm_device *dev = dsi->drm_dev; + + /** + * data_rate = (pixel_clock / 1000) * pixel_dipth * mipi_ratio; + * pixel_clock unit is Khz, data_rata unit is MHz, so need divide 1000. + * mipi_ratio is mipi clk coefficient for balance the pixel clk in mipi. + * we set mipi_ratio is 1.05. + */ + dsi->data_rate = dsi->vm.pixelclock * 3 * 21 / (1 * 1000 * 10); + + mtk_mipi_tx_set_data_rate(dsi->phy, dsi->data_rate); + phy_power_on(dsi->phy); + + ret = clk_prepare_enable(dsi->dsi0_engine_clk_cg); + if (ret < 0) { + dev_err(dev->dev, "can't enable dsi0_engine_clk_cg %d\n", ret); + goto err_dsi0_engine_clk_cg; + } + + ret = clk_prepare_enable(dsi->dsi0_digital_clk_cg); + if (ret < 0) { + dev_err(dev->dev, "can't enable dsi0_digital_clk_cg %d\n", ret); + goto err_dsi0_digital_clk_cg; + } + + mtk_dsi_clk_enable(dsi); + mtk_dsi_reset(dsi); + dsi_phy_timconfig(dsi); + + return 0; + +err_dsi0_digital_clk_cg: + clk_disable_unprepare(dsi->dsi0_engine_clk_cg); + +err_dsi0_engine_clk_cg: + return ret; +} + +static void dsi_clk_ulp_mode_enter(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0); +} + +static void dsi_clk_ulp_mode_leave(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_ULPM_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, LC_WAKEUP_EN); + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_WAKEUP_EN, 0); +} + +static void dsi_lane0_ulp_mode_enter(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_HS_TX_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0); +} + +static void dsi_lane0_ulp_mode_leave(struct mtk_dsi *dsi) +{ + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_ULPM_EN, 0); + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, LD0_WAKEUP_EN); + mtk_dsi_mask(dsi, DSI_PHY_LD0CON, LD0_WAKEUP_EN, 0); +} + +static bool dsi_clk_hs_state(struct mtk_dsi *dsi) +{ + u32 tmp_reg1; + + tmp_reg1 = readl(dsi->dsi_reg_base + DSI_PHY_LCCON); + return ((tmp_reg1 & LC_HS_TX_EN) == 1) ? true : false; +} + +static void dsi_clk_hs_mode(struct mtk_dsi *dsi, bool enter) +{ + if (enter && !dsi_clk_hs_state(dsi)) + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, LC_HS_TX_EN); + else if (!enter && dsi_clk_hs_state(dsi)) + mtk_dsi_mask(dsi, DSI_PHY_LCCON, LC_HS_TX_EN, 0); +} + +static void dsi_set_mode(struct mtk_dsi *dsi) +{ + u32 vid_mode = CMD_MODE; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + vid_mode = SYNC_PULSE_MODE; + + if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) && + !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) + vid_mode = BURST_MODE; + } + + writel(vid_mode, dsi->dsi_reg_base + DSI_MODE_CTRL); +} + +static void dsi_ps_control_vact(struct mtk_dsi *dsi) +{ + struct videomode *vm = &dsi->vm; + u32 dsi_buf_bpp, ps_wc; + u32 ps_bpp_mode; + + if (dsi->format == MIPI_DSI_FMT_RGB565) + dsi_buf_bpp = 2; + else + dsi_buf_bpp = 3; + + ps_wc = vm->hactive * dsi_buf_bpp; + ps_bpp_mode = ps_wc; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + ps_bpp_mode |= PACKED_PS_24BIT_RGB888; + break; + case MIPI_DSI_FMT_RGB666: + ps_bpp_mode |= PACKED_PS_18BIT_RGB666; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + ps_bpp_mode |= LOOSELY_PS_18BIT_RGB666; + break; + case MIPI_DSI_FMT_RGB565: + ps_bpp_mode |= PACKED_PS_16BIT_RGB565; + break; + } + + writel(vm->vactive, dsi->dsi_reg_base + DSI_VACT_NL); + writel(ps_bpp_mode, dsi->dsi_reg_base + DSI_PSCTRL); + writel(ps_wc, dsi->dsi_reg_base + DSI_HSTX_CKL_WC); +} + +static void dsi_rxtx_control(struct mtk_dsi *dsi) +{ + u32 tmp_reg; + + switch (dsi->lanes) { + case 1: + tmp_reg = 1 << 2; + break; + case 2: + tmp_reg = 3 << 2; + break; + case 3: + tmp_reg = 7 << 2; + break; + case 4: + tmp_reg = 0xf << 2; + break; + default: + tmp_reg = 0xf << 2; + break; + } + + writel(tmp_reg, dsi->dsi_reg_base + DSI_TXRX_CTRL); +} + +static void dsi_ps_control(struct mtk_dsi *dsi) +{ + unsigned int dsi_tmp_buf_bpp; + u32 tmp_reg; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + tmp_reg = PACKED_PS_24BIT_RGB888; + dsi_tmp_buf_bpp = 3; + break; + case MIPI_DSI_FMT_RGB666: + tmp_reg = LOOSELY_PS_18BIT_RGB666; + dsi_tmp_buf_bpp = 3; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + tmp_reg = PACKED_PS_18BIT_RGB666; + dsi_tmp_buf_bpp = 3; + break; + case MIPI_DSI_FMT_RGB565: + tmp_reg = PACKED_PS_16BIT_RGB565; + dsi_tmp_buf_bpp = 2; + break; + default: + tmp_reg = PACKED_PS_24BIT_RGB888; + dsi_tmp_buf_bpp = 3; + break; + } + + tmp_reg += dsi->vm.hactive * dsi_tmp_buf_bpp & DSI_PS_WC; + writel(tmp_reg, dsi->dsi_reg_base + DSI_PSCTRL); +} + +static void dsi_config_vdo_timing(struct mtk_dsi *dsi) +{ + unsigned int horizontal_sync_active_byte; + unsigned int horizontal_backporch_byte; + unsigned int horizontal_frontporch_byte; + unsigned int dsi_tmp_buf_bpp; + + struct videomode *vm = &dsi->vm; + + if (dsi->format == MIPI_DSI_FMT_RGB565) + dsi_tmp_buf_bpp = 2; + else + dsi_tmp_buf_bpp = 3; + + writel(vm->vsync_len, dsi->dsi_reg_base + DSI_VSA_NL); + writel(vm->vback_porch, dsi->dsi_reg_base + DSI_VBP_NL); + writel(vm->vfront_porch, dsi->dsi_reg_base + DSI_VFP_NL); + writel(vm->vactive, dsi->dsi_reg_base + DSI_VACT_NL); + + horizontal_sync_active_byte = (vm->hsync_len * dsi_tmp_buf_bpp - 10); + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + horizontal_backporch_byte = + (vm->hback_porch * dsi_tmp_buf_bpp - 10); + else + horizontal_backporch_byte = ((vm->hback_porch + vm->hsync_len) * + dsi_tmp_buf_bpp - 10); + + horizontal_frontporch_byte = (vm->hfront_porch * dsi_tmp_buf_bpp - 12); + + writel(horizontal_sync_active_byte, dsi->dsi_reg_base + DSI_HSA_WC); + writel(horizontal_backporch_byte, dsi->dsi_reg_base + DSI_HBP_WC); + writel(horizontal_frontporch_byte, dsi->dsi_reg_base + DSI_HFP_WC); + + dsi_ps_control(dsi); +} + +static void mtk_dsi_start(struct mtk_dsi *dsi) +{ + writel(0, dsi->dsi_reg_base + DSI_START); + writel(1, dsi->dsi_reg_base + DSI_START); +} + +static void mtk_dsi_poweroff(struct mtk_dsi *dsi) +{ + mtk_dsi_clk_disable(dsi); + + clk_disable_unprepare(dsi->dsi0_engine_clk_cg); + clk_disable_unprepare(dsi->dsi0_digital_clk_cg); + + phy_power_off(dsi->phy); +} + +static void mtk_output_dsi_enable(struct mtk_dsi *dsi) +{ + int ret; + + if (dsi->enabled) + return; + + if (dsi->panel) { + if (drm_panel_prepare(dsi->panel)) { + DRM_ERROR("failed to setup the panel\n"); + return; + } + } + + ret = mtk_dsi_poweron(dsi); + if (ret < 0) { + DRM_ERROR("failed to power on dsi\n"); + return; + } + + dsi_rxtx_control(dsi); + + dsi_clk_ulp_mode_leave(dsi); + dsi_lane0_ulp_mode_leave(dsi); + dsi_clk_hs_mode(dsi, 0); + dsi_set_mode(dsi); + + dsi_ps_control_vact(dsi); + dsi_config_vdo_timing(dsi); + + dsi_set_mode(dsi); + dsi_clk_hs_mode(dsi, 1); + + mtk_dsi_start(dsi); + + dsi->enabled = true; +} + +static void mtk_output_dsi_disable(struct mtk_dsi *dsi) +{ + if (!dsi->enabled) + return; + + if (dsi->panel) { + if (drm_panel_disable(dsi->panel)) { + DRM_ERROR("failed to disable the panel\n"); + return; + } + } + + dsi_lane0_ulp_mode_enter(dsi); + dsi_clk_ulp_mode_enter(dsi); + mtk_dsi_poweroff(dsi); + + dsi->enabled = false; +} + +static void mtk_dsi_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs mtk_dsi_encoder_funcs = { + .destroy = mtk_dsi_encoder_destroy, +}; + +static bool mtk_dsi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void mtk_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + dsi->vm.pixelclock = adjusted->clock; + dsi->vm.hactive = adjusted->hdisplay; + dsi->vm.hback_porch = adjusted->htotal - adjusted->hsync_end; + dsi->vm.hfront_porch = adjusted->hsync_start - adjusted->hdisplay; + dsi->vm.hsync_len = adjusted->hsync_end - adjusted->hsync_start; + + dsi->vm.vactive = adjusted->vdisplay; + dsi->vm.vback_porch = adjusted->vtotal - adjusted->vsync_end; + dsi->vm.vfront_porch = adjusted->vsync_start - adjusted->vdisplay; + dsi->vm.vsync_len = adjusted->vsync_end - adjusted->vsync_start; +} + +static void mtk_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + mtk_output_dsi_disable(dsi); +} + +static void mtk_dsi_encoder_enable(struct drm_encoder *encoder) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + mtk_output_dsi_enable(dsi); +} + +static enum drm_connector_status mtk_dsi_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void mtk_dsi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static int mtk_dsi_connector_get_modes(struct drm_connector *connector) +{ + struct mtk_dsi *dsi = connector_to_dsi(connector); + + return drm_panel_get_modes(dsi->panel); +} + +static struct drm_encoder *mtk_dsi_connector_best_encoder( + struct drm_connector *connector) +{ + struct mtk_dsi *dsi = connector_to_dsi(connector); + + return &dsi->encoder; +} + +static const struct drm_encoder_helper_funcs mtk_dsi_encoder_helper_funcs = { + .mode_fixup = mtk_dsi_encoder_mode_fixup, + .mode_set = mtk_dsi_encoder_mode_set, + .disable = mtk_dsi_encoder_disable, + .enable = mtk_dsi_encoder_enable, +}; + +static const struct drm_connector_funcs mtk_dsi_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = mtk_dsi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = mtk_dsi_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs + mtk_dsi_connector_helper_funcs = { + .get_modes = mtk_dsi_connector_get_modes, + .best_encoder = mtk_dsi_connector_best_encoder, +}; + +static int mtk_drm_attach_lcm_bridge(struct drm_bridge *bridge, + struct drm_encoder *encoder) +{ + int ret; + + encoder->bridge = bridge; + bridge->encoder = encoder; + ret = drm_bridge_attach(encoder->dev, bridge); + if (ret) { + DRM_ERROR("Failed to attach bridge to drm\n"); + return ret; + } + + return 0; +} + +static int mtk_dsi_create_conn_enc(struct mtk_dsi *dsi) +{ + int ret; + + ret = drm_encoder_init(dsi->drm_dev, &dsi->encoder, + &mtk_dsi_encoder_funcs, DRM_MODE_ENCODER_DSI); + + if (ret) { + DRM_ERROR("Failed to encoder init to drm\n"); + return ret; + } + drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs); + + dsi->encoder.possible_crtcs = 1; + + /* Pre-empt DP connector creation if there's a bridge */ + ret = mtk_drm_attach_lcm_bridge(dsi->bridge, &dsi->encoder); + if (!ret) + return 0; + + ret = drm_connector_init(dsi->drm_dev, &dsi->conn, + &mtk_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + DRM_ERROR("Failed to connector init to drm\n"); + goto errconnector; + } + + drm_connector_helper_add(&dsi->conn, &mtk_dsi_connector_helper_funcs); + + ret = drm_connector_register(&dsi->conn); + if (ret) { + DRM_ERROR("Failed to connector register to drm\n"); + goto errconnectorreg; + } + + dsi->conn.dpms = DRM_MODE_DPMS_OFF; + drm_mode_connector_attach_encoder(&dsi->conn, &dsi->encoder); + + if (dsi->panel) { + ret = drm_panel_attach(dsi->panel, &dsi->conn); + if (ret) { + DRM_ERROR("Failed to attact panel to drm\n"); + return ret; + } + } + return 0; + +errconnector: + drm_encoder_cleanup(&dsi->encoder); +errconnectorreg: + drm_connector_cleanup(&dsi->conn); + + return ret; +} + +static void mtk_dsi_destroy_conn_enc(struct mtk_dsi *dsi) +{ + drm_encoder_cleanup(&dsi->encoder); + drm_connector_unregister(&dsi->conn); + drm_connector_cleanup(&dsi->conn); +} + +static int mtk_dsi_bind(struct device *dev, struct device *master, void *data) +{ + int ret; + struct mtk_dsi *dsi = dev_get_drvdata(dev); + + dsi->drm_dev = data; + + ret = mtk_dsi_create_conn_enc(dsi); + if (ret) { + DRM_ERROR("Encoder create failed with %d\n", ret); + return ret; + } + + return 0; +} + +static void mtk_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_dsi *dsi; + + dsi = platform_get_drvdata(to_platform_device(dev)); + mtk_dsi_destroy_conn_enc(dsi); + + dsi->drm_dev = NULL; +} + +static const struct component_ops mtk_dsi_component_ops = { + .bind = mtk_dsi_bind, + .unbind = mtk_dsi_unbind, +}; + +static int mtk_dsi_probe(struct platform_device *pdev) +{ + struct mtk_dsi *dsi; + struct device *dev = &pdev->dev; + struct device_node *remote_node, *endpoint; + struct resource *regs; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (endpoint) { + remote_node = of_graph_get_remote_port_parent(endpoint); + if (!remote_node) { + dev_err(dev, "No panel connected\n"); + return -ENODEV; + } + + dsi->bridge = of_drm_find_bridge(remote_node); + dsi->panel = of_drm_find_panel(remote_node); + of_node_put(remote_node); + if (!dsi->bridge && !dsi->panel) { + dev_info(dev, "Waiting for bridge or panel driver\n"); + return -EPROBE_DEFER; + } + } + + dsi->dsi0_engine_clk_cg = devm_clk_get(dev, "engine"); + if (IS_ERR(dsi->dsi0_engine_clk_cg)) { + ret = PTR_ERR(dsi->dsi0_engine_clk_cg); + dev_err(dev, "Failed to get engine clock: %d\n", ret); + return ret; + } + + dsi->dsi0_digital_clk_cg = devm_clk_get(dev, "digital"); + if (IS_ERR(dsi->dsi0_digital_clk_cg)) { + ret = PTR_ERR(dsi->dsi0_digital_clk_cg); + dev_err(dev, "Failed to get digital clock: %d\n", ret); + return ret; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->dsi_reg_base = devm_ioremap_resource(dev, regs); + if (IS_ERR(dsi->dsi_reg_base)) { + ret = PTR_ERR(dsi->dsi_reg_base); + dev_err(dev, "Failed to ioremap memory: %d\n", ret); + return ret; + } + + dsi->phy = devm_phy_get(dev, "dphy"); + if (IS_ERR(dsi->phy)) { + ret = PTR_ERR(dsi->phy); + dev_err(dev, "Failed to get MIPI-DPHY: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, dsi); + + ret = component_add(&pdev->dev, &mtk_dsi_component_ops); + if (ret) { + dev_err(dev, "Failed to add DSI component\n"); + return -EPROBE_DEFER; + } + + return 0; +} + +static int mtk_dsi_remove(struct platform_device *pdev) +{ + struct mtk_dsi *dsi = platform_get_drvdata(pdev); + + mtk_output_dsi_disable(dsi); + component_del(&pdev->dev, &mtk_dsi_component_ops); + + return 0; +} + +#ifdef CONFIG_PM +static int mtk_dsi_suspend(struct device *dev) +{ + struct mtk_dsi *dsi; + + dsi = dev_get_drvdata(dev); + + mtk_output_dsi_disable(dsi); + DRM_INFO("dsi suspend success!\n"); + + return 0; +} + +static int mtk_dsi_resume(struct device *dev) +{ + struct mtk_dsi *dsi; + + dsi = dev_get_drvdata(dev); + + mtk_output_dsi_enable(dsi); + DRM_INFO("dsi resume success!\n"); + + return 0; +} +#endif +SIMPLE_DEV_PM_OPS(mtk_dsi_pm_ops, mtk_dsi_suspend, mtk_dsi_resume); + +static const struct of_device_id mtk_dsi_of_match[] = { + { .compatible = "mediatek,mt8173-dsi" }, + { }, +}; + +struct platform_driver mtk_dsi_driver = { + .probe = mtk_dsi_probe, + .remove = mtk_dsi_remove, + .driver = { + .name = "mtk-dsi", + .of_match_table = mtk_dsi_of_match, + .pm = &mtk_dsi_pm_ops, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.h b/drivers/gpu/drm/mediatek/mtk_dsi.h new file mode 100644 index 0000000..30a146b --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dsi.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MTK_DSI_H_ +#define _MTK_DSI_H_ + +#include <drm/drm_crtc.h> + +struct phy; + +struct mtk_dsi { + struct drm_device *drm_dev; + struct drm_encoder encoder; + struct drm_connector conn; + struct drm_panel *panel; + struct drm_bridge *bridge; + struct phy *phy; + + void __iomem *dsi_reg_base; + void __iomem *dsi_tx_reg_base; + + struct clk *dsi0_engine_clk_cg; + struct clk *dsi0_digital_clk_cg; + + u32 data_rate; + + unsigned long mode_flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; + struct videomode vm; + bool enabled; +}; + +static inline struct mtk_dsi *encoder_to_dsi(struct drm_encoder *e) +{ + return container_of(e, struct mtk_dsi, encoder); +} + +static inline struct mtk_dsi *connector_to_dsi(struct drm_connector *c) +{ + return container_of(c, struct mtk_dsi, conn); +} + +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_mipi_tx.c b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c new file mode 100644 index 0000000..9d975cd --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.c @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> + +#define MIPITX_DSI_CON 0x00 +#define RG_DSI_LDOCORE_EN BIT(0) +#define RG_DSI_CKG_LDOOUT_EN BIT(1) +#define RG_DSI_BCLK_SEL (3 << 2) +#define RG_DSI_LD_IDX_SEL (7 << 4) +#define RG_DSI_PHYCLK_SEL (2 << 8) +#define RG_DSI_DSICLK_FREQ_SEL BIT(10) +#define RG_DSI_LPTX_CLMP_EN BIT(11) + +#define MIPITX_DSI_CLOCK_LANE 0x04 +#define RG_DSI_LNTC_LDOOUT_EN BIT(0) +#define RG_DSI_LNTC_CKLANE_EN BIT(1) +#define RG_DSI_LNTC_LPTX_IPLUS1 BIT(2) +#define RG_DSI_LNTC_LPTX_IPLUS2 BIT(3) +#define RG_DSI_LNTC_LPTX_IMINUS BIT(4) +#define RG_DSI_LNTC_LPCD_IPLUS BIT(5) +#define RG_DSI_LNTC_LPCD_IMLUS BIT(6) +#define RG_DSI_LNTC_RT_CODE (0xf << 8) + +#define MIPITX_DSI_DATA_LANE0 0x08 +#define RG_DSI_LNT0_LDOOUT_EN BIT(0) +#define RG_DSI_LNT0_CKLANE_EN BIT(1) +#define RG_DSI_LNT0_LPTX_IPLUS1 BIT(2) +#define RG_DSI_LNT0_LPTX_IPLUS2 BIT(3) +#define RG_DSI_LNT0_LPTX_IMINUS BIT(4) +#define RG_DSI_LNT0_LPCD_IPLUS BIT(5) +#define RG_DSI_LNT0_LPCD_IMINUS BIT(6) +#define RG_DSI_LNT0_RT_CODE (0xf << 8) + +#define MIPITX_DSI_DATA_LANE1 0x0c +#define RG_DSI_LNT1_LDOOUT_EN BIT(0) +#define RG_DSI_LNT1_CKLANE_EN BIT(1) +#define RG_DSI_LNT1_LPTX_IPLUS1 BIT(2) +#define RG_DSI_LNT1_LPTX_IPLUS2 BIT(3) +#define RG_DSI_LNT1_LPTX_IMINUS BIT(4) +#define RG_DSI_LNT1_LPCD_IPLUS BIT(5) +#define RG_DSI_LNT1_LPCD_IMINUS BIT(6) +#define RG_DSI_LNT1_RT_CODE (0xf << 8) + +#define MIPITX_DSI_DATA_LANE2 0x10 +#define RG_DSI_LNT2_LDOOUT_EN BIT(0) +#define RG_DSI_LNT2_CKLANE_EN BIT(1) +#define RG_DSI_LNT2_LPTX_IPLUS1 BIT(2) +#define RG_DSI_LNT2_LPTX_IPLUS2 BIT(3) +#define RG_DSI_LNT2_LPTX_IMINUS BIT(4) +#define RG_DSI_LNT2_LPCD_IPLUS BIT(5) +#define RG_DSI_LNT2_LPCD_IMINUS BIT(6) +#define RG_DSI_LNT2_RT_CODE (0xf << 8) + +#define MIPITX_DSI_DATA_LANE3 0x14 +#define RG_DSI_LNT3_LDOOUT_EN BIT(0) +#define RG_DSI_LNT3_CKLANE_EN BIT(1) +#define RG_DSI_LNT3_LPTX_IPLUS1 BIT(2) +#define RG_DSI_LNT3_LPTX_IPLUS2 BIT(3) +#define RG_DSI_LNT3_LPTX_IMINUS BIT(4) +#define RG_DSI_LNT3_LPCD_IPLUS BIT(5) +#define RG_DSI_LNT3_LPCD_IMINUS BIT(6) +#define RG_DSI_LNT3_RT_CODE (0xf << 8) + +#define MIPITX_DSI_TOP_CON 0x40 +#define RG_DSI_LNT_INTR_EN BIT(0) +#define RG_DSI_LNT_HS_BIAS_EN BIT(1) +#define RG_DSI_LNT_IMP_CAL_EN BIT(2) +#define RG_DSI_LNT_TESTMODE_EN BIT(3) +#define RG_DSI_LNT_IMP_CAL_CODE (0xf << 4) +#define RG_DSI_LNT_AIO_SEL (7 << 8) +#define RG_DSI_PAD_TIE_LOW_EN BIT(11) +#define RG_DSI_DEBUG_INPUT_EN BIT(12) +#define RG_DSI_PRESERVE (7 << 13) + +#define MIPITX_DSI_BG_CON 0x44 +#define RG_DSI_BG_CORE_EN BIT(0) +#define RG_DSI_BG_CKEN BIT(1) +#define RG_DSI_BG_DIV (0x3 << 2) +#define RG_DSI_BG_FAST_CHARGE BIT(4) +#define RG_DSI_VOUT_MSK (0x3ffff << 5) +#define RG_DSI_V12_SEL (7 << 5) +#define RG_DSI_V10_SEL (7 << 8) +#define RG_DSI_V072_SEL (7 << 11) +#define RG_DSI_V04_SEL (7 << 14) +#define RG_DSI_V032_SEL (7 << 17) +#define RG_DSI_V02_SEL (7 << 20) +#define RG_DSI_BG_R1_TRIM (0xf << 24) +#define RG_DSI_BG_R2_TRIM (0xf << 28) + +#define MIPITX_DSI_PLL_CON0 0x50 +#define RG_DSI_MPPLL_PLL_EN BIT(0) +#define RG_DSI_MPPLL_DIV_MSK (0x1ff << 1) +#define RG_DSI_MPPLL_PREDIV (3 << 1) +#define RG_DSI_MPPLL_TXDIV0 (3 << 3) +#define RG_DSI_MPPLL_TXDIV1 (3 << 5) +#define RG_DSI_MPPLL_POSDIV (7 << 7) +#define RG_DSI_MPPLL_MONVC_EN BIT(10) +#define RG_DSI_MPPLL_MONREF_EN BIT(11) +#define RG_DSI_MPPLL_VOD_EN BIT(12) + +#define MIPITX_DSI_PLL_CON1 0x54 +#define RG_DSI_MPPLL_SDM_FRA_EN BIT(0) +#define RG_DSI_MPPLL_SDM_SSC_PH_INIT BIT(1) +#define RG_DSI_MPPLL_SDM_SSC_EN BIT(2) +#define RG_DSI_MPPLL_SDM_SSC_PRD (0xffff << 16) + +#define MIPITX_DSI_PLL_CON2 0x58 + +#define MIPITX_DSI_PLL_PWR 0x68 +#define RG_DSI_MPPLL_SDM_PWR_ON BIT(0) +#define RG_DSI_MPPLL_SDM_ISO_EN BIT(1) +#define RG_DSI_MPPLL_SDM_PWR_ACK BIT(8) + +#define MIPITX_DSI_SW_CTRL 0x80 +#define SW_CTRL_EN BIT(0) + +#define MIPITX_DSI_SW_CTRL_CON0 0x84 +#define SW_LNTC_LPTX_PRE_OE BIT(0) +#define SW_LNTC_LPTX_OE BIT(1) +#define SW_LNTC_LPTX_P BIT(2) +#define SW_LNTC_LPTX_N BIT(3) +#define SW_LNTC_HSTX_PRE_OE BIT(4) +#define SW_LNTC_HSTX_OE BIT(5) +#define SW_LNTC_HSTX_ZEROCLK BIT(6) +#define SW_LNT0_LPTX_PRE_OE BIT(7) +#define SW_LNT0_LPTX_OE BIT(8) +#define SW_LNT0_LPTX_P BIT(9) +#define SW_LNT0_LPTX_N BIT(10) +#define SW_LNT0_HSTX_PRE_OE BIT(11) +#define SW_LNT0_HSTX_OE BIT(12) +#define SW_LNT0_LPRX_EN BIT(13) +#define SW_LNT1_LPTX_PRE_OE BIT(14) +#define SW_LNT1_LPTX_OE BIT(15) +#define SW_LNT1_LPTX_P BIT(16) +#define SW_LNT1_LPTX_N BIT(17) +#define SW_LNT1_HSTX_PRE_OE BIT(18) +#define SW_LNT1_HSTX_OE BIT(19) +#define SW_LNT2_LPTX_PRE_OE BIT(20) +#define SW_LNT2_LPTX_OE BIT(21) +#define SW_LNT2_LPTX_P BIT(22) +#define SW_LNT2_LPTX_N BIT(23) +#define SW_LNT2_HSTX_PRE_OE BIT(24) +#define SW_LNT2_HSTX_OE BIT(25) + +struct mtk_mipi_tx { + void __iomem *regs; + unsigned int data_rate; +}; + +static void mtk_mipi_tx_mask(struct mtk_mipi_tx *mipi_tx, u32 offset, u32 mask, + u32 data) +{ + u32 temp = readl(mipi_tx->regs + offset); + + writel((temp & ~mask) | (data & mask), mipi_tx->regs + offset); +} + +int mtk_mipi_tx_set_data_rate(struct phy *phy, unsigned int data_rate) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + + if (data_rate < 50 || data_rate > 1250) + return -EINVAL; + + mipi_tx->data_rate = data_rate; + + return 0; +} + +static int mtk_mipi_tx_power_on(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + unsigned int txdiv, txdiv0, txdiv1; + u64 pcw; + + if (mipi_tx->data_rate >= 500) { + txdiv = 1; + txdiv0 = 0; + txdiv1 = 0; + } else if (mipi_tx->data_rate >= 250) { + txdiv = 2; + txdiv0 = 1; + txdiv1 = 0; + } else if (mipi_tx->data_rate >= 125) { + txdiv = 4; + txdiv0 = 2; + txdiv1 = 0; + } else if (mipi_tx->data_rate > 62) { + txdiv = 8; + txdiv0 = 2; + txdiv1 = 1; + } else if (mipi_tx->data_rate >= 50) { + txdiv = 16; + txdiv0 = 2; + txdiv1 = 2; + } else { + return -EINVAL; + } + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_BG_CON, + RG_DSI_VOUT_MSK | RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN, + (4 << 20) | (4 << 17) | (4 << 14) | + (4 << 11) | (4 << 8) | (4 << 5) | + RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN); + + usleep_range(30, 100); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, + RG_DSI_LNT_IMP_CAL_CODE | RG_DSI_LNT_HS_BIAS_EN, + (8 << 4) | RG_DSI_LNT_HS_BIAS_EN); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CON, + RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN, + RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_PWR, + RG_DSI_MPPLL_SDM_PWR_ON | RG_DSI_MPPLL_SDM_ISO_EN, + RG_DSI_MPPLL_SDM_PWR_ON); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, + RG_DSI_MPPLL_TXDIV0 | RG_DSI_MPPLL_TXDIV1 | + RG_DSI_MPPLL_PREDIV, + (txdiv0 << 3) | (txdiv1 << 5)); + + /* + * PLL PCW config + * PCW bit 24~30 = integer part of pcw + * PCW bit 0~23 = fractional part of pcw + * pcw = data_Rate*4*txdiv/(Ref_clk*2); + * Post DIV =4, so need data_Rate*4 + * Ref_clk is 26MHz + */ + pcw = ((u64) mipi_tx->data_rate * txdiv) << 24; + pcw /= 13; + writel(pcw, mipi_tx->regs + MIPITX_DSI_PLL_CON2); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON1, + RG_DSI_MPPLL_SDM_FRA_EN, RG_DSI_MPPLL_SDM_FRA_EN); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CLOCK_LANE, + RG_DSI_LNTC_LDOOUT_EN, RG_DSI_LNTC_LDOOUT_EN); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE0, + RG_DSI_LNT0_LDOOUT_EN, RG_DSI_LNT0_LDOOUT_EN); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE1, + RG_DSI_LNT1_LDOOUT_EN, RG_DSI_LNT1_LDOOUT_EN); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE2, + RG_DSI_LNT2_LDOOUT_EN, RG_DSI_LNT2_LDOOUT_EN); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE3, + RG_DSI_LNT3_LDOOUT_EN, RG_DSI_LNT3_LDOOUT_EN); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, + RG_DSI_MPPLL_PLL_EN, RG_DSI_MPPLL_PLL_EN); + + usleep_range(20, 100); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON1, + RG_DSI_MPPLL_SDM_SSC_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN, 0); + + return 0; +} + +static int mtk_mipi_tx_power_off(struct phy *phy) +{ + struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN, + RG_DSI_PAD_TIE_LOW_EN); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CLOCK_LANE, + RG_DSI_LNTC_LDOOUT_EN, 0); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE0, + RG_DSI_LNT0_LDOOUT_EN, 0); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE1, + RG_DSI_LNT1_LDOOUT_EN, 0); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE2, + RG_DSI_LNT2_LDOOUT_EN, 0); + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_DATA_LANE3, + RG_DSI_LNT3_LDOOUT_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_PWR, + RG_DSI_MPPLL_SDM_ISO_EN | RG_DSI_MPPLL_SDM_PWR_ON, + RG_DSI_MPPLL_SDM_ISO_EN); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_TOP_CON, RG_DSI_LNT_HS_BIAS_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_CON, + RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_BG_CON, + RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN, 0); + + mtk_mipi_tx_mask(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_DIV_MSK, 0); + + return 0; +} + +static struct phy_ops mtk_mipi_tx_ops = { + .power_on = mtk_mipi_tx_power_on, + .power_off = mtk_mipi_tx_power_off, + .owner = THIS_MODULE, +}; + +static int mtk_mipi_tx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_mipi_tx *mipi_tx; + struct resource *mem; + struct phy *phy; + struct phy_provider *phy_provider; + int ret; + + mipi_tx = devm_kzalloc(dev, sizeof(*mipi_tx), GFP_KERNEL); + if (!mipi_tx) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mipi_tx->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(mipi_tx->regs)) { + ret = PTR_ERR(mipi_tx->regs); + dev_err(dev, "Failed to get memory resource: %d\n", ret); + return ret; + } + + phy = devm_phy_create(dev, NULL, &mtk_mipi_tx_ops); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create MIPI D-PHY\n"); + return PTR_ERR(phy); + } + phy_set_drvdata(phy, mipi_tx); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int mtk_mipi_tx_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id mtk_mipi_tx_match[] = { + { .compatible = "mediatek,mt8173-mipi-tx", }, + {}, +}; + +struct platform_driver mtk_mipi_tx_driver = { + .probe = mtk_mipi_tx_probe, + .remove = mtk_mipi_tx_remove, + .driver = { + .name = "mediatek-mipi-tx", + .of_match_table = mtk_mipi_tx_match, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_mipi_tx.h b/drivers/gpu/drm/mediatek/mtk_mipi_tx.h new file mode 100644 index 0000000..35f4a10 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_mipi_tx.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 MediaTek Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MTK_MIPI_TX_H_ +#define _MTK_MIPI_TX_H + +struct phy; + +int mtk_mipi_tx_set_data_rate(struct phy *phy, unsigned int data_rate); + +#endif
From: Jie Qiu jie.qiu@mediatek.com
Add DPI connector/encoder to support HDMI output via the attached HDMI bridge.
Signed-off-by: Jie Qiu jie.qiu@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- drivers/gpu/drm/mediatek/Makefile | 3 +- drivers/gpu/drm/mediatek/mtk_dpi.c | 683 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_dpi.h | 80 ++++ drivers/gpu/drm/mediatek/mtk_dpi_regs.h | 228 +++++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 9 + drivers/gpu/drm/mediatek/mtk_drm_drv.h | 1 + 6 files changed, 1003 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi.h create mode 100644 drivers/gpu/drm/mediatek/mtk_dpi_regs.h
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile index 0d4aeeb..93380fe 100644 --- a/drivers/gpu/drm/mediatek/Makefile +++ b/drivers/gpu/drm/mediatek/Makefile @@ -6,7 +6,8 @@ mediatek-drm-y := mtk_drm_drv.o \ mtk_drm_gem.o \ mtk_drm_plane.o \ mtk_dsi.o \ - mtk_mipi_tx.o + mtk_mipi_tx.o \ + mtk_dpi.o
obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c new file mode 100644 index 0000000..43eaf33 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c @@ -0,0 +1,683 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <linux/kernel.h> +#include <linux/component.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/interrupt.h> +#include <linux/clk.h> + +#include "mtk_dpi.h" +#include "mtk_dpi_regs.h" + +enum mtk_dpi_polarity { + MTK_DPI_POLARITY_RISING, + MTK_DPI_POLARITY_FALLING, +}; + +struct mtk_dpi_polarities { + enum mtk_dpi_polarity de_pol; + enum mtk_dpi_polarity ck_pol; + enum mtk_dpi_polarity hsync_pol; + enum mtk_dpi_polarity vsync_pol; +}; + +struct mtk_dpi_sync_param { + u32 sync_width; + u32 front_porch; + u32 back_porch; + bool shift_half_line; +}; + +struct mtk_dpi_yc_limit { + u16 y_top; + u16 y_bottom; + u16 c_top; + u16 c_bottom; +}; + +static void mtk_dpi_mask(struct mtk_dpi *dpi, u32 offset, u32 val, u32 mask) +{ + u32 tmp = readl(dpi->regs + offset) & ~mask; + + tmp |= (val & mask); + writel(tmp, dpi->regs + offset); +} + +static void mtk_dpi_sw_reset(struct mtk_dpi *dpi, bool reset) +{ + mtk_dpi_mask(dpi, DPI_RET, reset ? RST : 0, RST); +} + +static void mtk_dpi_enable(struct mtk_dpi *dpi) +{ + mtk_dpi_mask(dpi, DPI_EN, EN, EN); +} + +static void mtk_dpi_disable(struct mtk_dpi *dpi) +{ + mtk_dpi_mask(dpi, DPI_EN, 0, EN); +} + +static void mtk_dpi_config_hsync(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_mask(dpi, DPI_TGEN_HWIDTH, + sync->sync_width << HPW, HPW_MASK); + mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, + sync->back_porch << HBP, HBP_MASK); + mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->front_porch << HFP, + HFP_MASK); +} + +static void mtk_dpi_config_vsync(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync, + u32 width_addr, u32 porch_addr) +{ + mtk_dpi_mask(dpi, width_addr, + sync->sync_width << VSYNC_WIDTH_SHIFT, + VSYNC_WIDTH_MASK); + mtk_dpi_mask(dpi, width_addr, + sync->shift_half_line << VSYNC_HALF_LINE_SHIFT, + VSYNC_HALF_LINE_MASK); + mtk_dpi_mask(dpi, porch_addr, + sync->back_porch << VSYNC_BACK_PORCH_SHIFT, + VSYNC_BACK_PORCH_MASK); + mtk_dpi_mask(dpi, porch_addr, + sync->front_porch << VSYNC_FRONT_PORCH_SHIFT, + VSYNC_FRONT_PORCH_MASK); +} + +static void mtk_dpi_config_vsync_lodd(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH, DPI_TGEN_VPORCH); +} + +static void mtk_dpi_config_vsync_leven(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_LEVEN, + DPI_TGEN_VPORCH_LEVEN); +} + +static void mtk_dpi_config_vsync_rodd(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_RODD, + DPI_TGEN_VPORCH_RODD); +} + +static void mtk_dpi_config_vsync_reven(struct mtk_dpi *dpi, + struct mtk_dpi_sync_param *sync) +{ + mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_REVEN, + DPI_TGEN_VPORCH_REVEN); +} + +static void mtk_dpi_config_pol(struct mtk_dpi *dpi, + struct mtk_dpi_polarities *dpi_pol) +{ + unsigned int pol; + + pol = (dpi_pol->ck_pol == MTK_DPI_POLARITY_RISING ? 0 : CK_POL) | + (dpi_pol->de_pol == MTK_DPI_POLARITY_RISING ? 0 : DE_POL) | + (dpi_pol->hsync_pol == MTK_DPI_POLARITY_RISING ? 0 : HSYNC_POL) | + (dpi_pol->vsync_pol == MTK_DPI_POLARITY_RISING ? 0 : VSYNC_POL); + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, pol, + CK_POL | DE_POL | HSYNC_POL | VSYNC_POL); +} + +static void mtk_dpi_config_3d(struct mtk_dpi *dpi, bool en_3d) +{ + mtk_dpi_mask(dpi, DPI_CON, en_3d ? TDFP_EN : 0, TDFP_EN); +} + +static void mtk_dpi_config_interface(struct mtk_dpi *dpi, bool inter) +{ + mtk_dpi_mask(dpi, DPI_CON, inter ? INTL_EN : 0, INTL_EN); +} + +static void mtk_dpi_config_fb_size(struct mtk_dpi *dpi, u32 width, u32 height) +{ + mtk_dpi_mask(dpi, DPI_SIZE, width << HSIZE, HSIZE_MASK); + mtk_dpi_mask(dpi, DPI_SIZE, height << VSIZE, VSIZE_MASK); +} + +static void mtk_dpi_config_channel_limit(struct mtk_dpi *dpi, + struct mtk_dpi_yc_limit *limit) +{ + mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_bottom << Y_LIMINT_BOT, + Y_LIMINT_BOT_MASK); + mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_top << Y_LIMINT_TOP, + Y_LIMINT_TOP_MASK); + mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_bottom << C_LIMIT_BOT, + C_LIMIT_BOT_MASK); + mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_top << C_LIMIT_TOP, + C_LIMIT_TOP_MASK); +} + +static void mtk_dpi_config_bit_num(struct mtk_dpi *dpi, + enum mtk_dpi_out_bit_num num) +{ + u32 val; + + switch (num) { + case MTK_DPI_OUT_BIT_NUM_8BITS: + val = OUT_BIT_8; + break; + case MTK_DPI_OUT_BIT_NUM_10BITS: + val = OUT_BIT_10; + break; + case MTK_DPI_OUT_BIT_NUM_12BITS: + val = OUT_BIT_12; + break; + case MTK_DPI_OUT_BIT_NUM_16BITS: + val = OUT_BIT_16; + break; + default: + val = OUT_BIT_8; + break; + } + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << OUT_BIT, + OUT_BIT_MASK); +} + +static void mtk_dpi_config_yc_map(struct mtk_dpi *dpi, + enum mtk_dpi_out_yc_map map) +{ + u32 val; + + switch (map) { + case MTK_DPI_OUT_YC_MAP_RGB: + val = YC_MAP_RGB; + break; + case MTK_DPI_OUT_YC_MAP_CYCY: + val = YC_MAP_CYCY; + break; + case MTK_DPI_OUT_YC_MAP_YCYC: + val = YC_MAP_YCYC; + break; + case MTK_DPI_OUT_YC_MAP_CY: + val = YC_MAP_CY; + break; + case MTK_DPI_OUT_YC_MAP_YC: + val = YC_MAP_YC; + break; + default: + val = YC_MAP_RGB; + break; + } + + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << YC_MAP, YC_MAP_MASK); +} + +static void mtk_dpi_config_channel_swap(struct mtk_dpi *dpi, + enum mtk_dpi_out_channel_swap swap) +{ + u32 val; + + switch (swap) { + case MTK_DPI_OUT_CHANNEL_SWAP_RGB: + val = SWAP_RGB; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_GBR: + val = SWAP_GBR; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_BRG: + val = SWAP_BRG; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_RBG: + val = SWAP_RBG; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_GRB: + val = SWAP_GRB; + break; + case MTK_DPI_OUT_CHANNEL_SWAP_BGR: + val = SWAP_BGR; + break; + default: + val = SWAP_RGB; + break; + } + + mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << CH_SWAP, CH_SWAP_MASK); +} + +static void mtk_dpi_config_yuv422_enable(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? YUV422_EN : 0, YUV422_EN); +} + +static void mtk_dpi_config_csc_enable(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? CSC_ENABLE : 0, CSC_ENABLE); +} + +static void mtk_dpi_config_swap_input(struct mtk_dpi *dpi, bool enable) +{ + mtk_dpi_mask(dpi, DPI_CON, enable ? IN_RB_SWAP : 0, IN_RB_SWAP); +} + +static void mtk_dpi_config_2n_h_fre(struct mtk_dpi *dpi) +{ + mtk_dpi_mask(dpi, DPI_H_FRE_CON, H_FRE_2N, H_FRE_2N); +} + +static void mtk_dpi_config_color_format(struct mtk_dpi *dpi, + enum mtk_dpi_out_color_format format) +{ + if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_444) || + (format == MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL)) { + mtk_dpi_config_yuv422_enable(dpi, false); + mtk_dpi_config_csc_enable(dpi, true); + mtk_dpi_config_swap_input(dpi, false); + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_BGR); + } else if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_422) || + (format == MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL)) { + mtk_dpi_config_yuv422_enable(dpi, true); + mtk_dpi_config_csc_enable(dpi, true); + mtk_dpi_config_swap_input(dpi, true); + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB); + } else { + mtk_dpi_config_yuv422_enable(dpi, false); + mtk_dpi_config_csc_enable(dpi, false); + mtk_dpi_config_swap_input(dpi, false); + mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB); + } +} + +static int mtk_dpi_power_off(struct mtk_dpi *dpi) +{ + mtk_dpi_disable(dpi); + clk_disable_unprepare(dpi->pixel_clk); + + return 0; +} + +static int mtk_dpi_power_on(struct mtk_dpi *dpi) +{ + int ret; + + ret = clk_prepare_enable(dpi->pixel_clk); + if (ret) { + dev_err(dpi->dev, "Failed to enable pixel clock: %d\n", ret); + return ret; + } + mtk_dpi_enable(dpi); + + return 0; +} + +int mtk_dpi_set_display_mode(struct mtk_dpi *dpi, struct drm_display_mode *mode) +{ + struct mtk_dpi_yc_limit limit; + struct mtk_dpi_polarities dpi_pol; + struct mtk_dpi_sync_param hsync; + struct mtk_dpi_sync_param vsync_lodd = { 0 }; + struct mtk_dpi_sync_param vsync_leven = { 0 }; + struct mtk_dpi_sync_param vsync_rodd = { 0 }; + struct mtk_dpi_sync_param vsync_reven = { 0 }; + unsigned long pix_rate; + unsigned long pll_rate; + unsigned int factor; + + if (!dpi) { + dev_err(dpi->dev, "invalid argument\n"); + return -EINVAL; + } + + pix_rate = 1000UL * mode->clock; + if (mode->clock <= 74000) + factor = 8 * 3; + else + factor = 4 * 3; + pll_rate = pix_rate * factor; + + dev_dbg(dpi->dev, "Want PLL %lu Hz, pixel clock %lu Hz\n", + pll_rate, pix_rate); + + clk_set_rate(dpi->tvd_clk, pll_rate); + pll_rate = clk_get_rate(dpi->tvd_clk); + + pix_rate = pll_rate / factor; + clk_set_rate(dpi->pixel_clk, pix_rate); + pix_rate = clk_get_rate(dpi->pixel_clk); + + dev_dbg(dpi->dev, "Got PLL %lu Hz, pixel clock %lu Hz\n", + pll_rate, pix_rate); + + limit.c_bottom = 0x0010; + limit.c_top = 0x0FE0; + limit.y_bottom = 0x0010; + limit.y_top = 0x0FE0; + + dpi_pol.ck_pol = MTK_DPI_POLARITY_FALLING; + dpi_pol.de_pol = MTK_DPI_POLARITY_RISING; + dpi_pol.hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ? + MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING; + dpi_pol.vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ? + MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING; + + hsync.sync_width = mode->hsync_end - mode->hsync_start; + hsync.back_porch = mode->htotal - mode->hsync_end; + hsync.front_porch = mode->hsync_start - mode->hdisplay; + hsync.shift_half_line = false; + + vsync_lodd.sync_width = mode->vsync_end - mode->vsync_start; + vsync_lodd.back_porch = mode->vtotal - mode->vsync_end; + vsync_lodd.front_porch = mode->vsync_start - mode->vdisplay; + vsync_lodd.shift_half_line = false; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE && + mode->flags & DRM_MODE_FLAG_3D_MASK) { + vsync_leven = vsync_lodd; + vsync_rodd = vsync_lodd; + vsync_reven = vsync_lodd; + vsync_leven.shift_half_line = true; + vsync_reven.shift_half_line = true; + } else if (mode->flags & DRM_MODE_FLAG_INTERLACE && + !(mode->flags & DRM_MODE_FLAG_3D_MASK)) { + vsync_leven = vsync_lodd; + vsync_leven.shift_half_line = true; + } else if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) && + mode->flags & DRM_MODE_FLAG_3D_MASK) { + vsync_rodd = vsync_lodd; + } + mtk_dpi_sw_reset(dpi, true); + mtk_dpi_config_pol(dpi, &dpi_pol); + + mtk_dpi_config_hsync(dpi, &hsync); + mtk_dpi_config_vsync_lodd(dpi, &vsync_lodd); + mtk_dpi_config_vsync_rodd(dpi, &vsync_rodd); + mtk_dpi_config_vsync_leven(dpi, &vsync_leven); + mtk_dpi_config_vsync_reven(dpi, &vsync_reven); + + mtk_dpi_config_3d(dpi, !!(mode->flags & DRM_MODE_FLAG_3D_MASK)); + mtk_dpi_config_interface(dpi, !!(mode->flags & + DRM_MODE_FLAG_INTERLACE)); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay / 2); + else + mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay); + + mtk_dpi_config_channel_limit(dpi, &limit); + mtk_dpi_config_bit_num(dpi, dpi->bit_num); + mtk_dpi_config_channel_swap(dpi, dpi->channel_swap); + mtk_dpi_config_yc_map(dpi, dpi->yc_map); + mtk_dpi_config_color_format(dpi, dpi->color_format); + mtk_dpi_config_2n_h_fre(dpi); + mtk_dpi_sw_reset(dpi, false); + + return 0; +} + +static void mtk_dpi_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs mtk_dpi_encoder_funcs = { + .destroy = mtk_dpi_encoder_destroy, +}; + +static bool mtk_dpi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void mtk_dpi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder); + + drm_mode_copy(&dpi->mode, adjusted_mode); +} + +static void mtk_dpi_encoder_disable(struct drm_encoder *encoder) +{ + struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder); + + mtk_dpi_power_off(dpi); +} + +static void mtk_dpi_encoder_enable(struct drm_encoder *encoder) +{ + struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder); + + mtk_dpi_power_on(dpi); + mtk_dpi_set_display_mode(dpi, &dpi->mode); +} + +static int mtk_dpi_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static const struct drm_encoder_helper_funcs mtk_dpi_encoder_helper_funcs = { + .mode_fixup = mtk_dpi_encoder_mode_fixup, + .mode_set = mtk_dpi_encoder_mode_set, + .disable = mtk_dpi_encoder_disable, + .enable = mtk_dpi_encoder_enable, + .atomic_check = mtk_dpi_atomic_check, +}; + +static int mtk_dpi_bind(struct device *dev, struct device *master, void *data) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int ret; + + ret = drm_encoder_init(drm_dev, &dpi->encoder, &mtk_dpi_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret) { + dev_err(dev, "Failed to initialize decoder: %d\n", ret); + return ret; + } + drm_encoder_helper_add(&dpi->encoder, &mtk_dpi_encoder_helper_funcs); + + /* Currently DPI0 is fixed to be driven by OVL1 */ + dpi->encoder.possible_crtcs = BIT(1); + + dpi->encoder.bridge->encoder = &dpi->encoder; + ret = drm_bridge_attach(dpi->encoder.dev, dpi->encoder.bridge); + if (ret) { + dev_err(dev, "Failed to attach bridge: %d\n", ret); + goto err_cleanup; + } + + dpi->bit_num = MTK_DPI_OUT_BIT_NUM_8BITS; + dpi->channel_swap = MTK_DPI_OUT_CHANNEL_SWAP_RGB; + dpi->yc_map = MTK_DPI_OUT_YC_MAP_RGB; + dpi->color_format = MTK_DPI_COLOR_FORMAT_RGB; + + return 0; + +err_cleanup: + drm_encoder_cleanup(&dpi->encoder); + return ret; +} + +static void mtk_dpi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + + drm_encoder_cleanup(&dpi->encoder); +} + +static const struct component_ops mtk_dpi_component_ops = { + .bind = mtk_dpi_bind, + .unbind = mtk_dpi_unbind, +}; + +static int mtk_dpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_dpi *dpi; + struct resource *mem; + struct device_node *ep, *bridge_node = NULL; + int ret; + + dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL); + if (!dpi) + return -ENOMEM; + + dpi->dev = dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dpi->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(dpi->regs)) { + ret = PTR_ERR(dpi->regs); + dev_err(dev, "Failed to ioremap mem resource: %d\n", ret); + return ret; + } + + dpi->engine_clk = devm_clk_get(dev, "engine"); + if (IS_ERR(dpi->engine_clk)) { + ret = PTR_ERR(dpi->engine_clk); + dev_err(dev, "Failed to get engine clock: %d\n", ret); + return ret; + } + + dpi->pixel_clk = devm_clk_get(dev, "pixel"); + if (IS_ERR(dpi->pixel_clk)) { + ret = PTR_ERR(dpi->pixel_clk); + dev_err(dev, "Failed to get pixel clock: %d\n", ret); + return ret; + } + + dpi->tvd_clk = devm_clk_get(dev, "pll"); + if (IS_ERR(dpi->tvd_clk)) { + ret = PTR_ERR(dpi->tvd_clk); + dev_err(dev, "Failed to get tvdpll clock: %d\n", ret); + return ret; + } + + dpi->irq = platform_get_irq(pdev, 0); + if (dpi->irq <= 0) { + dev_err(dev, "Failed to get irq: %d\n", dpi->irq); + return -EINVAL; + } + + ep = of_graph_get_next_endpoint(dev->of_node, NULL); + if (ep) { + bridge_node = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + } + if (!bridge_node) { + dev_err(dev, "Failed to find bridge node: %d\n", ret); + return ret; + } + + dev_info(dev, "Found bridge node: %s\n", bridge_node->full_name); + + dpi->encoder.bridge = of_drm_find_bridge(bridge_node); + of_node_put(bridge_node); + if (!dpi->encoder.bridge) + return -EPROBE_DEFER; + + platform_set_drvdata(pdev, dpi); + + clk_prepare_enable(dpi->engine_clk); + + ret = component_add(dev, &mtk_dpi_component_ops); + if (ret) { + dev_err(dev, "Failed to add component: %d\n", ret); + return ret; + } + + return 0; +} + +static int mtk_dpi_remove(struct platform_device *pdev) +{ + struct mtk_dpi *dpi = platform_get_drvdata(pdev); + + clk_disable_unprepare(dpi->engine_clk); + + component_del(&pdev->dev, &mtk_dpi_component_ops); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_dpi_suspend(struct device *dev) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + int ret; + + if (IS_ERR(dpi)) { + dev_info(dev, "dpi suspend failed!\n"); + return PTR_ERR(dpi); + } + + ret = mtk_dpi_power_off(dpi); + if (ret) { + dev_info(dev, "dpi suspend failed!\n"); + return ret; + } + + dev_info(dev, "dpi suspend success!\n"); + + return 0; +} + +static int mtk_dpi_resume(struct device *dev) +{ + struct mtk_dpi *dpi = dev_get_drvdata(dev); + int ret; + + if (IS_ERR(dpi)) { + dev_err(dev, "dpi resume failed!\n"); + return PTR_ERR(dpi); + } + + ret = mtk_dpi_power_on(dpi); + if (ret) { + dev_err(dev, "dpi resume failed!\n"); + return ret; + } + + dev_info(dev, "dpi resume success!\n"); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mtk_dpi_pm_ops, mtk_dpi_suspend, mtk_dpi_resume); + +static const struct of_device_id mtk_dpi_of_ids[] = { + { .compatible = "mediatek,mt8173-dpi", }, + {} +}; + +struct platform_driver mtk_dpi_driver = { + .probe = mtk_dpi_probe, + .remove = mtk_dpi_remove, + .driver = { + .name = "mediatek-dpi", + .of_match_table = mtk_dpi_of_ids, + .pm = &mtk_dpi_pm_ops, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.h b/drivers/gpu/drm/mediatek/mtk_dpi.h new file mode 100644 index 0000000..b83af44 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_DPI_H +#define _MTK_DPI_H + +#include <linux/types.h> +#include <drm/drm_crtc.h> + +struct device; +struct clk; + +enum mtk_dpi_out_bit_num { + MTK_DPI_OUT_BIT_NUM_8BITS, + MTK_DPI_OUT_BIT_NUM_10BITS, + MTK_DPI_OUT_BIT_NUM_12BITS, + MTK_DPI_OUT_BIT_NUM_16BITS +}; + +enum mtk_dpi_out_yc_map { + MTK_DPI_OUT_YC_MAP_RGB, + MTK_DPI_OUT_YC_MAP_CYCY, + MTK_DPI_OUT_YC_MAP_YCYC, + MTK_DPI_OUT_YC_MAP_CY, + MTK_DPI_OUT_YC_MAP_YC +}; + +enum mtk_dpi_out_channel_swap { + MTK_DPI_OUT_CHANNEL_SWAP_RGB, + MTK_DPI_OUT_CHANNEL_SWAP_GBR, + MTK_DPI_OUT_CHANNEL_SWAP_BRG, + MTK_DPI_OUT_CHANNEL_SWAP_RBG, + MTK_DPI_OUT_CHANNEL_SWAP_GRB, + MTK_DPI_OUT_CHANNEL_SWAP_BGR +}; + +enum mtk_dpi_out_color_format { + MTK_DPI_COLOR_FORMAT_RGB, + MTK_DPI_COLOR_FORMAT_RGB_FULL, + MTK_DPI_COLOR_FORMAT_YCBCR_444, + MTK_DPI_COLOR_FORMAT_YCBCR_422, + MTK_DPI_COLOR_FORMAT_XV_YCC, + MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL, + MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL +}; + +struct mtk_dpi { + struct drm_encoder encoder; + void __iomem *regs; + struct device *dev; + struct clk *engine_clk; + struct clk *pixel_clk; + struct clk *tvd_clk; + int irq; + struct drm_display_mode mode; + enum mtk_dpi_out_color_format color_format; + enum mtk_dpi_out_yc_map yc_map; + enum mtk_dpi_out_bit_num bit_num; + enum mtk_dpi_out_channel_swap channel_swap; +}; + +static inline struct mtk_dpi *mtk_dpi_from_encoder(struct drm_encoder *e) +{ + return container_of(e, struct mtk_dpi, encoder); +} + +int mtk_dpi_set_display_mode(struct mtk_dpi *dpi, + struct drm_display_mode *mode); + +#endif /* _MTK_DPI_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_dpi_regs.h b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h new file mode 100644 index 0000000..4b6ad47 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_dpi_regs.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MTK_DPI_REGS_H +#define __MTK_DPI_REGS_H + +#define DPI_EN 0x00 +#define EN BIT(0) + +#define DPI_RET 0x04 +#define RST BIT(0) + +#define DPI_INTEN 0x08 +#define INT_VSYNC_EN BIT(0) +#define INT_VDE_EN BIT(1) +#define INT_UNDERFLOW_EN BIT(2) + +#define DPI_INTSTA 0x0C +#define INT_VSYNC_STA BIT(0) +#define INT_VDE_STA BIT(1) +#define INT_UNDERFLOW_STA BIT(2) + +#define DPI_CON 0x10 +#define BG_ENABLE BIT(0) +#define IN_RB_SWAP BIT(1) +#define INTL_EN BIT(2) +#define TDFP_EN BIT(3) +#define CLPF_EN BIT(4) +#define YUV422_EN BIT(5) +#define CSC_ENABLE BIT(6) +#define R601_SEL BIT(7) +#define EMBSYNC_EN BIT(8) +#define VS_LODD_EN BIT(16) +#define VS_LEVEN_EN BIT(17) +#define VS_RODD_EN BIT(18) +#define VS_REVEN BIT(19) +#define FAKE_DE_LODD BIT(20) +#define FAKE_DE_LEVEN BIT(21) +#define FAKE_DE_RODD BIT(22) +#define FAKE_DE_REVEN BIT(23) + +#define DPI_OUTPUT_SETTING 0x14 +#define CH_SWAP 0 +#define CH_SWAP_MASK (0x7 << 0) +#define SWAP_RGB 0x00 +#define SWAP_GBR 0x01 +#define SWAP_BRG 0x02 +#define SWAP_RBG 0x03 +#define SWAP_GRB 0x04 +#define SWAP_BGR 0x05 +#define BIT_SWAP BIT(3) +#define B_MASK BIT(4) +#define G_MASK BIT(5) +#define R_MASK BIT(6) +#define DE_MASK BIT(8) +#define HS_MASK BIT(9) +#define VS_MASK BIT(10) +#define DE_POL BIT(12) +#define HSYNC_POL BIT(13) +#define VSYNC_POL BIT(14) +#define CK_POL BIT(15) +#define OEN_OFF BIT(16) +#define EDGE_SEL BIT(17) +#define OUT_BIT 18 +#define OUT_BIT_MASK (0x3 << 18) +#define OUT_BIT_8 0x00 +#define OUT_BIT_10 0x01 +#define OUT_BIT_12 0x02 +#define OUT_BIT_16 0x03 +#define YC_MAP 20 +#define YC_MAP_MASK (0x7 << 20) +#define YC_MAP_RGB 0x00 +#define YC_MAP_CYCY 0x04 +#define YC_MAP_YCYC 0x05 +#define YC_MAP_CY 0x06 +#define YC_MAP_YC 0x07 + +#define DPI_SIZE 0x18 +#define HSIZE 0 +#define HSIZE_MASK (0x1FFF << 0) +#define VSIZE 16 +#define VSIZE_MASK (0x1FFF << 16) + +#define DPI_DDR_SETTING 0x1C +#define DDR_EN BIT(0) +#define DDDR_SEL BIT(1) +#define DDR_4PHASE BIT(2) +#define DDR_WIDTH (0x3 << 4) +#define DDR_PAD_MODE (0x1 << 8) + +#define DPI_TGEN_HWIDTH 0x20 +#define HPW 0 +#define HPW_MASK (0xFFF << 0) + +#define DPI_TGEN_HPORCH 0x24 +#define HBP 0 +#define HBP_MASK (0xFFF << 0) +#define HFP 16 +#define HFP_MASK (0xFFF << 16) + +#define DPI_TGEN_VWIDTH 0x28 +#define DPI_TGEN_VPORCH 0x2C + +#define VSYNC_WIDTH_SHIFT 0 +#define VSYNC_WIDTH_MASK (0xFFF << 0) +#define VSYNC_HALF_LINE_SHIFT 16 +#define VSYNC_HALF_LINE_MASK BIT(16) +#define VSYNC_BACK_PORCH_SHIFT 0 +#define VSYNC_BACK_PORCH_MASK (0xFFF << 0) +#define VSYNC_FRONT_PORCH_SHIFT 16 +#define VSYNC_FRONT_PORCH_MASK (0xFFF << 16) + +#define DPI_BG_HCNTL 0x30 +#define BG_RIGHT (0x1FFF << 0) +#define BG_LEFT (0x1FFF << 16) + +#define DPI_BG_VCNTL 0x34 +#define BG_BOT (0x1FFF << 0) +#define BG_TOP (0x1FFF << 16) + +#define DPI_BG_COLOR 0x38 +#define BG_B (0xF << 0) +#define BG_G (0xF << 8) +#define BG_R (0xF << 16) + +#define DPI_FIFO_CTL 0x3C +#define FIFO_VALID_SET (0x1F << 0) +#define FIFO_RST_SEL (0x1 << 8) + +#define DPI_STATUS 0x40 +#define VCOUNTER (0x1FFF << 0) +#define DPI_BUSY BIT(16) +#define OUTEN BIT(17) +#define FIELD BIT(20) +#define TDLR BIT(21) + +#define DPI_TMODE 0x44 +#define DPI_OEN_ON BIT(0) + +#define DPI_CHECKSUM 0x48 +#define DPI_CHECKSUM_MASK (0xFFFFFF << 0) +#define DPI_CHECKSUM_READY BIT(30) +#define DPI_CHECKSUM_EN BIT(31) + +#define DPI_DUMMY 0x50 +#define DPI_DUMMY_MASK (0xFFFFFFFF << 0) + +#define DPI_TGEN_VWIDTH_LEVEN 0x68 +#define DPI_TGEN_VPORCH_LEVEN 0x6C +#define DPI_TGEN_VWIDTH_RODD 0x70 +#define DPI_TGEN_VPORCH_RODD 0x74 +#define DPI_TGEN_VWIDTH_REVEN 0x78 +#define DPI_TGEN_VPORCH_REVEN 0x7C + +#define DPI_ESAV_VTIMING_LODD 0x80 +#define ESAV_VOFST_LODD (0xFFF << 0) +#define ESAV_VWID_LODD (0xFFF << 16) + +#define DPI_ESAV_VTIMING_LEVEN 0x84 +#define ESAV_VOFST_LEVEN (0xFFF << 0) +#define ESAV_VWID_LEVEN (0xFFF << 16) + +#define DPI_ESAV_VTIMING_RODD 0x88 +#define ESAV_VOFST_RODD (0xFFF << 0) +#define ESAV_VWID_RODD (0xFFF << 16) + +#define DPI_ESAV_VTIMING_REVEN 0x8C +#define ESAV_VOFST_REVEN (0xFFF << 0) +#define ESAV_VWID_REVEN (0xFFF << 16) + +#define DPI_ESAV_FTIMING 0x90 +#define ESAV_FOFST_ODD (0xFFF << 0) +#define ESAV_FOFST_EVEN (0xFFF << 16) + +#define DPI_CLPF_SETTING 0x94 +#define CLPF_TYPE (0x3 << 0) +#define ROUND_EN BIT(4) + +#define DPI_Y_LIMIT 0x98 +#define Y_LIMINT_BOT 0 +#define Y_LIMINT_BOT_MASK (0xFFF << 0) +#define Y_LIMINT_TOP 16 +#define Y_LIMINT_TOP_MASK (0xFFF << 16) + +#define DPI_C_LIMIT 0x9C +#define C_LIMIT_BOT 0 +#define C_LIMIT_BOT_MASK (0xFFF << 0) +#define C_LIMIT_TOP 16 +#define C_LIMIT_TOP_MASK (0xFFF << 16) + +#define DPI_YUV422_SETTING 0xA0 +#define UV_SWAP BIT(0) +#define CR_DELSEL BIT(4) +#define CB_DELSEL BIT(5) +#define Y_DELSEL BIT(6) +#define DE_DELSEL BIT(7) + +#define DPI_EMBSYNC_SETTING 0xA4 +#define EMBSYNC_R_CR_EN BIT(0) +#define EMPSYNC_G_Y_EN BIT(1) +#define EMPSYNC_B_CB_EN BIT(2) +#define ESAV_F_INV BIT(4) +#define ESAV_V_INV BIT(5) +#define ESAV_H_INV BIT(6) +#define ESAV_CODE_MAN BIT(8) +#define VS_OUT_SEL (0x7 << 12) + +#define DPI_ESAV_CODE_SET0 0xA8 +#define ESAV_CODE0 (0xFFF << 0) +#define ESAV_CODE1 (0xFFF << 16) + +#define DPI_ESAV_CODE_SET1 0xAC +#define ESAV_CODE2 (0xFFF << 0) +#define ESAV_CODE3_MSB BIT(16) + +#define DPI_H_FRE_CON 0xE0 +#define H_FRE_2N BIT(25) +#endif /* __MTK_DPI_REGS_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index cbaf208..138262c 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -563,8 +563,16 @@ static int __init mtk_drm_init(void) goto dsi_err; }
+ ret = platform_driver_register(&mtk_dpi_driver); + if (ret < 0) { + pr_err("Failed to register DPI platform driver: %d\n!", ret); + goto mipi_tx_err; + } + return 0;
+mipi_tx_err: + platform_driver_unregister(&mtk_mipi_tx_driver); dsi_err: platform_driver_unregister(&mtk_dsi_driver); disp_ovl_err: @@ -577,6 +585,7 @@ err:
static void __exit mtk_drm_exit(void) { + platform_driver_unregister(&mtk_dpi_driver); platform_driver_unregister(&mtk_mipi_tx_driver); platform_driver_unregister(&mtk_dsi_driver); platform_driver_unregister(&mtk_disp_ovl_driver); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.h b/drivers/gpu/drm/mediatek/mtk_drm_drv.h index 9c81abe..04161ad 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.h +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -59,5 +59,6 @@ struct mtk_drm_private { extern struct platform_driver mtk_disp_ovl_driver; extern struct platform_driver mtk_dsi_driver; extern struct platform_driver mtk_mipi_tx_driver; +extern struct platform_driver mtk_dpi_driver;
#endif /* MTK_DRM_DRV_H */
Add the device tree binding documentation for Mediatek HDMI, HDMI PHY and HDMI DDC devices.
Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- Changes since v4: - Remove mediatek,cec and ddc-i2c-bus from hdmi node - Make output port required - Add mediatek, prefix to phy node current bias properties --- .../bindings/display/mediatek/mediatek,hdmi.txt | 142 +++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt new file mode 100644 index 0000000..e3dde29 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt @@ -0,0 +1,142 @@ +Mediatek HDMI Encoder +===================== + +The Mediatek HDMI encoder can generate HDMI 1.4a or MHL 2.0 signals from +its parallel input. + +Required properties: +- compatible: Should be "mediatek,<chip>-hdmi". +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clocks + See Documentation/devicetree/bindings/clock/clock-bindings.txt for details. +- clock-names: must contain "pixel", "pll", "bclk", and "spdif". +- phys: phandle link to the HDMI PHY node. + See Documentation/devicetree/bindings/phy/phy-bindings.txt for details. +- phy-names: must contain "hdmi" +- mediatek,syscon-hdmi: phandle link and register offset to the system + configuration registers. For mt8173 this must be offset 0x900 into the + MMSYS_CONFIG region: <&mmsys 0x900>. +- ports: A node containing input and output port nodes with endpoint + definitions as documented in Documentation/devicetree/bindings/graph.txt. +- port@0: The input port in the ports node should be connected to a DPI output + port. +- port@1: The output port in the ports node should be connected to the input + port of a connector node that contains a ddc-i2c-bus property, or to the + input port of an attached bridge chip, such as a SlimPort transmitter. + +HDMI CEC +======== + +The HDMI CEC controller handles hotplug detection and CEC communication. + +Required properties: +- compatible: Should be "mediatek,<chip>-cec" +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clock + +HDMI DDC +======== + +The HDMI DDC i2c controller is used to interface with the HDMI DDC pins. +The Mediatek's I2C controller is used to interface with I2C devices. + +Required properties: +- compatible: Should be "mediatek,<chip>-hdmi-ddc" +- reg: Physical base address and length of the controller's registers +- clocks: device clock +- clock-names: Should be "ddc-i2c". + +HDMI PHY +======== + +The HDMI PHY serializes the HDMI encoder's three channel 10-bit parallel +output and drives the HDMI pads. + +Required properties: +- compatible: "mediatek,<chip>-hdmi-phy" +- reg: Physical base address and length of the module's registers +- clocks: PLL reference clock +- clock-names: must contain "pll_ref" +- #phy-cells: must be <0>. + +Optional properties: +- mediatek,ibias: TX DRV bias current for <1.65Gbps, defaults to 0xa +- mediatek,ibias_up: TX DRV bias current for >1.65Gbps, defaults to 0x1c + +Example: + +cec: cec@10013000 { + compatible = "mediatek,mt8173-cec"; + reg = <0 0x10013000 0 0xbc>; + interrupts = <GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>; + clocks = <&infracfg CLK_INFRA_CEC>; +}; + +hdmi_phy: hdmi-phy@10209100 { + compatible = "mediatek,mt8173-hdmi-phy"; + reg = <0 0x10209100 0 0x24>; + clocks = <&apmixedsys CLK_APMIXED_HDMI_REF>; + clock-names = "pll_ref"; + mediatek,ibias = <0xa>; + mediatek,ibias_up = <0x1c>; + #phy-cells = <0>; +}; + +hdmi_ddc0: i2c@11012000 { + compatible = "mediatek,mt8173-hdmi-ddc"; + reg = <0 0x11012000 0 0x1c>; + interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>; + clocks = <&pericfg CLK_PERI_I2C5>; + clock-names = "ddc-i2c"; +}; + +hdmi0: hdmi@14025000 { + compatible = "mediatek,mt8173-hdmi"; + reg = <0 0x14025000 0 0x400>; + interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_HDMI_PIXEL>, + <&mmsys CLK_MM_HDMI_PLLCK>, + <&mmsys CLK_MM_HDMI_AUDIO>, + <&mmsys CLK_MM_HDMI_SPDIF>; + clock-names = "pixel", "pll", "bclk", "spdif"; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_pin>; + phys = <&hdmi_phy>; + phy-names = "hdmi"; + mediatek,syscon-hdmi = <&mmsys 0x900>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hdmi0_in: endpoint { + remote-endpoint = <&dpi0_out>; + }; + }; + + port@1 { + reg = <1>; + + hdmi0_out: endpoint { + remote-endpoint = <&hdmi_con_in>; + }; + }; + }; +}; + +connector { + compatible = "hdmi-connector"; + type = "a"; + ddc-i2c-bus = <&hdmiddc0>; + + port { + hdmi_con_in: endpoint { + remote-endpoint = <&hdmi0_out>; + }; + }; +};
On Wed, Nov 04, 2015 at 12:45:02PM +0100, Philipp Zabel wrote:
Add the device tree binding documentation for Mediatek HDMI, HDMI PHY and HDMI DDC devices.
Signed-off-by: Philipp Zabel p.zabel@pengutronix.de
Acked-by: Rob Herring robh@kernel.org
Changes since v4:
- Remove mediatek,cec and ddc-i2c-bus from hdmi node
- Make output port required
- Add mediatek, prefix to phy node current bias properties
.../bindings/display/mediatek/mediatek,hdmi.txt | 142 +++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt
diff --git a/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt b/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt new file mode 100644 index 0000000..e3dde29 --- /dev/null +++ b/Documentation/devicetree/bindings/display/mediatek/mediatek,hdmi.txt @@ -0,0 +1,142 @@ +Mediatek HDMI Encoder +=====================
+The Mediatek HDMI encoder can generate HDMI 1.4a or MHL 2.0 signals from +its parallel input.
+Required properties: +- compatible: Should be "mediatek,<chip>-hdmi". +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clocks
- See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "pixel", "pll", "bclk", and "spdif". +- phys: phandle link to the HDMI PHY node.
- See Documentation/devicetree/bindings/phy/phy-bindings.txt for details.
+- phy-names: must contain "hdmi" +- mediatek,syscon-hdmi: phandle link and register offset to the system
- configuration registers. For mt8173 this must be offset 0x900 into the
- MMSYS_CONFIG region: <&mmsys 0x900>.
+- ports: A node containing input and output port nodes with endpoint
- definitions as documented in Documentation/devicetree/bindings/graph.txt.
+- port@0: The input port in the ports node should be connected to a DPI output
- port.
+- port@1: The output port in the ports node should be connected to the input
- port of a connector node that contains a ddc-i2c-bus property, or to the
- input port of an attached bridge chip, such as a SlimPort transmitter.
+HDMI CEC +========
+The HDMI CEC controller handles hotplug detection and CEC communication.
+Required properties: +- compatible: Should be "mediatek,<chip>-cec" +- reg: Physical base address and length of the controller's registers +- interrupts: The interrupt signal from the function block. +- clocks: device clock
+HDMI DDC +========
+The HDMI DDC i2c controller is used to interface with the HDMI DDC pins. +The Mediatek's I2C controller is used to interface with I2C devices.
+Required properties: +- compatible: Should be "mediatek,<chip>-hdmi-ddc" +- reg: Physical base address and length of the controller's registers +- clocks: device clock +- clock-names: Should be "ddc-i2c".
+HDMI PHY +========
+The HDMI PHY serializes the HDMI encoder's three channel 10-bit parallel +output and drives the HDMI pads.
+Required properties: +- compatible: "mediatek,<chip>-hdmi-phy" +- reg: Physical base address and length of the module's registers +- clocks: PLL reference clock +- clock-names: must contain "pll_ref" +- #phy-cells: must be <0>.
+Optional properties: +- mediatek,ibias: TX DRV bias current for <1.65Gbps, defaults to 0xa +- mediatek,ibias_up: TX DRV bias current for >1.65Gbps, defaults to 0x1c
+Example:
+cec: cec@10013000 {
- compatible = "mediatek,mt8173-cec";
- reg = <0 0x10013000 0 0xbc>;
- interrupts = <GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&infracfg CLK_INFRA_CEC>;
+};
+hdmi_phy: hdmi-phy@10209100 {
- compatible = "mediatek,mt8173-hdmi-phy";
- reg = <0 0x10209100 0 0x24>;
- clocks = <&apmixedsys CLK_APMIXED_HDMI_REF>;
- clock-names = "pll_ref";
- mediatek,ibias = <0xa>;
- mediatek,ibias_up = <0x1c>;
- #phy-cells = <0>;
+};
+hdmi_ddc0: i2c@11012000 {
- compatible = "mediatek,mt8173-hdmi-ddc";
- reg = <0 0x11012000 0 0x1c>;
- interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&pericfg CLK_PERI_I2C5>;
- clock-names = "ddc-i2c";
+};
+hdmi0: hdmi@14025000 {
- compatible = "mediatek,mt8173-hdmi";
- reg = <0 0x14025000 0 0x400>;
- interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_LOW>;
- clocks = <&mmsys CLK_MM_HDMI_PIXEL>,
<&mmsys CLK_MM_HDMI_PLLCK>,
<&mmsys CLK_MM_HDMI_AUDIO>,
<&mmsys CLK_MM_HDMI_SPDIF>;
- clock-names = "pixel", "pll", "bclk", "spdif";
- pinctrl-names = "default";
- pinctrl-0 = <&hdmi_pin>;
- phys = <&hdmi_phy>;
- phy-names = "hdmi";
- mediatek,syscon-hdmi = <&mmsys 0x900>;
- ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
hdmi0_in: endpoint {
remote-endpoint = <&dpi0_out>;
};
};
port@1 {
reg = <1>;
hdmi0_out: endpoint {
remote-endpoint = <&hdmi_con_in>;
};
};
- };
+};
+connector {
- compatible = "hdmi-connector";
- type = "a";
- ddc-i2c-bus = <&hdmiddc0>;
- port {
hdmi_con_in: endpoint {
remote-endpoint = <&hdmi0_out>;
};
- };
+};
2.6.1
From: Jie Qiu jie.qiu@mediatek.com
This patch adds drivers for the HDMI bridge connected to the DPI0 display subsystem function block, for the HDMI DDC block, and for the HDMI PHY to support HDMI output.
Signed-off-by: Jie Qiu jie.qiu@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- Changes since v4: - Always set mode during bridge enable - Move HDMI PHY registers into a separate file - Make port@1 required, use of_graph_get_port_by_id - Move ddc-i2c-bus property into the output connector node - Enable the clock later in CEC probe --- drivers/gpu/drm/mediatek/Kconfig | 6 + drivers/gpu/drm/mediatek/Makefile | 8 + drivers/gpu/drm/mediatek/mtk_cec.c | 251 +++++++++ drivers/gpu/drm/mediatek/mtk_cec.h | 25 + drivers/gpu/drm/mediatek/mtk_drm_drv.c | 1 + drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c | 642 ++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi.c | 515 ++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi.h | 118 +++++ drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c | 362 +++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_hw.c | 762 +++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_hw.h | 76 +++ drivers/gpu/drm/mediatek/mtk_hdmi_phy.c | 340 ++++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_phy.h | 20 + drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h | 118 +++++ drivers/gpu/drm/mediatek/mtk_hdmi_regs.h | 221 ++++++++ include/drm/mediatek/mtk_hdmi_audio.h | 150 ++++++ 16 files changed, 3615 insertions(+) create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.c create mode 100644 drivers/gpu/drm/mediatek/mtk_cec.h create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_hw.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_hw.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy.c create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h create mode 100644 drivers/gpu/drm/mediatek/mtk_hdmi_regs.h create mode 100644 include/drm/mediatek/mtk_hdmi_audio.h
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig index 5343cf1..85af51c 100644 --- a/drivers/gpu/drm/mediatek/Kconfig +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -14,3 +14,9 @@ config DRM_MEDIATEK This driver provides kernel mode setting and buffer management to userspace.
+config DRM_MEDIATEK_HDMI + tristate "DRM HDMI Support for Mediatek SoCs" + depends on DRM_MEDIATEK + select GENERIC_PHY + help + DRM/KMS HDMI driver for Mediatek SoCs diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile index 93380fe..e2a5c5c 100644 --- a/drivers/gpu/drm/mediatek/Makefile +++ b/drivers/gpu/drm/mediatek/Makefile @@ -11,3 +11,11 @@ mediatek-drm-y := mtk_drm_drv.o \
obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
+mediatek-drm-hdmi-objs := mtk_drm_hdmi_drv.o \ + mtk_cec.o \ + mtk_hdmi_ddc_drv.o \ + mtk_hdmi.o \ + mtk_hdmi_hw.o \ + mtk_hdmi_phy.o + +obj-$(CONFIG_DRM_MEDIATEK_HDMI) += mediatek-drm-hdmi.o diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c new file mode 100644 index 0000000..65f4d60 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_cec.c @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#define TR_CONFIG 0x00 +#define CLEAR_CEC_IRQ BIT(15) + +#define CEC_CKGEN 0x04 +#define CEC_32K_PDN BIT(19) +#define PDN BIT(16) + +#define RX_EVENT 0x54 +#define HDMI_PORD BIT(25) +#define HDMI_HTPLG BIT(24) +#define HDMI_PORD_INT_EN BIT(9) +#define HDMI_HTPLG_INT_EN BIT(8) + +#define RX_GEN_WD 0x58 +#define HDMI_PORD_INT_32K_STATUS BIT(26) +#define RX_RISC_INT_32K_STATUS BIT(25) +#define HDMI_HTPLG_INT_32K_STATUS BIT(24) +#define HDMI_PORD_INT_32K_CLR BIT(18) +#define RX_INT_32K_CLR BIT(17) +#define HDMI_HTPLG_INT_32K_CLR BIT(16) +#define HDMI_PORD_INT_32K_STA_MASK BIT(10) +#define RX_RISC_INT_32K_STA_MASK BIT(9) +#define HDMI_HTPLG_INT_32K_STA_MASK BIT(8) +#define HDMI_PORD_INT_32K_EN BIT(2) +#define RX_INT_32K_EN BIT(1) +#define HDMI_HTPLG_INT_32K_EN BIT(0) + +#define NORMAL_INT_CTRL 0x5C +#define HDMI_HTPLG_INT_STA BIT(0) +#define HDMI_PORD_INT_STA BIT(1) +#define HDMI_HTPLG_INT_CLR BIT(16) +#define HDMI_PORD_INT_CLR BIT(17) +#define HDMI_FULL_INT_CLR BIT(20) + +struct mtk_cec { + void __iomem *regs; + struct clk *clk; + int irq; + bool hpd; + struct drm_device *drm; + void (*hpd_event)(bool hpd, void *data); + void *hpd_event_data; +}; + +static void mtk_cec_mask(struct mtk_cec *cec, unsigned int offset, + unsigned int val, unsigned int mask) +{ + u32 tmp = readl(cec->regs + offset) & ~mask; + + tmp |= val & mask; + writel(val, cec->regs + offset); +} + +void mtk_cec_set_hpd_event(struct device *dev, + void (*hpd_event)(bool hpd, void *data), + void *hpd_event_data) +{ + struct mtk_cec *cec = dev_get_drvdata(dev); + + cec->hpd_event_data = hpd_event_data; + cec->hpd_event = hpd_event; +} + +bool mtk_cec_hpd_high(struct device *dev) +{ + struct mtk_cec *cec = dev_get_drvdata(dev); + unsigned int status; + + status = readl(cec->regs + RX_EVENT); + + return (status & (HDMI_PORD | HDMI_HTPLG)) == (HDMI_PORD | HDMI_HTPLG); +} + +void mtk_cec_set_drm_device(struct device *dev, struct drm_device *drm) +{ + struct mtk_cec *cec = dev_get_drvdata(dev); + + cec->drm = drm; +} + +int mtk_cec_irq(struct device *dev) +{ + struct mtk_cec *cec = dev_get_drvdata(dev); + + return cec->irq; +} + +static void mtk_cec_htplg_irq_enable(struct mtk_cec *cec) +{ + mtk_cec_mask(cec, CEC_CKGEN, 0, PDN); + mtk_cec_mask(cec, CEC_CKGEN, CEC_32K_PDN, CEC_32K_PDN); + mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR, + HDMI_PORD_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR, + HDMI_HTPLG_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_EN); + mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_EN); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_EN); + + mtk_cec_mask(cec, RX_EVENT, HDMI_PORD_INT_EN, HDMI_PORD_INT_EN); + mtk_cec_mask(cec, RX_EVENT, HDMI_HTPLG_INT_EN, HDMI_HTPLG_INT_EN); +} + +static void mtk_cec_htplg_irq_disable(struct mtk_cec *cec) +{ + mtk_cec_mask(cec, RX_EVENT, 0, HDMI_PORD_INT_EN); + mtk_cec_mask(cec, RX_EVENT, 0, HDMI_HTPLG_INT_EN); +} + +static void mtk_cec_clear_htplg_irq(struct mtk_cec *cec) +{ + mtk_cec_mask(cec, TR_CONFIG, CLEAR_CEC_IRQ, CLEAR_CEC_IRQ); + mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_HTPLG_INT_CLR, + HDMI_HTPLG_INT_CLR); + mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_PORD_INT_CLR, + HDMI_PORD_INT_CLR); + mtk_cec_mask(cec, NORMAL_INT_CTRL, HDMI_FULL_INT_CLR, + HDMI_FULL_INT_CLR); + mtk_cec_mask(cec, RX_GEN_WD, HDMI_PORD_INT_32K_CLR, + HDMI_PORD_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, RX_INT_32K_CLR, RX_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, HDMI_HTPLG_INT_32K_CLR, + HDMI_HTPLG_INT_32K_CLR); + udelay(5); + mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_HTPLG_INT_CLR); + mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_PORD_INT_CLR); + mtk_cec_mask(cec, TR_CONFIG, 0, CLEAR_CEC_IRQ); + mtk_cec_mask(cec, NORMAL_INT_CTRL, 0, HDMI_FULL_INT_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_PORD_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, RX_INT_32K_CLR); + mtk_cec_mask(cec, RX_GEN_WD, 0, HDMI_HTPLG_INT_32K_CLR); +} + +static irqreturn_t mtk_cec_htplg_isr_thread(int irq, void *arg) +{ + struct device *dev = arg; + struct mtk_cec *cec = dev_get_drvdata(dev); + bool hpd; + + mtk_cec_clear_htplg_irq(cec); + hpd = mtk_cec_hpd_high(dev); + + if (cec->hpd != hpd) { + dev_info(dev, "hotplug event!,cur hpd = %d, hpd = %d\n", + cec->hpd, hpd); + cec->hpd = hpd; + if (cec->hpd_event) + cec->hpd_event(hpd, cec->hpd_event_data); + } + return IRQ_HANDLED; +} + +static int mtk_cec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_cec *cec; + struct resource *res; + int ret; + + cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL); + if (!cec) + return -ENOMEM; + + platform_set_drvdata(pdev, cec); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cec->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(cec->regs)) { + ret = PTR_ERR(cec->regs); + dev_err(dev, "Failed to ioremap cec: %d\n", ret); + return ret; + } + + cec->clk = devm_clk_get(dev, NULL); + if (IS_ERR(cec->clk)) { + ret = PTR_ERR(cec->clk); + dev_err(dev, "Failed to get cec clock: %d\n", ret); + return ret; + } + + cec->irq = platform_get_irq(pdev, 0); + if (cec->irq < 0) { + dev_err(dev, "Failed to get cec irq: %d\n", cec->irq); + return cec->irq; + } + + ret = devm_request_threaded_irq(dev, cec->irq, NULL, + mtk_cec_htplg_isr_thread, + IRQF_SHARED | IRQF_TRIGGER_LOW | + IRQF_ONESHOT, "hdmi hpd", dev); + if (ret) { + dev_err(dev, "Failed to register cec irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(cec->clk); + if (ret) { + dev_err(dev, "Failed to enable cec clock: %d\n", ret); + return ret; + } + + mtk_cec_htplg_irq_enable(cec); + + return 0; +} + +static int mtk_cec_remove(struct platform_device *pdev) +{ + struct mtk_cec *cec = platform_get_drvdata(pdev); + + mtk_cec_htplg_irq_disable(cec); + clk_disable_unprepare(cec->clk); + return 0; +} + +static const struct of_device_id mtk_cec_of_ids[] = { + { .compatible = "mediatek,mt8173-cec", }, + {} +}; + +struct platform_driver mtk_cec_driver = { + .probe = mtk_cec_probe, + .remove = mtk_cec_remove, + .driver = { + .name = "mediatek-cec", + .of_match_table = mtk_cec_of_ids, + }, +}; diff --git a/drivers/gpu/drm/mediatek/mtk_cec.h b/drivers/gpu/drm/mediatek/mtk_cec.h new file mode 100644 index 0000000..8306420 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_cec.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_CEC_H +#define _MTK_CEC_H + +struct device; + +void mtk_cec_set_hpd_event(struct device *dev, + void (*hotplug_event)(bool hpd, void *data), + void *hotplug_event_data); +bool mtk_cec_hpd_high(struct device *dev); +int mtk_cec_irq(struct device *dev); + +#endif /* _MTK_CEC_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index 138262c..7d41d11 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -24,6 +24,7 @@ #include <linux/pm_runtime.h> #include <soc/mediatek/smi.h>
+#include "mtk_cec.h" #include "mtk_drm_crtc.h" #include "mtk_drm_ddp_comp.h" #include "mtk_drm_drv.h" diff --git a/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c new file mode 100644 index 0000000..14b1fe2 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_hdmi_drv.c @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_atomic_helper.h> +#include <linux/clk.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/of_platform.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include "mtk_cec.h" +#include "mtk_hdmi.h" +#include "mtk_hdmi_hw.h" +#include "mtk_hdmi_phy.h" + +static const char * const mtk_hdmi_clk_names[MTK_HDMI_CLK_COUNT] = { + [MTK_HDMI_CLK_HDMI_PIXEL] = "pixel", + [MTK_HDMI_CLK_HDMI_PLL] = "pll", + [MTK_HDMI_CLK_AUD_BCLK] = "bclk", + [MTK_HDMI_CLK_AUD_SPDIF] = "spdif", +}; + +static const enum mtk_hdmi_clk_id mtk_hdmi_enable_clocks[] = { + MTK_HDMI_CLK_AUD_BCLK, + MTK_HDMI_CLK_AUD_SPDIF, +}; + +static int mtk_hdmi_get_all_clk(struct mtk_hdmi *hdmi, + struct device_node *np) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_hdmi_clk_names); i++) { + hdmi->clk[i] = of_clk_get_by_name(np, + mtk_hdmi_clk_names[i]); + if (IS_ERR(hdmi->clk[i])) + return PTR_ERR(hdmi->clk[i]); + } + return 0; +} + +static int mtk_hdmi_clk_enable_audio(struct mtk_hdmi *hdmi) +{ + int ret; + + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]); + if (ret) + return ret; + + ret = clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]); + if (ret) + goto err; + + return 0; +err: + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]); + return ret; +} + +static void mtk_hdmi_clk_disable_audio(struct mtk_hdmi *hdmi) +{ + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_BCLK]); + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_AUD_SPDIF]); +} + +static enum drm_connector_status hdmi_conn_detect(struct drm_connector *conn, + bool force) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + + return mtk_hdmi_hpd_high(hdmi) ? + connector_status_connected : connector_status_disconnected; +} + +static void hdmi_conn_destroy(struct drm_connector *conn) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + + mtk_cec_set_hpd_event(hdmi->cec_dev, NULL, NULL); + + drm_connector_unregister(conn); + drm_connector_cleanup(conn); +} + +static int hdmi_conn_set_property(struct drm_connector *conn, + struct drm_property *property, uint64_t val) +{ + return 0; +} + +static int mtk_hdmi_conn_get_modes(struct drm_connector *conn) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + struct edid *edid; + int ret; + + if (!hdmi->ddc_adpt) + return -ENODEV; + + edid = drm_get_edid(conn, hdmi->ddc_adpt); + if (!edid) + return -ENODEV; + + hdmi->dvi_mode = !drm_detect_hdmi_monitor(edid); + + drm_mode_connector_update_edid_property(conn, edid); + + ret = drm_add_edid_modes(conn, edid); + kfree(edid); + return ret; +} + +static int mtk_hdmi_conn_mode_valid(struct drm_connector *conn, + struct drm_display_mode *mode) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + + dev_info(hdmi->dev, "xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n", + mode->hdisplay, mode->vdisplay, mode->vrefresh, + !!(mode->flags & DRM_MODE_FLAG_INTERLACE), mode->clock * 1000); + + if (hdmi->bridge.next) { + struct drm_display_mode adjusted_mode; + + drm_mode_copy(&adjusted_mode, mode); + if (!drm_bridge_mode_fixup(hdmi->bridge.next, mode, + &adjusted_mode)) + return MODE_BAD; + } + + if (mode->clock >= 27000 && + mode->clock <= 297000 && + mode->hdisplay <= 0x1fff && + mode->vdisplay <= 0x1fff) + return MODE_OK; + + return MODE_BAD; +} + +static struct drm_encoder *mtk_hdmi_conn_best_enc(struct drm_connector *conn) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_conn(conn); + + return hdmi->bridge.encoder; +} + +static const struct drm_connector_funcs mtk_hdmi_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = hdmi_conn_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = hdmi_conn_destroy, + .set_property = hdmi_conn_set_property, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs + mtk_hdmi_connector_helper_funcs = { + .get_modes = mtk_hdmi_conn_get_modes, + .mode_valid = mtk_hdmi_conn_mode_valid, + .best_encoder = mtk_hdmi_conn_best_enc, +}; + +static void mtk_hdmi_hpd_event(bool hpd, void *data) +{ + struct mtk_hdmi *hdmi = data; + + if (hdmi && hdmi->bridge.encoder && hdmi->bridge.encoder->dev) + drm_helper_hpd_irq_event(hdmi->bridge.encoder->dev); +} + +/* + * Bridge callbacks + */ + +int mtk_hdmi_bridge_attach(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + int ret; + + ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn, + &mtk_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + dev_err(hdmi->dev, "Failed to initialize connector: %d\n", ret); + return ret; + } + drm_connector_helper_add(&hdmi->conn, &mtk_hdmi_connector_helper_funcs); + + hdmi->conn.polled = DRM_CONNECTOR_POLL_HPD; + hdmi->conn.interlace_allowed = true; + hdmi->conn.doublescan_allowed = false; + + ret = drm_connector_register(&hdmi->conn); + if (ret) { + dev_err(hdmi->dev, "Failed to register connector: %d\n", ret); + return ret; + } + + ret = drm_mode_connector_attach_encoder(&hdmi->conn, + bridge->encoder); + if (ret) { + dev_err(hdmi->dev, + "Failed to attach connector to encoder: %d\n", ret); + return ret; + } + + if (bridge->next) { + bridge->next->encoder = bridge->encoder; + ret = drm_bridge_attach(bridge->encoder->dev, bridge->next); + if (ret) { + dev_err(hdmi->dev, + "Failed to attach external bridge: %d\n", ret); + return ret; + } + } + + mtk_cec_set_hpd_event(hdmi->cec_dev, mtk_hdmi_hpd_event, hdmi); + + return 0; +} + +bool mtk_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +void mtk_hdmi_bridge_disable(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + phy_power_off(hdmi->phy); + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]); + clk_disable_unprepare(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]); +} + +void mtk_hdmi_bridge_post_disable(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + mtk_hdmi_power_off(hdmi); +} + +void mtk_hdmi_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + dev_info(hdmi->dev, "cur info: name:%s, hdisplay:%d\n", + adjusted_mode->name, adjusted_mode->hdisplay); + dev_info(hdmi->dev, "hsync_start:%d,hsync_end:%d, htotal:%d", + adjusted_mode->hsync_start, adjusted_mode->hsync_end, + adjusted_mode->htotal); + dev_info(hdmi->dev, "hskew:%d, vdisplay:%d\n", + adjusted_mode->hskew, adjusted_mode->vdisplay); + dev_info(hdmi->dev, "vsync_start:%d, vsync_end:%d, vtotal:%d", + adjusted_mode->vsync_start, adjusted_mode->vsync_end, + adjusted_mode->vtotal); + dev_info(hdmi->dev, "vscan:%d, flag:%d\n", + adjusted_mode->vscan, adjusted_mode->flags); + + drm_mode_copy(&hdmi->mode, adjusted_mode); +} + +void mtk_hdmi_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + mtk_hdmi_power_on(hdmi); +} + +void mtk_hdmi_bridge_enable(struct drm_bridge *bridge) +{ + struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); + + mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode); + clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]); + clk_prepare_enable(hdmi->clk[MTK_HDMI_CLK_HDMI_PIXEL]); + phy_power_on(hdmi->phy); +} + +static const struct drm_bridge_funcs mtk_hdmi_bridge_funcs = { + .attach = mtk_hdmi_bridge_attach, + .mode_fixup = mtk_hdmi_bridge_mode_fixup, + .disable = mtk_hdmi_bridge_disable, + .post_disable = mtk_hdmi_bridge_post_disable, + .mode_set = mtk_hdmi_bridge_mode_set, + .pre_enable = mtk_hdmi_bridge_pre_enable, + .enable = mtk_hdmi_bridge_enable, +}; + +static int mtk_hdmi_dt_parse_pdata(struct mtk_hdmi *hdmi, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *cec_np, *port, *ep, *remote, *i2c_np; + struct platform_device *cec_pdev; + struct regmap *regmap; + struct resource *mem; + int ret; + + hdmi->flt_n_5v_gpio = of_get_named_gpio(np, "flt_n_5v-gpios", 0); + if (hdmi->flt_n_5v_gpio < 0) { + dev_err(dev, "Failed to get flt_n_5v gpio: %d\n", + hdmi->flt_n_5v_gpio); + return hdmi->flt_n_5v_gpio; + } + + ret = mtk_hdmi_get_all_clk(hdmi, np); + if (ret) { + dev_err(dev, "Failed to get clocks: %d\n", ret); + return ret; + } + + /* The CEC module handles HDMI hotplug detection */ + cec_np = of_find_compatible_node(np->parent, NULL, + "mediatek,mt8173-cec"); + if (!cec_np) { + dev_err(dev, "Failed to find CEC node\n"); + return -EINVAL; + } + + cec_pdev = of_find_device_by_node(cec_np); + if (!cec_pdev) { + dev_err(hdmi->dev, "Waiting for CEC device %s\n", + cec_np->full_name); + return -EPROBE_DEFER; + } + hdmi->cec_dev = &cec_pdev->dev; + + /* + * The mediatek,syscon-hdmi property contains a phandle link to the + * MMSYS_CONFIG device and the register offset of the HDMI_SYS_CFG + * registers it contains. + */ + regmap = syscon_regmap_lookup_by_phandle(np, "mediatek,syscon-hdmi"); + ret = of_property_read_u32_index(np, "mediatek,syscon-hdmi", 1, + &hdmi->sys_offset); + if (IS_ERR(regmap)) + ret = PTR_ERR(regmap); + if (ret) { + ret = PTR_ERR(regmap); + dev_err(dev, + "Failed to get system configuration registers: %d\n", + ret); + return ret; + } + hdmi->sys_regmap = regmap; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(hdmi->regs)) { + dev_err(dev, "Failed to ioremap hdmi_shell: %ld\n", + PTR_ERR(hdmi->regs)); + return PTR_ERR(hdmi->regs); + } + + port = of_graph_get_port_by_id(np, 1); + if (!port) { + dev_err(dev, "Missing output port node\n"); + return -EINVAL; + } + + ep = of_get_child_by_name(port, "endpoint"); + if (!ep) { + dev_err(dev, "Missing endpoint node in port %s\n", + port->full_name); + of_node_put(port); + return -EINVAL; + } + of_node_put(port); + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + dev_err(dev, "Missing connector/bridge node for endpoint %s\n", + ep->full_name); + of_node_put(ep); + return -EINVAL; + } + of_node_put(ep); + + if (!of_device_is_compatible(remote, "hdmi-connector")) { + hdmi->bridge.next = of_drm_find_bridge(remote); + if (!hdmi->bridge.next) { + dev_err(dev, "Waiting for external bridge\n"); + of_node_put(remote); + return -EPROBE_DEFER; + } + } + + i2c_np = of_parse_phandle(remote, "ddc-i2c-bus", 0); + if (!i2c_np) { + dev_err(dev, "Failed to find ddc-i2c-bus node in %s\n", + remote->full_name); + of_node_put(remote); + return -EINVAL; + } + of_node_put(remote); + + hdmi->ddc_adpt = of_find_i2c_adapter_by_node(i2c_np); + if (!hdmi->ddc_adpt) { + dev_err(dev, "Failed to get ddc i2c adapter by node\n"); + return -EINVAL; + } + + return 0; +} + +static irqreturn_t hdmi_flt_n_5v_irq_thread(int irq, void *arg) +{ + struct mtk_hdmi *hdmi = arg; + + dev_err(hdmi->dev, "detected 5v pin error status\n"); + return IRQ_HANDLED; +} + +static int mtk_drm_hdmi_probe(struct platform_device *pdev) +{ + struct mtk_hdmi *hdmi; + struct device *dev = &pdev->dev; + int ret; + struct mtk_hdmi_audio_data audio_data; + struct platform_device_info audio_pdev_info; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->dev = dev; + + ret = mtk_hdmi_dt_parse_pdata(hdmi, pdev); + if (ret) { + dev_err(dev, "mtk_hdmi_dt_parse_pdata failed!!\n"); + return ret; + } + + hdmi->flt_n_5v_irq = gpio_to_irq(hdmi->flt_n_5v_gpio); + if (hdmi->flt_n_5v_irq < 0) { + dev_err(dev, "hdmi->flt_n_5v_irq = %d\n", + hdmi->flt_n_5v_irq); + return hdmi->flt_n_5v_irq; + } + + ret = devm_request_threaded_irq(dev, hdmi->flt_n_5v_irq, + NULL, hdmi_flt_n_5v_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "hdmi flt_n_5v", hdmi); + if (ret) { + dev_err(dev, "Failed to register hdmi flt_n_5v interrupt\n"); + return ret; + } + + hdmi->phy = devm_phy_get(dev, "hdmi"); + if (IS_ERR(hdmi->phy)) { + ret = PTR_ERR(hdmi->phy); + dev_err(dev, "Failed to get HDMI PHY: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, hdmi); + + ret = mtk_hdmi_output_init(hdmi); + if (ret) { + dev_err(dev, "Failed to initialize hdmi output\n"); + return ret; + } + + memset(&audio_data, 0, sizeof(audio_data)); + audio_data.irq = mtk_cec_irq(hdmi->cec_dev); + audio_data.mtk_hdmi = hdmi; + audio_data.enable = mtk_hdmi_audio_enable; + audio_data.disable = mtk_hdmi_audio_disable; + audio_data.set_audio_param = mtk_hdmi_audio_set_param; + audio_data.hpd_detect = mtk_hdmi_hpd_high; + audio_data.detect_dvi_monitor = mtk_hdmi_detect_dvi_monitor; + + memset(&audio_pdev_info, 0, sizeof(audio_pdev_info)); + audio_pdev_info.parent = dev; + audio_pdev_info.id = PLATFORM_DEVID_NONE; + audio_pdev_info.name = "mtk-hdmi-codec"; + audio_pdev_info.dma_mask = DMA_BIT_MASK(32); + audio_pdev_info.data = &audio_data; + audio_pdev_info.size_data = sizeof(audio_data); + hdmi->audio_pdev = platform_device_register_full(&audio_pdev_info); + if (IS_ERR(hdmi->audio_pdev)) + dev_err(dev, "Failed to register audio device: %ld\n", PTR_ERR(hdmi->audio_pdev)); + + hdmi->bridge.funcs = &mtk_hdmi_bridge_funcs; + hdmi->bridge.of_node = pdev->dev.of_node; + ret = drm_bridge_add(&hdmi->bridge); + if (ret) { + dev_err(dev, "failed to add bridge, ret = %d\n", ret); + return ret; + } + + ret = mtk_hdmi_clk_enable_audio(hdmi); + if (ret) { + dev_err(dev, "Failed to enable audio clocks: %d\n", ret); + goto err_bridge_remove; + } + + dev_info(dev, "mediatek hdmi probe success\n"); + return 0; + +err_bridge_remove: + drm_bridge_remove(&hdmi->bridge); + return ret; +} + +static int mtk_drm_hdmi_remove(struct platform_device *pdev) +{ + struct mtk_hdmi *hdmi = platform_get_drvdata(pdev); + + drm_bridge_remove(&hdmi->bridge); + platform_device_unregister(hdmi->audio_pdev); + platform_set_drvdata(pdev, NULL); + mtk_hdmi_clk_disable_audio(hdmi); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_hdmi_suspend(struct device *dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + + mtk_hdmi_power_off(hdmi); + mtk_hdmi_clk_disable_audio(hdmi); + dev_info(dev, "hdmi suspend success!\n"); + return 0; +} + +static int mtk_hdmi_resume(struct device *dev) +{ + struct mtk_hdmi *hdmi = dev_get_drvdata(dev); + int ret = 0; + + ret = mtk_hdmi_clk_enable_audio(hdmi); + if (ret) { + dev_err(dev, "hdmi resume failed!\n"); + return ret; + } + + mtk_hdmi_power_on(hdmi); + mtk_hdmi_output_set_display_mode(hdmi, &hdmi->mode); + dev_info(dev, "hdmi resume success!\n"); + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(mtk_hdmi_pm_ops, + mtk_hdmi_suspend, mtk_hdmi_resume); + +static const struct of_device_id mtk_drm_hdmi_of_ids[] = { + { .compatible = "mediatek,mt8173-hdmi", }, + {} +}; + +struct platform_driver mtk_hdmi_driver = { + .probe = mtk_drm_hdmi_probe, + .remove = mtk_drm_hdmi_remove, + .driver = { + .name = "mediatek-drm-hdmi", + .of_match_table = mtk_drm_hdmi_of_ids, + .pm = &mtk_hdmi_pm_ops, + }, +}; + +static int __init mtk_hdmitx_init(void) +{ + int ret; + + ret = platform_driver_register(&mtk_hdmi_phy_driver); + if (ret < 0) { + pr_err("Failed to register hdmi phy driver: %d\n", ret); + return ret; + } + + ret = platform_driver_register(&mtk_hdmi_ddc_driver); + if (ret < 0) { + pr_err("Failed to register hdmi ddc driver: %d\n", ret); + goto phy_err; + } + + ret = platform_driver_register(&mtk_cec_driver); + if (ret < 0) { + pr_err("Failed to register cec driver: %d\n", ret); + goto ddc_err; + } + + ret = platform_driver_register(&mtk_hdmi_driver); + if (ret < 0) { + pr_err("Failed to register hdmi driver: %d\n", ret); + goto cec_err; + } + + return 0; + +cec_err: + platform_driver_unregister(&mtk_cec_driver); +ddc_err: + platform_driver_unregister(&mtk_hdmi_ddc_driver); +phy_err: + platform_driver_unregister(&mtk_hdmi_phy_driver); + return ret; +} + +static void __exit mtk_hdmitx_exit(void) +{ + platform_driver_unregister(&mtk_hdmi_driver); + platform_driver_unregister(&mtk_cec_driver); + platform_driver_unregister(&mtk_hdmi_ddc_driver); + platform_driver_unregister(&mtk_hdmi_phy_driver); +} + +module_init(mtk_hdmitx_init); +module_exit(mtk_hdmitx_exit); + +MODULE_AUTHOR("Jie Qiu jie.qiu@mediatek.com"); +MODULE_DESCRIPTION("MediaTek HDMI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c new file mode 100644 index 0000000..949d59f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <drm/drm_edid.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/phy/phy.h> +#include "mtk_cec.h" +#include "mtk_hdmi.h" +#include "mtk_hdmi_hw.h" +#include "mtk_hdmi_phy.h" + +static u8 mtk_hdmi_aud_get_chnl_count(enum hdmi_aud_channel_type channel_type) +{ + switch (channel_type) { + case HDMI_AUD_CHAN_TYPE_1_0: + case HDMI_AUD_CHAN_TYPE_1_1: + case HDMI_AUD_CHAN_TYPE_2_0: + return 2; + case HDMI_AUD_CHAN_TYPE_2_1: + case HDMI_AUD_CHAN_TYPE_3_0: + return 3; + case HDMI_AUD_CHAN_TYPE_3_1: + case HDMI_AUD_CHAN_TYPE_4_0: + case HDMI_AUD_CHAN_TYPE_3_0_LRS: + return 4; + case HDMI_AUD_CHAN_TYPE_4_1: + case HDMI_AUD_CHAN_TYPE_5_0: + case HDMI_AUD_CHAN_TYPE_3_1_LRS: + case HDMI_AUD_CHAN_TYPE_4_0_CLRS: + return 5; + case HDMI_AUD_CHAN_TYPE_5_1: + case HDMI_AUD_CHAN_TYPE_6_0: + case HDMI_AUD_CHAN_TYPE_4_1_CLRS: + case HDMI_AUD_CHAN_TYPE_6_0_CS: + case HDMI_AUD_CHAN_TYPE_6_0_CH: + case HDMI_AUD_CHAN_TYPE_6_0_OH: + case HDMI_AUD_CHAN_TYPE_6_0_CHR: + return 6; + case HDMI_AUD_CHAN_TYPE_6_1: + case HDMI_AUD_CHAN_TYPE_6_1_CS: + case HDMI_AUD_CHAN_TYPE_6_1_CH: + case HDMI_AUD_CHAN_TYPE_6_1_OH: + case HDMI_AUD_CHAN_TYPE_6_1_CHR: + case HDMI_AUD_CHAN_TYPE_7_0: + case HDMI_AUD_CHAN_TYPE_7_0_LH_RH: + case HDMI_AUD_CHAN_TYPE_7_0_LSR_RSR: + case HDMI_AUD_CHAN_TYPE_7_0_LC_RC: + case HDMI_AUD_CHAN_TYPE_7_0_LW_RW: + case HDMI_AUD_CHAN_TYPE_7_0_LSD_RSD: + case HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS: + case HDMI_AUD_CHAN_TYPE_7_0_LHS_RHS: + case HDMI_AUD_CHAN_TYPE_7_0_CS_CH: + case HDMI_AUD_CHAN_TYPE_7_0_CS_OH: + case HDMI_AUD_CHAN_TYPE_7_0_CS_CHR: + case HDMI_AUD_CHAN_TYPE_7_0_CH_OH: + case HDMI_AUD_CHAN_TYPE_7_0_CH_CHR: + case HDMI_AUD_CHAN_TYPE_7_0_OH_CHR: + case HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS_LSR_RSR: + case HDMI_AUD_CHAN_TYPE_8_0_LH_RH_CS: + return 7; + case HDMI_AUD_CHAN_TYPE_7_1: + case HDMI_AUD_CHAN_TYPE_7_1_LH_RH: + case HDMI_AUD_CHAN_TYPE_7_1_LSR_RSR: + case HDMI_AUD_CHAN_TYPE_7_1_LC_RC: + case HDMI_AUD_CHAN_TYPE_7_1_LW_RW: + case HDMI_AUD_CHAN_TYPE_7_1_LSD_RSD: + case HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS: + case HDMI_AUD_CHAN_TYPE_7_1_LHS_RHS: + case HDMI_AUD_CHAN_TYPE_7_1_CS_CH: + case HDMI_AUD_CHAN_TYPE_7_1_CS_OH: + case HDMI_AUD_CHAN_TYPE_7_1_CS_CHR: + case HDMI_AUD_CHAN_TYPE_7_1_CH_OH: + case HDMI_AUD_CHAN_TYPE_7_1_CH_CHR: + case HDMI_AUD_CHAN_TYPE_7_1_OH_CHR: + case HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS_LSR_RSR: + return 8; + default: + return 2; + } +} + +static int mtk_hdmi_video_change_vpll(struct mtk_hdmi *hdmi, u32 clock, + enum hdmi_display_color_depth depth) +{ + unsigned long rate; + int ret; + + /* The DPI driver already should have set TVDPLL to the correct rate */ + ret = clk_set_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL], clock); + if (ret) { + dev_err(hdmi->dev, "Failed to set PLL to %u Hz: %d\n", clock, + ret); + return ret; + } + + rate = clk_get_rate(hdmi->clk[MTK_HDMI_CLK_HDMI_PLL]); + + if (DIV_ROUND_CLOSEST(rate, 1000) != DIV_ROUND_CLOSEST(clock, 1000)) + dev_warn(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, + rate); + else + dev_dbg(hdmi->dev, "Want PLL %u Hz, got %lu Hz\n", clock, rate); + + mtk_hdmi_phy_set_pll(hdmi->phy, clock, depth); + mtk_hdmi_hw_config_sys(hdmi); + mtk_hdmi_hw_set_deep_color_mode(hdmi, depth); + return 0; +} + +static void mtk_hdmi_video_set_display_mode(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + mtk_hdmi_hw_reset(hdmi); + mtk_hdmi_hw_enable_notice(hdmi, true); + mtk_hdmi_hw_write_int_mask(hdmi, 0xff); + mtk_hdmi_hw_enable_dvi_mode(hdmi, hdmi->dvi_mode); + mtk_hdmi_hw_ncts_auto_write_enable(hdmi, true); + + mtk_hdmi_hw_msic_setting(hdmi, mode); +} + +static int mtk_hdmi_aud_enable_packet(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_hw_send_aud_packet(hdmi, enable); + return 0; +} + +static int mtk_hdmi_aud_on_off_hw_ncts(struct mtk_hdmi *hdmi, bool on) +{ + mtk_hdmi_hw_ncts_enable(hdmi, on); + return 0; +} + +static int mtk_hdmi_aud_set_input(struct mtk_hdmi *hdmi) +{ + u8 chan_count; + + mtk_hdmi_hw_aud_set_channel_swap(hdmi, HDMI_AUD_SWAP_LFE_CC); + mtk_hdmi_hw_aud_raw_data_enable(hdmi, true); + + if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF && + hdmi->aud_param.aud_codec == HDMI_AUDIO_CODING_TYPE_DST) { + mtk_hdmi_hw_aud_set_bit_num(hdmi, + HDMI_AUDIO_SAMPLE_SIZE_24); + } else if (hdmi->aud_param.aud_i2s_fmt == + HDMI_I2S_MODE_LJT_24BIT) { + hdmi->aud_param.aud_i2s_fmt = HDMI_I2S_MODE_LJT_16BIT; + } + + mtk_hdmi_hw_aud_set_i2s_fmt(hdmi, + hdmi->aud_param.aud_i2s_fmt); + mtk_hdmi_hw_aud_set_bit_num(hdmi, HDMI_AUDIO_SAMPLE_SIZE_24); + + mtk_hdmi_hw_aud_set_high_bitrate(hdmi, false); + mtk_hdmi_phy_aud_dst_normal_double_enable(hdmi, false); + mtk_hdmi_hw_aud_dst_enable(hdmi, false); + + if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_SPDIF) { + mtk_hdmi_hw_aud_dsd_enable(hdmi, false); + if (hdmi->aud_param.aud_codec == + HDMI_AUDIO_CODING_TYPE_DST) { + mtk_hdmi_phy_aud_dst_normal_double_enable(hdmi, + true); + mtk_hdmi_hw_aud_dst_enable(hdmi, true); + } + + chan_count = mtk_hdmi_aud_get_chnl_count + (HDMI_AUD_CHAN_TYPE_2_0); + mtk_hdmi_hw_aud_set_i2s_chan_num(hdmi, + HDMI_AUD_CHAN_TYPE_2_0, + chan_count); + mtk_hdmi_hw_aud_set_input_type(hdmi, + HDMI_AUD_INPUT_SPDIF); + } else { + mtk_hdmi_hw_aud_dsd_enable(hdmi, false); + chan_count = + mtk_hdmi_aud_get_chnl_count( + hdmi->aud_param.aud_input_chan_type); + mtk_hdmi_hw_aud_set_i2s_chan_num( + hdmi, + hdmi->aud_param.aud_input_chan_type, + chan_count); + mtk_hdmi_hw_aud_set_input_type(hdmi, + HDMI_AUD_INPUT_I2S); + } + return 0; +} + +static int mtk_hdmi_aud_set_src(struct mtk_hdmi *hdmi, + struct drm_display_mode *display_mode) +{ + mtk_hdmi_aud_on_off_hw_ncts(hdmi, false); + + if (hdmi->aud_param.aud_input_type == HDMI_AUD_INPUT_I2S) { + switch (hdmi->aud_param.aud_hdmi_fs) { + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: + mtk_hdmi_hw_aud_src_off(hdmi); + /* mtk_hdmi_hw_aud_src_enable(hdmi, false); */ + mtk_hdmi_hw_aud_set_mclk( + hdmi, + hdmi->aud_param.aud_mclk); + mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false); + break; + default: + break; + } + } else { + switch (hdmi->aud_param.iec_frame_fs) { + case HDMI_IEC_32K: + hdmi->aud_param.aud_hdmi_fs = + HDMI_AUDIO_SAMPLE_FREQUENCY_32000; + mtk_hdmi_hw_aud_src_off(hdmi); + mtk_hdmi_hw_aud_set_mclk(hdmi, + HDMI_AUD_MCLK_128FS); + mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false); + break; + case HDMI_IEC_48K: + hdmi->aud_param.aud_hdmi_fs = + HDMI_AUDIO_SAMPLE_FREQUENCY_48000; + mtk_hdmi_hw_aud_src_off(hdmi); + mtk_hdmi_hw_aud_set_mclk(hdmi, + HDMI_AUD_MCLK_128FS); + mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false); + break; + case HDMI_IEC_44K: + hdmi->aud_param.aud_hdmi_fs = + HDMI_AUDIO_SAMPLE_FREQUENCY_44100; + mtk_hdmi_hw_aud_src_off(hdmi); + mtk_hdmi_hw_aud_set_mclk(hdmi, + HDMI_AUD_MCLK_128FS); + mtk_hdmi_hw_aud_aclk_inv_enable(hdmi, false); + break; + default: + break; + } + } + mtk_hdmi_hw_aud_set_ncts(hdmi, hdmi->depth, hdmi->aud_param.aud_hdmi_fs, + display_mode->clock); + + mtk_hdmi_hw_aud_src_reenable(hdmi); + return 0; +} + +static int mtk_hdmi_aud_set_chnl_status(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_aud_set_channel_status( + hdmi, + hdmi->aud_param.hdmi_l_channel_state, + hdmi->aud_param.hdmi_r_channel_state, + hdmi->aud_param.aud_hdmi_fs); + return 0; +} + +static int mtk_hdmi_aud_output_config(struct mtk_hdmi *hdmi, + struct drm_display_mode *display_mode) +{ + mtk_hdmi_hw_aud_mute(hdmi, true); + mtk_hdmi_aud_enable_packet(hdmi, false); + + mtk_hdmi_aud_set_input(hdmi); + mtk_hdmi_aud_set_src(hdmi, display_mode); + mtk_hdmi_aud_set_chnl_status(hdmi); + + usleep_range(50, 100); + + mtk_hdmi_aud_on_off_hw_ncts(hdmi, true); + mtk_hdmi_aud_enable_packet(hdmi, true); + mtk_hdmi_hw_aud_mute(hdmi, false); + return 0; +} + +static int mtk_hdmi_setup_av_mute_packet(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_send_av_mute(hdmi); + return 0; +} + +static int mtk_hdmi_setup_av_unmute_packet(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_send_av_unmute(hdmi); + return 0; +} + +static int mtk_hdmi_setup_avi_infoframe(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 buffer[17]; + ssize_t err; + + err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); + if (err < 0) { + dev_err(hdmi->dev, + "Failed to get AVI infoframe from mode: %ld\n", err); + return err; + } + + err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack AVI infoframe: %ld\n", err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_setup_spd_infoframe(struct mtk_hdmi *hdmi, + const char *vendor, + const char *product) +{ + struct hdmi_spd_infoframe frame; + u8 buffer[29]; + ssize_t err; + + err = hdmi_spd_infoframe_init(&frame, vendor, product); + if (err < 0) { + dev_err(hdmi->dev, "Failed to initialize SPD infoframe %ld\n", + err); + return err; + } + + err = hdmi_spd_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack SDP infoframe: %ld\n", err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_setup_audio_infoframe(struct mtk_hdmi *hdmi) +{ + struct hdmi_audio_infoframe frame; + u8 buffer[14]; + ssize_t err; + + err = hdmi_audio_infoframe_init(&frame); + if (err < 0) { + dev_err(hdmi->dev, "Faied to setup audio infoframe: %ld\n", + err); + return err; + } + + frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; + frame.channels = + mtk_hdmi_aud_get_chnl_count( + hdmi->aud_param.aud_input_chan_type); + + err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack audio infoframe: %ld\n", + err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +static int mtk_hdmi_setup_vendor_specific_infoframe(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + struct hdmi_vendor_infoframe frame; + u8 buffer[10]; + ssize_t err; + + err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mode); + if (err) { + dev_err(hdmi->dev, + "Failed to get vendor infoframe from mode: %ld\n", err); + return err; + } + + err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err) { + dev_err(hdmi->dev, "Failed to pack vendor infoframe: %ld\n", + err); + return err; + } + + mtk_hdmi_hw_send_info_frame(hdmi, buffer, sizeof(buffer)); + return 0; +} + +int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi) +{ + return hdmi->cec_dev ? mtk_cec_hpd_high(hdmi->cec_dev) : false; +} + +int mtk_hdmi_output_init(struct mtk_hdmi *hdmi) +{ + struct hdmi_audio_param *aud_param = &hdmi->aud_param; + + if (hdmi->init) + return -EINVAL; + + hdmi->csp = HDMI_COLORSPACE_RGB; + hdmi->depth = HDMI_DEEP_COLOR_24BITS; + hdmi->output = true; + aud_param->aud_codec = HDMI_AUDIO_CODING_TYPE_PCM; + aud_param->aud_hdmi_fs = HDMI_AUDIO_SAMPLE_FREQUENCY_48000; + aud_param->aud_sampe_size = HDMI_AUDIO_SAMPLE_SIZE_16; + aud_param->aud_input_type = HDMI_AUD_INPUT_I2S; + aud_param->aud_i2s_fmt = HDMI_I2S_MODE_I2S_24BIT; + aud_param->aud_mclk = HDMI_AUD_MCLK_128FS; + aud_param->iec_frame_fs = HDMI_IEC_48K; + aud_param->aud_input_chan_type = HDMI_AUD_CHAN_TYPE_2_0; + aud_param->hdmi_l_channel_state[2] = 2; + aud_param->hdmi_r_channel_state[2] = 2; + hdmi->init = true; + + return 0; +} + +void mtk_hdmi_power_on(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_make_reg_writable(hdmi, true); + mtk_hdmi_hw_1p4_version_enable(hdmi, true); +} + +void mtk_hdmi_power_off(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_hw_1p4_version_enable(hdmi, true); + mtk_hdmi_hw_make_reg_writable(hdmi, false); +} + +void mtk_hdmi_audio_enable(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_aud_enable_packet(hdmi, true); + hdmi->audio_enable = true; +} + +void mtk_hdmi_audio_disable(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_aud_enable_packet(hdmi, false); + hdmi->audio_enable = false; +} + +int mtk_hdmi_audio_set_param(struct mtk_hdmi *hdmi, + struct hdmi_audio_param *param) +{ + if (!hdmi->audio_enable) { + dev_err(hdmi->dev, "hdmi audio is in disable state!\n"); + return -EINVAL; + } + dev_info(hdmi->dev, "codec:%d, input:%d, channel:%d, fs:%d\n", + param->aud_codec, param->aud_input_type, + param->aud_input_chan_type, param->aud_hdmi_fs); + memcpy(&hdmi->aud_param, param, sizeof(*param)); + return mtk_hdmi_aud_output_config(hdmi, &hdmi->mode); +} + +int mtk_hdmi_detect_dvi_monitor(struct mtk_hdmi *hdmi) +{ + return hdmi->dvi_mode; +} + +int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + int ret; + + if (!hdmi->init) { + dev_err(hdmi->dev, "doesn't init hdmi control context!\n"); + return -EINVAL; + } + + mtk_hdmi_hw_vid_black(hdmi, true); + mtk_hdmi_hw_aud_mute(hdmi, true); + mtk_hdmi_setup_av_mute_packet(hdmi); + phy_power_off(hdmi->phy); + + ret = mtk_hdmi_video_change_vpll(hdmi, + mode->clock * 1000, + hdmi->depth); + if (ret) { + dev_err(hdmi->dev, "set vpll failed!\n"); + return ret; + } + mtk_hdmi_video_set_display_mode(hdmi, mode); + + phy_power_on(hdmi->phy); + mtk_hdmi_aud_output_config(hdmi, mode); + + mtk_hdmi_setup_audio_infoframe(hdmi); + mtk_hdmi_setup_avi_infoframe(hdmi, mode); + mtk_hdmi_setup_spd_infoframe(hdmi, "mediatek", "chromebook"); + if (mode->flags & DRM_MODE_FLAG_3D_MASK) + mtk_hdmi_setup_vendor_specific_infoframe(hdmi, mode); + + mtk_hdmi_hw_vid_black(hdmi, false); + mtk_hdmi_hw_aud_mute(hdmi, false); + mtk_hdmi_setup_av_unmute_packet(hdmi); + + return 0; +} diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.h b/drivers/gpu/drm/mediatek/mtk_hdmi.h new file mode 100644 index 0000000..c5a8917 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_HDMI_CTRL_H +#define _MTK_HDMI_CTRL_H + +#include <drm/drm_crtc.h> +#include <drm/mediatek/mtk_hdmi_audio.h> +#include <linux/hdmi.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/types.h> + +struct clk; +struct device; +struct device_node; +struct i2c_adapter; +struct platform_device; +struct phy; +struct regmap; + +enum mtk_hdmi_clk_id { + MTK_HDMI_CLK_HDMI_PIXEL, + MTK_HDMI_CLK_HDMI_PLL, + MTK_HDMI_CLK_AUD_BCLK, + MTK_HDMI_CLK_AUD_SPDIF, + MTK_HDMI_CLK_COUNT +}; + +enum hdmi_display_color_depth { + HDMI_DEEP_COLOR_24BITS, + HDMI_DEEP_COLOR_30BITS, + HDMI_DEEP_COLOR_36BITS, + HDMI_DEEP_COLOR_48BITS, +}; + +struct mtk_hdmi { + struct drm_bridge bridge; + struct drm_connector conn; + struct device *dev; + struct phy *phy; + struct device *cec_dev; + struct i2c_adapter *ddc_adpt; + struct clk *clk[MTK_HDMI_CLK_COUNT]; +#if defined(CONFIG_DEBUG_FS) + struct dentry *debugfs; +#endif + struct platform_device *audio_pdev; + struct drm_display_mode mode; + bool dvi_mode; + int flt_n_5v_gpio; + int flt_n_5v_irq; + u32 min_clock; + u32 max_clock; + u32 max_hdisplay; + u32 max_vdisplay; + u32 ibias; + u32 ibias_up; + struct regmap *sys_regmap; + unsigned int sys_offset; + void __iomem *regs; + bool init; + enum hdmi_display_color_depth depth; + enum hdmi_colorspace csp; + bool audio_enable; + bool output; + struct hdmi_audio_param aud_param; +}; + +static inline struct mtk_hdmi *hdmi_ctx_from_bridge(struct drm_bridge *b) +{ + return container_of(b, struct mtk_hdmi, bridge); +} + +static inline struct mtk_hdmi *hdmi_ctx_from_conn(struct drm_connector *c) +{ + return container_of(c, struct mtk_hdmi, conn); +} + +int mtk_hdmi_output_init(struct mtk_hdmi *hdmi); +int mtk_hdmi_hpd_high(struct mtk_hdmi *hdmi); +int mtk_hdmi_output_set_display_mode(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode); +void mtk_hdmi_power_on(struct mtk_hdmi *hdmi); +void mtk_hdmi_power_off(struct mtk_hdmi *hdmi); +void mtk_hdmi_audio_enable(struct mtk_hdmi *hctx); +void mtk_hdmi_audio_disable(struct mtk_hdmi *hctx); +int mtk_hdmi_audio_set_param(struct mtk_hdmi *hctx, + struct hdmi_audio_param *param); +int mtk_hdmi_detect_dvi_monitor(struct mtk_hdmi *hctx); +#if defined(CONFIG_DEBUG_FS) +int mtk_drm_hdmi_debugfs_init(struct mtk_hdmi *hdmi); +void mtk_drm_hdmi_debugfs_exit(struct mtk_hdmi *hdmi); +#else +int mtk_drm_hdmi_debugfs_init(struct mtk_hdmi *hdmi) +{ + return 0; +} + +void mtk_drm_hdmi_debugfs_exit(struct mtk_hdmi *hdmi) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +extern struct platform_driver mtk_cec_driver; +extern struct platform_driver mtk_hdmi_ddc_driver; +extern struct platform_driver mtk_hdmi_phy_driver; +#endif /* _MTK_HDMI_CTRL_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c new file mode 100644 index 0000000..22e5487 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_drv.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> + +#define SIF1_CLOK (288) +#define DDC_DDCMCTL0 (0x0) +#define DDCM_ODRAIN BIT(31) +#define DDCM_CLK_DIV_OFFSET (16) +#define DDCM_CLK_DIV_MASK (0xfff << 16) +#define DDCM_CS_STATUS BIT(4) +#define DDCM_SCL_STATE BIT(3) +#define DDCM_SDA_STATE BIT(2) +#define DDCM_SM0EN BIT(1) +#define DDCM_SCL_STRECH BIT(0) +#define DDC_DDCMCTL1 (0x4) +#define DDCM_ACK_OFFSET (16) +#define DDCM_ACK_MASK (0xff << 16) +#define DDCM_PGLEN_OFFSET (8) +#define DDCM_PGLEN_MASK (0x7 << 8) +#define DDCM_SIF_MODE_OFFSET (4) +#define DDCM_SIF_MODE_MASK (0x7 << 4) +#define DDCM_START (0x1) +#define DDCM_WRITE_DATA (0x2) +#define DDCM_STOP (0x3) +#define DDCM_READ_DATA_NO_ACK (0x4) +#define DDCM_READ_DATA_ACK (0x5) +#define DDCM_TRI BIT(0) +#define DDC_DDCMD0 (0x8) +#define DDCM_DATA3 (0xff << 24) +#define DDCM_DATA2 (0xff << 16) +#define DDCM_DATA1 (0xff << 8) +#define DDCM_DATA0 (0xff << 0) +#define DDC_DDCMD1 (0xc) +#define DDCM_DATA7 (0xff << 24) +#define DDCM_DATA6 (0xff << 16) +#define DDCM_DATA5 (0xff << 8) +#define DDCM_DATA4 (0xff << 0) + +struct mtk_hdmi_i2c { + struct i2c_adapter adap; + struct clk *clk; + void __iomem *regs; +}; + +static inline void sif_set_bit(struct mtk_hdmi_i2c *i2c, unsigned int offset, + unsigned int val) +{ + writel(readl(i2c->regs + offset) | val, i2c->regs + offset); +} + +static inline void sif_clr_bit(struct mtk_hdmi_i2c *i2c, unsigned int offset, + unsigned int val) +{ + writel(readl(i2c->regs + offset) & ~val, i2c->regs + offset); +} + +static inline bool sif_bit_is_set(struct mtk_hdmi_i2c *i2c, unsigned int offset, + unsigned int val) +{ + return (readl(i2c->regs + offset) & val) == val; +} + +static inline void sif_write_mask(struct mtk_hdmi_i2c *i2c, unsigned int offset, + unsigned int mask, unsigned int shift, + unsigned int val) +{ + unsigned int tmp; + + tmp = readl(i2c->regs + offset); + tmp &= ~mask; + tmp |= (val << shift) & mask; + writel(tmp, i2c->regs + offset); +} + +static inline unsigned int sif_read_mask(struct mtk_hdmi_i2c *i2c, + unsigned int offset, unsigned int mask, + unsigned int shift) +{ + return (readl(i2c->regs + offset) & mask) >> shift; +} + +static void ddcm_trigger_mode(struct mtk_hdmi_i2c *i2c, int mode) +{ + int timeout = 20 * 1000; + + sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_SIF_MODE_MASK, + DDCM_SIF_MODE_OFFSET, mode); + sif_set_bit(i2c, DDC_DDCMCTL1, DDCM_TRI); + while (sif_bit_is_set(i2c, DDC_DDCMCTL1, DDCM_TRI)) { + timeout -= 2; + udelay(2); + if (timeout <= 0) + break; + } +} + +static int mtk_hdmi_i2c_read_msg(struct mtk_hdmi_i2c *i2c, struct i2c_msg *msg) +{ + struct device *dev = i2c->adap.dev.parent; + u32 remain_count, ack_count, ack_final, read_count, temp_count; + u32 index = 0; + u32 ack; + int i; + + ddcm_trigger_mode(i2c, DDCM_START); + sif_write_mask(i2c, DDC_DDCMD0, 0xff, 0, (msg->addr << 1) | 0x01); + sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, + 0x00); + ddcm_trigger_mode(i2c, DDCM_WRITE_DATA); + ack = sif_read_mask(i2c, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); + dev_dbg(dev, "ack = 0x%x\n", ack); + if (ack != 0x01) { + dev_err(dev, "i2c ack err!\n"); + return -ENXIO; + } + + remain_count = msg->len; + ack_count = (msg->len - 1) / 8; + ack_final = 0; + + while (remain_count > 0) { + if (ack_count > 0) { + read_count = 8; + ack_final = 0; + ack_count--; + } else { + read_count = remain_count; + ack_final = 1; + } + + sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_PGLEN_MASK, + DDCM_PGLEN_OFFSET, read_count - 1); + ddcm_trigger_mode(i2c, (ack_final == 1) ? + DDCM_READ_DATA_NO_ACK : + DDCM_READ_DATA_ACK); + + ack = sif_read_mask(i2c, DDC_DDCMCTL1, DDCM_ACK_MASK, + DDCM_ACK_OFFSET); + temp_count = 0; + while (((ack & (1 << temp_count)) != 0) && (temp_count < 8)) + temp_count++; + if (((ack_final == 1) && (temp_count != (read_count - 1))) || + ((ack_final == 0) && (temp_count != read_count))) { + dev_err(dev, "Address NACK! ACK(0x%x)\n", ack); + break; + } + + for (i = read_count; i >= 1; i--) { + int shift; + int offset; + + if (i > 4) { + offset = DDC_DDCMD1; + shift = (i - 5) * 8; + } else { + offset = DDC_DDCMD0; + shift = (i - 1) * 8; + } + + msg->buf[index + i - 1] = sif_read_mask(i2c, offset, + 0xff << shift, + shift); + } + + remain_count -= read_count; + index += read_count; + } + + return 0; +} + +static int mtk_hdmi_i2c_write_msg(struct mtk_hdmi_i2c *i2c, struct i2c_msg *msg) +{ + struct device *dev = i2c->adap.dev.parent; + u32 ack; + + ddcm_trigger_mode(i2c, DDCM_START); + sif_write_mask(i2c, DDC_DDCMD0, DDCM_DATA0, 0, msg->addr << 1); + sif_write_mask(i2c, DDC_DDCMD0, DDCM_DATA1, 8, msg->buf[0]); + sif_write_mask(i2c, DDC_DDCMCTL1, DDCM_PGLEN_MASK, DDCM_PGLEN_OFFSET, + 0x1); + ddcm_trigger_mode(i2c, DDCM_WRITE_DATA); + + ack = sif_read_mask(i2c, DDC_DDCMCTL1, DDCM_ACK_MASK, DDCM_ACK_OFFSET); + dev_dbg(dev, "ack = %d\n", ack); + + if (ack != 0x03) { + dev_err(dev, "i2c ack err!\n"); + return -EIO; + } + + return 0; +} + +static int mtk_hdmi_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct mtk_hdmi_i2c *i2c = adapter->algo_data; + struct device *dev = adapter->dev.parent; + int ret; + int i; + + if (!i2c) { + dev_err(dev, "invalid arguments\n"); + return -EINVAL; + } + + sif_set_bit(i2c, DDC_DDCMCTL0, DDCM_SCL_STRECH); + sif_set_bit(i2c, DDC_DDCMCTL0, DDCM_SM0EN); + sif_clr_bit(i2c, DDC_DDCMCTL0, DDCM_ODRAIN); + + if (sif_bit_is_set(i2c, DDC_DDCMCTL1, DDCM_TRI)) { + dev_err(dev, "ddc line is busy!\n"); + return -EBUSY; + } + + sif_write_mask(i2c, DDC_DDCMCTL0, DDCM_CLK_DIV_MASK, + DDCM_CLK_DIV_OFFSET, SIF1_CLOK); + + for (i = 0; i < num; i++) { + struct i2c_msg *msg = &msgs[i]; + + dev_dbg(dev, "i2c msg, adr:0x%x, flags:%d, len :0x%x\n", + msg->addr, msg->flags, msg->len); + + if (msg->flags & I2C_M_RD) + ret = mtk_hdmi_i2c_read_msg(i2c, msg); + else + ret = mtk_hdmi_i2c_write_msg(i2c, msg); + if (ret < 0) + goto xfer_end; + } + + ddcm_trigger_mode(i2c, DDCM_STOP); + + return i; + +xfer_end: + ddcm_trigger_mode(i2c, DDCM_STOP); + dev_err(dev, "ddc failed!\n"); + return ret; +} + +static u32 mtk_hdmi_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm mtk_hdmi_i2c_algorithm = { + .master_xfer = mtk_hdmi_i2c_xfer, + .functionality = mtk_hdmi_i2c_func, +}; + +static int mtk_hdmi_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_hdmi_i2c *i2c; + struct resource *mem; + int ret; + + i2c = devm_kzalloc(dev, sizeof(struct mtk_hdmi_i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->clk = devm_clk_get(dev, "ddc-i2c"); + if (IS_ERR(i2c->clk)) { + dev_err(dev, "get ddc_clk failed : %p ,\n", i2c->clk); + return PTR_ERR(i2c->clk); + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2c->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(i2c->regs)) { + dev_err(dev, "get memory source fail!\n"); + return PTR_ERR(i2c->regs); + } + + ret = clk_prepare_enable(i2c->clk); + if (ret) { + dev_err(dev, "enable ddc clk failed!\n"); + return ret; + } + + strlcpy(i2c->adap.name, "mediatek-hdmi-i2c", sizeof(i2c->adap.name)); + i2c->adap.owner = THIS_MODULE; + i2c->adap.algo = &mtk_hdmi_i2c_algorithm; + i2c->adap.retries = 3; + i2c->adap.dev.of_node = dev->of_node; + i2c->adap.algo_data = i2c; + i2c->adap.dev.parent = &pdev->dev; + + ret = i2c_add_adapter(&i2c->adap); + if (ret < 0) { + dev_err(dev, "failed to add bus to i2c core\n"); + goto err_clk_disable; + } + + platform_set_drvdata(pdev, i2c); + + dev_dbg(dev, "i2c->adap: %p\n", &i2c->adap); + dev_dbg(dev, "i2c->clk: %p\n", i2c->clk); + dev_dbg(dev, "physical adr: 0x%llx, end: 0x%llx\n", mem->start, + mem->end); + + return 0; + +err_clk_disable: + clk_disable_unprepare(i2c->clk); + return ret; +} + +static int mtk_hdmi_i2c_remove(struct platform_device *pdev) +{ + struct mtk_hdmi_i2c *i2c = platform_get_drvdata(pdev); + + clk_disable_unprepare(i2c->clk); + i2c_del_adapter(&i2c->adap); + + return 0; +} + +static const struct of_device_id mtk_hdmi_i2c_match[] = { + { .compatible = "mediatek,mt8173-hdmi-ddc", }, + {}, +}; + +struct platform_driver mtk_hdmi_ddc_driver = { + .probe = mtk_hdmi_i2c_probe, + .remove = mtk_hdmi_i2c_remove, + .driver = { + .name = "mediatek-hdmi-ddc", + .of_match_table = mtk_hdmi_i2c_match, + }, +}; + +MODULE_AUTHOR("Jie Qiu jie.qiu@mediatek.com"); +MODULE_DESCRIPTION("MediaTek HDMI i2c Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c new file mode 100644 index 0000000..6e91706 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "mtk_hdmi_hw.h" +#include "mtk_hdmi_regs.h" +#include "mtk_hdmi.h" + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/hdmi.h> +#include <linux/io.h> +#include <linux/regmap.h> + +static u32 mtk_hdmi_read(struct mtk_hdmi *hdmi, u32 offset) +{ + return readl(hdmi->regs + offset); +} + +static void mtk_hdmi_write(struct mtk_hdmi *hdmi, u32 offset, u32 val) +{ + writel(val, hdmi->regs + offset); +} + +static void mtk_hdmi_mask(struct mtk_hdmi *hdmi, u32 offset, u32 val, u32 mask) +{ + u32 tmp = mtk_hdmi_read(hdmi, offset) & ~mask; + + tmp |= (val & mask); + mtk_hdmi_write(hdmi, offset, tmp); +} + +static const u8 PREDIV[3][4] = { + {0x0, 0x0, 0x0, 0x0}, /* 27Mhz */ + {0x1, 0x1, 0x1, 0x1}, /* 74Mhz */ + {0x1, 0x1, 0x1, 0x1} /* 148Mhz */ +}; + +static const u8 TXDIV[3][4] = { + {0x3, 0x3, 0x3, 0x2}, /* 27Mhz */ + {0x2, 0x1, 0x1, 0x1}, /* 74Mhz */ + {0x1, 0x0, 0x0, 0x0} /* 148Mhz */ +}; + +static const u8 FBKSEL[3][4] = { + {0x1, 0x1, 0x1, 0x1}, /* 27Mhz */ + {0x1, 0x0, 0x1, 0x1}, /* 74Mhz */ + {0x1, 0x0, 0x1, 0x1} /* 148Mhz */ +}; + +static const u8 FBKDIV[3][4] = { + {19, 24, 29, 19}, /* 27Mhz */ + {19, 24, 14, 19}, /* 74Mhz */ + {19, 24, 14, 19} /* 148Mhz */ +}; + +static const u8 DIVEN[3][4] = { + {0x2, 0x1, 0x1, 0x2}, /* 27Mhz */ + {0x2, 0x2, 0x2, 0x2}, /* 74Mhz */ + {0x2, 0x2, 0x2, 0x2} /* 148Mhz */ +}; + +static const u8 HTPLLBP[3][4] = { + {0xc, 0xc, 0x8, 0xc}, /* 27Mhz */ + {0xc, 0xf, 0xf, 0xc}, /* 74Mhz */ + {0xc, 0xf, 0xf, 0xc} /* 148Mhz */ +}; + +static const u8 HTPLLBC[3][4] = { + {0x2, 0x3, 0x3, 0x2}, /* 27Mhz */ + {0x2, 0x3, 0x3, 0x2}, /* 74Mhz */ + {0x2, 0x3, 0x3, 0x2} /* 148Mhz */ +}; + +static const u8 HTPLLBR[3][4] = { + {0x1, 0x1, 0x0, 0x1}, /* 27Mhz */ + {0x1, 0x2, 0x2, 0x1}, /* 74Mhz */ + {0x1, 0x2, 0x2, 0x1} /* 148Mhz */ +}; + +#define NCTS_BYTES 0x07 +static const u8 HDMI_NCTS[7][9][NCTS_BYTES] = { + {{0x00, 0x00, 0x69, 0x78, 0x00, 0x10, 0x00}, + {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x10, 0x00}, + {0x00, 0x03, 0x37, 0xf9, 0x00, 0x2d, 0x80}, + {0x00, 0x01, 0x22, 0x0a, 0x00, 0x10, 0x00}, + {0x00, 0x06, 0x6f, 0xf3, 0x00, 0x2d, 0x80}, + {0x00, 0x02, 0x44, 0x14, 0x00, 0x10, 0x00}, + {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x10, 0x00}, + {0x00, 0x06, 0x6F, 0xF3, 0x00, 0x16, 0xC0}, + {0x00, 0x03, 0x66, 0x1E, 0x00, 0x0C, 0x00} + }, + {{0x00, 0x00, 0x75, 0x30, 0x00, 0x18, 0x80}, + {0x00, 0x00, 0xea, 0x60, 0x00, 0x18, 0x80}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x45, 0xac}, + {0x00, 0x01, 0x42, 0x44, 0x00, 0x18, 0x80}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x22, 0xd6}, + {0x00, 0x02, 0x84, 0x88, 0x00, 0x18, 0x80}, + {0x00, 0x01, 0xd4, 0xc0, 0x00, 0x18, 0x80}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x11, 0x6B}, + {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x12, 0x60} + }, + {{0x00, 0x00, 0x69, 0x78, 0x00, 0x18, 0x00}, + {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x18, 0x00}, + {0x00, 0x02, 0x25, 0x51, 0x00, 0x2d, 0x80}, + {0x00, 0x01, 0x22, 0x0a, 0x00, 0x18, 0x00}, + {0x00, 0x02, 0x25, 0x51, 0x00, 0x16, 0xc0}, + {0x00, 0x02, 0x44, 0x14, 0x00, 0x18, 0x00}, + {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x18, 0x00}, + {0x00, 0x04, 0x4A, 0xA2, 0x00, 0x16, 0xC0}, + {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x14, 0x00} + }, + {{0x00, 0x00, 0x75, 0x30, 0x00, 0x31, 0x00}, + {0x00, 0x00, 0xea, 0x60, 0x00, 0x31, 0x00}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x8b, 0x58}, + {0x00, 0x01, 0x42, 0x44, 0x00, 0x31, 0x00}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x45, 0xac}, + {0x00, 0x02, 0x84, 0x88, 0x00, 0x31, 0x00}, + {0x00, 0x01, 0xd4, 0xc0, 0x00, 0x31, 0x00}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x22, 0xD6}, + {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x24, 0xC0} + }, + {{0x00, 0x00, 0x69, 0x78, 0x00, 0x30, 0x00}, + {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x30, 0x00}, + {0x00, 0x02, 0x25, 0x51, 0x00, 0x5b, 0x00}, + {0x00, 0x01, 0x22, 0x0a, 0x00, 0x30, 0x00}, + {0x00, 0x02, 0x25, 0x51, 0x00, 0x2d, 0x80}, + {0x00, 0x02, 0x44, 0x14, 0x00, 0x30, 0x00}, + {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x30, 0x00}, + {0x00, 0x04, 0x4A, 0xA2, 0x00, 0x2D, 0x80}, + {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x28, 0x80} + }, + {{0x00, 0x00, 0x75, 0x30, 0x00, 0x62, 0x00}, + {0x00, 0x00, 0xea, 0x60, 0x00, 0x62, 0x00}, + {0x00, 0x03, 0x93, 0x87, 0x01, 0x16, 0xb0}, + {0x00, 0x01, 0x42, 0x44, 0x00, 0x62, 0x00}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x8b, 0x58}, + {0x00, 0x02, 0x84, 0x88, 0x00, 0x62, 0x00}, + {0x00, 0x01, 0xd4, 0xc0, 0x00, 0x62, 0x00}, + {0x00, 0x03, 0x93, 0x87, 0x00, 0x45, 0xAC}, + {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x49, 0x80} + }, + {{0x00, 0x00, 0x69, 0x78, 0x00, 0x60, 0x00}, + {0x00, 0x00, 0xd2, 0xf0, 0x00, 0x60, 0x00}, + {0x00, 0x02, 0x25, 0x51, 0x00, 0xb6, 0x00}, + {0x00, 0x01, 0x22, 0x0a, 0x00, 0x60, 0x00}, + {0x00, 0x02, 0x25, 0x51, 0x00, 0x5b, 0x00}, + {0x00, 0x02, 0x44, 0x14, 0x00, 0x60, 0x00}, + {0x00, 0x01, 0xA5, 0xe0, 0x00, 0x60, 0x00}, + {0x00, 0x04, 0x4A, 0xA2, 0x00, 0x5B, 0x00}, + {0x00, 0x03, 0xC6, 0xCC, 0x00, 0x50, 0x00} + } +}; + +void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi, + bool black) +{ + mtk_hdmi_mask(hdmi, VIDEO_CFG_4, black ? GEN_RGB : NORMAL_PATH, + VIDEO_SOURCE_SEL); +} + +void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + HDMI_ON | ANLG_ON, enable ? (HDMI_ON | ANLG_ON) : 0); +} + +void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI2P0_EN, enable ? 0 : HDMI2P0_EN); +} + +void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi, bool mute) +{ + mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, mute ? AUDIO_ZERO : 0, AUDIO_ZERO); +} + +void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + HDMI_RST, HDMI_RST); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + HDMI_RST, 0); + mtk_hdmi_mask(hdmi, GRL_CFG3, 0, CFG3_CONTROL_PACKET_DELAY); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, + ANLG_ON, ANLG_ON); +} + +void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice) +{ + mtk_hdmi_mask(hdmi, GRL_CFG2, enable_notice ? CFG2_NOTICE_EN : 0, + CFG2_NOTICE_EN); +} + +void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask) +{ + mtk_hdmi_write(hdmi, GRL_INT_MASK, int_mask); +} + +void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_CFG1, enable ? CFG1_DVI : 0, CFG1_DVI); +} + +u32 mtk_hdmi_hw_get_int_type(struct mtk_hdmi *hdmi) +{ + return mtk_hdmi_read(hdmi, GRL_INT); +} + +void mtk_hdmi_hw_send_info_frame(struct mtk_hdmi *hdmi, u8 *buffer, u8 len) +{ + u32 ctrl_reg = GRL_CTRL; + int i; + u8 *frame_data; + u8 frame_type; + u8 frame_ver; + u8 frame_len; + u8 checksum; + int ctrl_frame_en = 0; + + frame_type = *buffer; + buffer += 1; + frame_ver = *buffer; + buffer += 1; + frame_len = *buffer; + buffer += 1; + checksum = *buffer; + buffer += 1; + frame_data = buffer; + + dev_info(hdmi->dev, + "frame_type:0x%x,frame_ver:0x%x,frame_len:0x%x,checksum:0x%x\n", + frame_type, frame_ver, frame_len, checksum); + + switch (frame_type) { + case HDMI_INFOFRAME_TYPE_AVI: + ctrl_frame_en = CTRL_AVI_EN; + ctrl_reg = GRL_CTRL; + break; + case HDMI_INFOFRAME_TYPE_SPD: + ctrl_frame_en = CTRL_SPD_EN; + ctrl_reg = GRL_CTRL; + break; + case HDMI_INFOFRAME_TYPE_AUDIO: + ctrl_frame_en = CTRL_AUDIO_EN; + ctrl_reg = GRL_CTRL; + break; + case HDMI_INFOFRAME_TYPE_VENDOR: + ctrl_frame_en = VS_EN; + ctrl_reg = GRL_ACP_ISRC_CTRL; + break; + default: + break; + } + mtk_hdmi_mask(hdmi, ctrl_reg, 0, ctrl_frame_en); + mtk_hdmi_write(hdmi, GRL_INFOFRM_TYPE, frame_type); + mtk_hdmi_write(hdmi, GRL_INFOFRM_VER, frame_ver); + mtk_hdmi_write(hdmi, GRL_INFOFRM_LNG, frame_len); + + mtk_hdmi_write(hdmi, GRL_IFM_PORT, checksum); + for (i = 0; i < frame_len; i++) + mtk_hdmi_write(hdmi, GRL_IFM_PORT, frame_data[i]); + + mtk_hdmi_mask(hdmi, ctrl_reg, ctrl_frame_en, ctrl_frame_en); +} + +void mtk_hdmi_hw_send_aud_packet(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_SHIFT_R2, enable ? 0 : AUDIO_PACKET_OFF, + AUDIO_PACKET_OFF); +} + +void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi) +{ + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI_OUT_FIFO_EN | MHL_MODE_ON, 0); + mdelay(2); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + HDMI_OUT_FIFO_EN | MHL_MODE_ON, HDMI_OUT_FIFO_EN); +} + +void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi, + enum hdmi_display_color_depth depth) +{ + u32 val = 0; + + switch (depth) { + case HDMI_DEEP_COLOR_24BITS: + val = COLOR_8BIT_MODE; + break; + case HDMI_DEEP_COLOR_30BITS: + val = COLOR_10BIT_MODE | DEEP_COLOR_EN; + break; + case HDMI_DEEP_COLOR_36BITS: + val = COLOR_12BIT_MODE | DEEP_COLOR_EN; + break; + case HDMI_DEEP_COLOR_48BITS: + val = COLOR_16BIT_MODE | DEEP_COLOR_EN; + break; + default: + val = COLOR_8BIT_MODE; + break; + } + + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, + DEEP_COLOR_MODE_MASK | DEEP_COLOR_EN, val); +} + +void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_mask(hdmi, GRL_CFG4, 0, CTRL_AVMUTE); + mdelay(2); + mtk_hdmi_mask(hdmi, GRL_CFG4, CTRL_AVMUTE, CTRL_AVMUTE); +} + +void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi) +{ + mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_EN, + CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET); + mdelay(2); + mtk_hdmi_mask(hdmi, GRL_CFG4, CFG4_AV_UNMUTE_SET, + CFG4_AV_UNMUTE_EN | CFG4_AV_UNMUTE_SET); +} + +void mtk_hdmi_hw_ncts_enable(struct mtk_hdmi *hdmi, bool on) +{ + mtk_hdmi_mask(hdmi, GRL_CTS_CTRL, on ? 0 : CTS_CTRL_SOFT, + CTS_CTRL_SOFT); +} + +void mtk_hdmi_hw_ncts_auto_write_enable(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_CTS_CTRL, enable ? NCTS_WRI_ANYTIME : 0, + NCTS_WRI_ANYTIME); +} + +void mtk_hdmi_hw_msic_setting(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode) +{ + mtk_hdmi_mask(hdmi, GRL_CFG4, 0, CFG_MHL_MODE); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE && + mode->clock == 74250 && + mode->vdisplay == 1080) + mtk_hdmi_mask(hdmi, GRL_CFG2, 0, MHL_DE_SEL); + else + mtk_hdmi_mask(hdmi, GRL_CFG2, MHL_DE_SEL, MHL_DE_SEL); +} + +void mtk_hdmi_hw_aud_set_channel_swap(struct mtk_hdmi *hdmi, + enum hdmi_aud_channel_swap_type swap) +{ + u8 swap_bit; + + switch (swap) { + case HDMI_AUD_SWAP_LR: + swap_bit = LR_SWAP; + break; + case HDMI_AUD_SWAP_LFE_CC: + swap_bit = LFE_CC_SWAP; + break; + case HDMI_AUD_SWAP_LSRS: + swap_bit = LSRS_SWAP; + break; + case HDMI_AUD_SWAP_RLS_RRS: + swap_bit = RLS_RRS_SWAP; + break; + case HDMI_AUD_SWAP_LR_STATUS: + swap_bit = LR_STATUS_SWAP; + break; + default: + swap_bit = LFE_CC_SWAP; + break; + } + mtk_hdmi_mask(hdmi, GRL_CH_SWAP, swap_bit, 0xff); +} + +void mtk_hdmi_hw_aud_raw_data_enable(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_MIX_CTRL, enable ? MIX_CTRL_FLAT : 0, + MIX_CTRL_FLAT); +} + +void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi, + enum hdmi_audio_sample_size bit_num) +{ + u32 val = 0; + + if (bit_num == HDMI_AUDIO_SAMPLE_SIZE_16) + val = AOUT_16BIT; + else if (bit_num == HDMI_AUDIO_SAMPLE_SIZE_20) + val = AOUT_20BIT; + else if (bit_num == HDMI_AUDIO_SAMPLE_SIZE_24) + val = AOUT_24BIT; + + mtk_hdmi_mask(hdmi, GRL_AOUT_BNUM_SEL, val, 0x03); +} + +void mtk_hdmi_hw_aud_set_i2s_fmt(struct mtk_hdmi *hdmi, + enum hdmi_aud_i2s_fmt i2s_fmt) +{ + u32 val = 0; + + val = mtk_hdmi_read(hdmi, GRL_CFG0); + val &= ~0x33; + + switch (i2s_fmt) { + case HDMI_I2S_MODE_RJT_24BIT: + val |= (CFG0_I2S_MODE_RTJ | CFG0_I2S_MODE_24BIT); + break; + case HDMI_I2S_MODE_RJT_16BIT: + val |= (CFG0_I2S_MODE_RTJ | CFG0_I2S_MODE_16BIT); + break; + case HDMI_I2S_MODE_LJT_24BIT: + val |= (CFG0_I2S_MODE_LTJ | CFG0_I2S_MODE_24BIT); + break; + case HDMI_I2S_MODE_LJT_16BIT: + val |= (CFG0_I2S_MODE_LTJ | CFG0_I2S_MODE_16BIT); + break; + case HDMI_I2S_MODE_I2S_24BIT: + val |= (CFG0_I2S_MODE_I2S | CFG0_I2S_MODE_24BIT); + break; + case HDMI_I2S_MODE_I2S_16BIT: + val |= (CFG0_I2S_MODE_I2S | CFG0_I2S_MODE_16BIT); + break; + default: + break; + } + mtk_hdmi_write(hdmi, GRL_CFG0, val); +} + +void mtk_hdmi_hw_aud_set_high_bitrate(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_AOUT_BNUM_SEL, + enable ? HIGH_BIT_RATE_PACKET_ALIGN : 0, + HIGH_BIT_RATE_PACKET_ALIGN); + mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, enable ? HIGH_BIT_RATE : 0, + HIGH_BIT_RATE); +} + +void mtk_hdmi_phy_aud_dst_normal_double_enable(struct mtk_hdmi *hdmi, + bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, enable ? DST_NORMAL_DOUBLE : 0, + DST_NORMAL_DOUBLE); +} + +void mtk_hdmi_hw_aud_dst_enable(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, enable ? SACD_DST : 0, SACD_DST); +} + +void mtk_hdmi_hw_aud_dsd_enable(struct mtk_hdmi *hdmi, bool enable) +{ + mtk_hdmi_mask(hdmi, GRL_AUDIO_CFG, enable ? SACD_SEL : 0, SACD_SEL); +} + +void mtk_hdmi_hw_aud_set_i2s_chan_num(struct mtk_hdmi *hdmi, + enum hdmi_aud_channel_type channel_type, + u8 channel_count) +{ + u8 val_1, val_2, val_3, val_4; + + if (channel_count == 2) { + val_1 = 0x04; + val_2 = 0x50; + } else if (channel_count == 3 || channel_count == 4) { + if (channel_count == 4 && + (channel_type == HDMI_AUD_CHAN_TYPE_3_0_LRS || + channel_type == HDMI_AUD_CHAN_TYPE_4_0)) { + val_1 = 0x14; + } else { + val_1 = 0x0c; + } + val_2 = 0x50; + } else if (channel_count == 6 || channel_count == 5) { + if (channel_count == 6 && + channel_type != HDMI_AUD_CHAN_TYPE_5_1 && + channel_type != HDMI_AUD_CHAN_TYPE_4_1_CLRS) { + val_1 = 0x3c; + val_2 = 0x50; + } else { + val_1 = 0x1c; + val_2 = 0x50; + } + } else if (channel_count == 8 || channel_count == 7) { + val_1 = 0x3c; + val_2 = 0x50; + } else { + val_1 = 0x04; + val_2 = 0x50; + } + + val_3 = 0xc6; + val_4 = 0xfa; + + mtk_hdmi_write(hdmi, GRL_CH_SW0, val_2); + mtk_hdmi_write(hdmi, GRL_CH_SW1, val_3); + mtk_hdmi_write(hdmi, GRL_CH_SW2, val_4); + mtk_hdmi_write(hdmi, GRL_I2S_UV, val_1); +} + +void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi, + enum hdmi_aud_input_type input_type) +{ + u32 val = 0; + + val = mtk_hdmi_read(hdmi, GRL_CFG1); + if (input_type == HDMI_AUD_INPUT_I2S && + (val & CFG1_SPDIF) == CFG1_SPDIF) { + val &= ~CFG1_SPDIF; + } else if (input_type == HDMI_AUD_INPUT_SPDIF && + (val & CFG1_SPDIF) == 0) { + val |= CFG1_SPDIF; + } + mtk_hdmi_write(hdmi, GRL_CFG1, val); +} + +void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi, + u8 *l_chan_status, u8 *r_chan_status, + enum hdmi_audio_sample_frequency + aud_hdmi_fs) +{ + u8 l_status[5]; + u8 r_status[5]; + u8 val = 0; + + l_status[0] = l_chan_status[0]; + l_status[1] = l_chan_status[1]; + l_status[2] = l_chan_status[2]; + r_status[0] = r_chan_status[0]; + r_status[1] = r_chan_status[1]; + r_status[2] = r_chan_status[2]; + + l_status[0] &= ~0x02; + r_status[0] &= ~0x02; + + val = l_chan_status[3] & 0xf0; + switch (aud_hdmi_fs) { + case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: + val |= 0x03; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: + val |= 0x08; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: + val |= 0x0a; + break; + case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: + val |= 0x02; + break; + default: + val |= 0x02; + break; + } + l_status[3] = val; + r_status[3] = val; + + val = l_chan_status[4]; + val |= ((~(l_status[3] & 0x0f)) << 4); + l_status[4] = val; + r_status[4] = val; + + val = l_status[0]; + mtk_hdmi_write(hdmi, GRL_I2S_C_STA0, val); + mtk_hdmi_write(hdmi, GRL_L_STATUS_0, val); + + val = r_status[0]; + mtk_hdmi_write(hdmi, GRL_R_STATUS_0, val); + + val = l_status[1]; + mtk_hdmi_write(hdmi, GRL_I2S_C_STA1, val); + mtk_hdmi_write(hdmi, GRL_L_STATUS_1, val); + + val = r_status[1]; + mtk_hdmi_write(hdmi, GRL_R_STATUS_1, val); + + val = l_status[2]; + mtk_hdmi_write(hdmi, GRL_I2S_C_STA2, val); + mtk_hdmi_write(hdmi, GRL_L_STATUS_2, val); + + val = r_status[2]; + mtk_hdmi_write(hdmi, GRL_R_STATUS_2, val); + + val = l_status[3]; + mtk_hdmi_write(hdmi, GRL_I2S_C_STA3, val); + mtk_hdmi_write(hdmi, GRL_L_STATUS_3, val); + + val = r_status[3]; + mtk_hdmi_write(hdmi, GRL_R_STATUS_3, val); + + val = l_status[4]; + mtk_hdmi_write(hdmi, GRL_I2S_C_STA4, val); + mtk_hdmi_write(hdmi, GRL_L_STATUS_4, val); + + val = r_status[4]; + mtk_hdmi_write(hdmi, GRL_R_STATUS_4, val); + + for (val = 0; val < 19; val++) { + mtk_hdmi_write(hdmi, GRL_L_STATUS_5 + val * 4, 0); + mtk_hdmi_write(hdmi, GRL_R_STATUS_5 + val * 4, 0); + } +} + +void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL); + if (val & MIX_CTRL_SRC_EN) { + val &= ~MIX_CTRL_SRC_EN; + mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val); + usleep_range(255, 512); + val |= MIX_CTRL_SRC_EN; + mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val); + } +} + +void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_MIX_CTRL); + val &= ~MIX_CTRL_SRC_EN; + mtk_hdmi_write(hdmi, GRL_MIX_CTRL, val); + mtk_hdmi_write(hdmi, GRL_SHIFT_L1, 0x00); +} + +void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi, enum hdmi_aud_mclk mclk) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_CFG5); + val &= CFG5_CD_RATIO_MASK; + + switch (mclk) { + case HDMI_AUD_MCLK_128FS: + val |= CFG5_FS128; + break; + case HDMI_AUD_MCLK_256FS: + val |= CFG5_FS256; + break; + case HDMI_AUD_MCLK_384FS: + val |= CFG5_FS384; + break; + case HDMI_AUD_MCLK_512FS: + val |= CFG5_FS512; + break; + case HDMI_AUD_MCLK_768FS: + val |= CFG5_FS768; + break; + default: + val |= CFG5_FS256; + break; + } + mtk_hdmi_write(hdmi, GRL_CFG5, val); +} + +void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi, bool enable) +{ + u32 val; + + val = mtk_hdmi_read(hdmi, GRL_CFG2); + if (enable) + val |= 0x80; + else + val &= ~0x80; + mtk_hdmi_write(hdmi, GRL_CFG2, val); +} + +static void do_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, + enum hdmi_display_color_depth depth, + enum hdmi_audio_sample_frequency freq, + int pix) +{ + unsigned char val[NCTS_BYTES]; + unsigned int temp; + int i = 0; + + mtk_hdmi_write(hdmi, GRL_NCTS, 0); + mtk_hdmi_write(hdmi, GRL_NCTS, 0); + mtk_hdmi_write(hdmi, GRL_NCTS, 0); + memset(val, 0, sizeof(val)); + + if (depth == HDMI_DEEP_COLOR_24BITS) { + for (i = 0; i < NCTS_BYTES; i++) { + if ((freq < 8) && (pix < 9)) + val[i] = HDMI_NCTS[freq - 1][pix][i]; + } + temp = (val[0] << 24) | (val[1] << 16) | + (val[2] << 8) | (val[3]); /* CTS */ + } else { + for (i = 0; i < NCTS_BYTES; i++) { + if ((freq < 7) && (pix < 9)) + val[i] = HDMI_NCTS[freq - 1][pix][i]; + } + + temp = + (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | (val[3]); + + if (depth == HDMI_DEEP_COLOR_30BITS) + temp = (temp >> 2) * 5; + else if (depth == HDMI_DEEP_COLOR_36BITS) + temp = (temp >> 1) * 3; + else if (depth == HDMI_DEEP_COLOR_48BITS) + temp = (temp << 1); + + val[0] = (temp >> 24) & 0xff; + val[1] = (temp >> 16) & 0xff; + val[2] = (temp >> 8) & 0xff; + val[3] = (temp) & 0xff; + } + + for (i = 0; i < NCTS_BYTES; i++) + mtk_hdmi_write(hdmi, GRL_NCTS, val[i]); +} + +void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, + enum hdmi_display_color_depth depth, + enum hdmi_audio_sample_frequency freq, int clock) +{ + int pix = 0; + + switch (clock) { + case 27000: + pix = 0; + break; + case 74175: + pix = 2; + break; + case 74250: + pix = 3; + break; + case 148350: + pix = 4; + break; + case 148500: + pix = 5; + break; + default: + pix = 0; + break; + } + + mtk_hdmi_mask(hdmi, DUMMY_304, AUDIO_I2S_NCTS_SEL_64, + AUDIO_I2S_NCTS_SEL); + do_hdmi_hw_aud_set_ncts(hdmi, depth, freq, pix); +} diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h new file mode 100644 index 0000000..0ced80a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_HDMI_HW_H +#define _MTK_HDMI_HW_H + +#include <linux/types.h> +#include <linux/hdmi.h> +#include "mtk_hdmi.h" + +void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi, bool black); +void mtk_hdmi_hw_aud_mute(struct mtk_hdmi *hdmi, bool mute); +void mtk_hdmi_hw_send_info_frame(struct mtk_hdmi *hdmi, u8 *buffer, u8 len); +void mtk_hdmi_hw_send_aud_packet(struct mtk_hdmi *hdmi, bool enable); +int mtk_hdmi_hw_set_clock(struct mtk_hdmi *hctx, u32 clock); +void mtk_hdmi_hw_config_sys(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_set_deep_color_mode(struct mtk_hdmi *hdmi, + enum hdmi_display_color_depth depth); +void mtk_hdmi_hw_send_av_mute(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_send_av_unmute(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_ncts_enable(struct mtk_hdmi *hdmi, bool on); +void mtk_hdmi_hw_aud_set_channel_swap(struct mtk_hdmi *hdmi, + enum hdmi_aud_channel_swap_type swap); +void mtk_hdmi_hw_aud_raw_data_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_aud_set_bit_num(struct mtk_hdmi *hdmi, + enum hdmi_audio_sample_size bit_num); +void mtk_hdmi_hw_aud_set_high_bitrate(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_phy_aud_dst_normal_double_enable(struct mtk_hdmi *hdmi, + bool enable); +void mtk_hdmi_hw_aud_dst_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_aud_dsd_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_aud_set_i2s_fmt(struct mtk_hdmi *hdmi, + enum hdmi_aud_i2s_fmt i2s_fmt); +void mtk_hdmi_hw_aud_set_i2s_chan_num(struct mtk_hdmi *hdmi, + enum hdmi_aud_channel_type channel_type, + u8 channel_count); +void mtk_hdmi_hw_aud_set_input_type(struct mtk_hdmi *hdmi, + enum hdmi_aud_input_type input_type); +void mtk_hdmi_hw_aud_set_channel_status(struct mtk_hdmi *hdmi, + u8 *l_chan_status, u8 *r_chan_staus, + enum hdmi_audio_sample_frequency + aud_hdmi_fs); +void mtk_hdmi_hw_aud_src_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_aud_set_mclk(struct mtk_hdmi *hdmi, enum hdmi_aud_mclk mclk); +void mtk_hdmi_hw_aud_src_off(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_aud_src_reenable(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_aud_aclk_inv_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_aud_set_ncts(struct mtk_hdmi *hdmi, + enum hdmi_display_color_depth depth, + enum hdmi_audio_sample_frequency freq, + int clock); +bool mtk_hdmi_hw_is_hpd_high(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_reset(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_enable_notice(struct mtk_hdmi *hdmi, bool enable_notice); +void mtk_hdmi_hw_write_int_mask(struct mtk_hdmi *hdmi, u32 int_mask); +void mtk_hdmi_hw_enable_dvi_mode(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_ncts_auto_write_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_msic_setting(struct mtk_hdmi *hdmi, + struct drm_display_mode *mode); +void mtk_hdmi_hw_1p4_version_enable(struct mtk_hdmi *hdmi, bool enable); +void mtk_hdmi_hw_htplg_irq_enable(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_htplg_irq_disable(struct mtk_hdmi *hdmi); +void mtk_hdmi_hw_clear_htplg_irq(struct mtk_hdmi *hdmi); + +#endif /* _MTK_HDMI_HW_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_phy.c b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.c new file mode 100644 index 0000000..73ac9be --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include "mtk_hdmi.h" +#include "mtk_hdmi_phy_regs.h" + +struct mtk_hdmi_phy { + void __iomem *regs; + struct clk *pll_ref_clk; + u8 drv_imp_clk; + u8 drv_imp_d2; + u8 drv_imp_d1; + u8 drv_imp_d0; + u32 ibias; + u32 ibias_up; +}; + +static const u8 PREDIV[3][4] = { + {0x0, 0x0, 0x0, 0x0}, /* 27Mhz */ + {0x1, 0x1, 0x1, 0x1}, /* 74Mhz */ + {0x1, 0x1, 0x1, 0x1} /* 148Mhz */ +}; + +static const u8 TXDIV[3][4] = { + {0x3, 0x3, 0x3, 0x2}, /* 27Mhz */ + {0x2, 0x1, 0x1, 0x1}, /* 74Mhz */ + {0x1, 0x0, 0x0, 0x0} /* 148Mhz */ +}; + +static const u8 FBKSEL[3][4] = { + {0x1, 0x1, 0x1, 0x1}, /* 27Mhz */ + {0x1, 0x0, 0x1, 0x1}, /* 74Mhz */ + {0x1, 0x0, 0x1, 0x1} /* 148Mhz */ +}; + +static const u8 FBKDIV[3][4] = { + {19, 24, 29, 19}, /* 27Mhz */ + {19, 24, 14, 19}, /* 74Mhz */ + {19, 24, 14, 19} /* 148Mhz */ +}; + +static const u8 DIVEN[3][4] = { + {0x2, 0x1, 0x1, 0x2}, /* 27Mhz */ + {0x2, 0x2, 0x2, 0x2}, /* 74Mhz */ + {0x2, 0x2, 0x2, 0x2} /* 148Mhz */ +}; + +static const u8 HTPLLBP[3][4] = { + {0xc, 0xc, 0x8, 0xc}, /* 27Mhz */ + {0xc, 0xf, 0xf, 0xc}, /* 74Mhz */ + {0xc, 0xf, 0xf, 0xc} /* 148Mhz */ +}; + +static const u8 HTPLLBC[3][4] = { + {0x2, 0x3, 0x3, 0x2}, /* 27Mhz */ + {0x2, 0x3, 0x3, 0x2}, /* 74Mhz */ + {0x2, 0x3, 0x3, 0x2} /* 148Mhz */ +}; + +static const u8 HTPLLBR[3][4] = { + {0x1, 0x1, 0x0, 0x1}, /* 27Mhz */ + {0x1, 0x2, 0x2, 0x1}, /* 74Mhz */ + {0x1, 0x2, 0x2, 0x1} /* 148Mhz */ +}; + +static void mtk_hdmi_phy_mask(struct mtk_hdmi_phy *hdmi_phy, u32 offset, + u32 val, u32 mask) +{ + u32 tmp = readl(hdmi_phy->regs + offset) & ~mask; + + tmp |= (val & mask); + writel(tmp, hdmi_phy->regs + offset); +} + +static void mtk_hdmi_phy_enable_tmds(struct mtk_hdmi_phy *hdmi_phy) +{ + clk_prepare_enable(hdmi_phy->pll_ref_clk); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_AUTOK_EN, + RG_HDMITX_PLL_AUTOK_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV, + RG_HDMITX_PLL_POSDIV); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_MHLCK_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_EN, + RG_HDMITX_PLL_BIAS_EN); + usleep_range(100, 150); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_EN, + RG_HDMITX_PLL_EN); + usleep_range(100, 150); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_BIAS_LPF_EN, + RG_HDMITX_PLL_BIAS_LPF_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, RG_HDMITX_PLL_TXDIV_EN, + RG_HDMITX_PLL_TXDIV_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_SER_EN, + RG_HDMITX_SER_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_PRD_EN, + RG_HDMITX_PRD_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_DRV_EN, + RG_HDMITX_DRV_EN); + usleep_range(100, 150); +} + +static void mtk_hdmi_phy_disable_tmds(struct mtk_hdmi_phy *hdmi_phy) +{ + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_DRV_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_PRD_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_SER_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_TXDIV_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_BIAS_LPF_EN); + usleep_range(100, 150); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, 0, RG_HDMITX_PLL_EN); + usleep_range(100, 150); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_BIAS_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, 0, RG_HDMITX_PLL_POSDIV); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, 0, RG_HDMITX_PLL_AUTOK_EN); + usleep_range(100, 150); + clk_disable_unprepare(hdmi_phy->pll_ref_clk); +} + +void mtk_hdmi_phy_set_pll(struct phy *phy, u32 clock, + enum hdmi_display_color_depth depth) +{ + struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + unsigned int ibias; + unsigned int pix; + + if (clock <= 74000000) + clk_set_rate(hdmi_phy->pll_ref_clk, clock); + else + clk_set_rate(hdmi_phy->pll_ref_clk, clock / 2); + + dev_dbg(&phy->dev, "Want REF %u Hz, pixel clock %lu Hz\n", + clock, clk_get_rate(hdmi_phy->pll_ref_clk)); + + if (clock <= 27000000) + pix = 0; + else if (clock <= 74000000) + pix = 1; + else + pix = 2; + + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, + ((PREDIV[pix][depth]) << PREDIV_SHIFT), + RG_HDMITX_PLL_PREDIV); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, RG_HDMITX_PLL_POSDIV, + RG_HDMITX_PLL_POSDIV); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, + (0x1 << PLL_IC_SHIFT) | (0x1 << PLL_IR_SHIFT), + RG_HDMITX_PLL_IC | RG_HDMITX_PLL_IR); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, + ((TXDIV[pix][depth]) << PLL_TXDIV_SHIFT), + RG_HDMITX_PLL_TXDIV); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, + ((FBKSEL[pix][depth]) << PLL_FBKSEL_SHIFT) | + ((FBKDIV[pix][depth]) << PLL_FBKDIV_SHIFT), + RG_HDMITX_PLL_FBKSEL | RG_HDMITX_PLL_FBKDIV); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON1, + ((DIVEN[pix][depth]) << PLL_DIVEN_SHIFT), + RG_HDMITX_PLL_DIVEN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON0, + ((HTPLLBP[pix][depth]) << PLL_BP_SHIFT) | + ((HTPLLBC[pix][depth]) << PLL_BC_SHIFT) | + ((HTPLLBR[pix][depth]) << PLL_BR_SHIFT), + RG_HDMITX_PLL_BP | RG_HDMITX_PLL_BC | + RG_HDMITX_PLL_BR); + + if ((pix == 2) && (depth != HDMI_DEEP_COLOR_24BITS)) { + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, RG_HDMITX_PRD_IMP_EN, + RG_HDMITX_PRD_IMP_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON4, + (0x6 << PRD_IBIAS_CLK_SHIFT) | + (0x6 << PRD_IBIAS_D2_SHIFT) | + (0x6 << PRD_IBIAS_D1_SHIFT) | + (0x6 << PRD_IBIAS_D0_SHIFT), + RG_HDMITX_PRD_IBIAS_CLK | + RG_HDMITX_PRD_IBIAS_D2 | + RG_HDMITX_PRD_IBIAS_D1 | + RG_HDMITX_PRD_IBIAS_D0); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, + 0xf << DRV_IMP_EN_SHIFT, + RG_HDMITX_DRV_IMP_EN); + ibias = hdmi_phy->ibias_up; + } else { + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, 0, RG_HDMITX_PRD_IMP_EN); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON4, + (0x3 << PRD_IBIAS_CLK_SHIFT) | + (0x3 << PRD_IBIAS_D2_SHIFT) | + (0x3 << PRD_IBIAS_D1_SHIFT) | + (0x3 << PRD_IBIAS_D0_SHIFT), + RG_HDMITX_PRD_IBIAS_CLK | + RG_HDMITX_PRD_IBIAS_D2 | + RG_HDMITX_PRD_IBIAS_D1 | + RG_HDMITX_PRD_IBIAS_D0); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON3, + (0x0 << DRV_IMP_EN_SHIFT), + RG_HDMITX_DRV_IMP_EN); + ibias = hdmi_phy->ibias; + } + + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON6, + (hdmi_phy->drv_imp_clk << DRV_IMP_CLK_SHIFT) | + (hdmi_phy->drv_imp_d2 << DRV_IMP_D2_SHIFT) | + (hdmi_phy->drv_imp_d1 << DRV_IMP_D1_SHIFT) | + (hdmi_phy->drv_imp_d0 << DRV_IMP_D0_SHIFT), + RG_HDMITX_DRV_IMP_CLK | RG_HDMITX_DRV_IMP_D2 | + RG_HDMITX_DRV_IMP_D1 | RG_HDMITX_DRV_IMP_D0); + mtk_hdmi_phy_mask(hdmi_phy, HDMI_CON5, + (ibias << DRV_IBIAS_CLK_SHIFT) | + (ibias << DRV_IBIAS_D2_SHIFT) | + (ibias << DRV_IBIAS_D1_SHIFT) | + (ibias << DRV_IBIAS_D0_SHIFT), + RG_HDMITX_DRV_IBIAS_CLK | RG_HDMITX_DRV_IBIAS_D2 | + RG_HDMITX_DRV_IBIAS_D1 | RG_HDMITX_DRV_IBIAS_D0); +} + +static int mtk_hdmi_phy_power_on(struct phy *phy) +{ + struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + + mtk_hdmi_phy_enable_tmds(hdmi_phy); + + return 0; +} + +static int mtk_hdmi_phy_power_off(struct phy *phy) +{ + struct mtk_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + + mtk_hdmi_phy_disable_tmds(hdmi_phy); + + return 0; +} + +static struct phy_ops mtk_hdmi_phy_ops = { + .power_on = mtk_hdmi_phy_power_on, + .power_off = mtk_hdmi_phy_power_off, + .owner = THIS_MODULE, +}; + +static int mtk_hdmi_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_hdmi_phy *hdmi_phy; + struct resource *mem; + struct phy *phy; + struct phy_provider *phy_provider; + int ret; + + hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL); + if (!hdmi_phy) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi_phy->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(hdmi_phy->regs)) { + ret = PTR_ERR(hdmi_phy->regs); + dev_err(dev, "Failed to get memory resource: %d\n", ret); + return ret; + } + + hdmi_phy->pll_ref_clk = devm_clk_get(dev, "pll_ref"); + if (IS_ERR(hdmi_phy->pll_ref_clk)) { + ret = PTR_ERR(hdmi_phy->pll_ref_clk); + dev_err(&pdev->dev, "Failed to get PLL reference clock: %d\n", + ret); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "mediatek,ibias", + &hdmi_phy->ibias); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get ibias: %d\n", ret); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "mediatek,ibias_up", + &hdmi_phy->ibias_up); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get ibias up: %d\n", ret); + return ret; + } + + dev_info(dev, "Using default TX DRV impedance: 4.2k/36\n"); + hdmi_phy->drv_imp_clk = 0x30; + hdmi_phy->drv_imp_d2 = 0x30; + hdmi_phy->drv_imp_d1 = 0x30; + hdmi_phy->drv_imp_d0 = 0x30; + + phy = devm_phy_create(dev, NULL, &mtk_hdmi_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create HDMI PHY\n"); + return PTR_ERR(phy); + } + phy_set_drvdata(phy, hdmi_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int mtk_hdmi_phy_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id mtk_hdmi_phy_match[] = { + { .compatible = "mediatek,mt8173-hdmi-phy", }, + {}, +}; + +struct platform_driver mtk_hdmi_phy_driver = { + .probe = mtk_hdmi_phy_probe, + .remove = mtk_hdmi_phy_remove, + .driver = { + .name = "mediatek-hdmi-phy", + .of_match_table = mtk_hdmi_phy_match, + }, +}; + +MODULE_AUTHOR("Jie Qiu jie.qiu@mediatek.com"); +MODULE_DESCRIPTION("MediaTek MT8173 HDMI PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_phy.h b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.h new file mode 100644 index 0000000..ff3e95a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_phy.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_HDMI_PHY_H +#define _MTK_HDMI_PHY_H + +void mtk_hdmi_phy_set_pll(struct phy *phy, u32 clock, + enum hdmi_display_color_depth depth); + +#endif /* _MTK_HDMI_PHY_H */ diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h new file mode 100644 index 0000000..314bb9f --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_phy_regs.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_HDMI_PHY_REGS_H +#define _MTK_HDMI_PHY_REGS_H + +#define HDMI_CON0 0x00 +#define RG_HDMITX_PLL_EN BIT(31) +#define RG_HDMITX_PLL_FBKDIV (0x7f << 24) +#define PLL_FBKDIV_SHIFT 24 +#define RG_HDMITX_PLL_FBKSEL (0x3 << 22) +#define PLL_FBKSEL_SHIFT 22 +#define RG_HDMITX_PLL_PREDIV (0x3 << 20) +#define PREDIV_SHIFT 20 +#define RG_HDMITX_PLL_POSDIV (0x3 << 18) +#define POSDIV_SHIFT 18 +#define RG_HDMITX_PLL_RST_DLY (0x3 << 16) +#define RG_HDMITX_PLL_IR (0xf << 12) +#define PLL_IR_SHIFT 12 +#define RG_HDMITX_PLL_IC (0xf << 8) +#define PLL_IC_SHIFT 8 +#define RG_HDMITX_PLL_BP (0xf << 4) +#define PLL_BP_SHIFT 4 +#define RG_HDMITX_PLL_BR (0x3 << 2) +#define PLL_BR_SHIFT 2 +#define RG_HDMITX_PLL_BC (0x3 << 0) +#define PLL_BC_SHIFT 0 +#define HDMI_CON1 0x04 +#define RG_HDMITX_PLL_DIVEN (0x7 << 29) +#define PLL_DIVEN_SHIFT 29 +#define RG_HDMITX_PLL_AUTOK_EN BIT(28) +#define RG_HDMITX_PLL_AUTOK_KF (0x3 << 26) +#define RG_HDMITX_PLL_AUTOK_KS (0x3 << 24) +#define RG_HDMITX_PLL_AUTOK_LOAD BIT(23) +#define RG_HDMITX_PLL_BAND (0x3f << 16) +#define RG_HDMITX_PLL_REF_SEL BIT(15) +#define RG_HDMITX_PLL_BIAS_EN BIT(14) +#define RG_HDMITX_PLL_BIAS_LPF_EN BIT(13) +#define RG_HDMITX_PLL_TXDIV_EN BIT(12) +#define RG_HDMITX_PLL_TXDIV (0x3 << 10) +#define PLL_TXDIV_SHIFT 10 +#define RG_HDMITX_PLL_LVROD_EN BIT(9) +#define RG_HDMITX_PLL_MONVC_EN BIT(8) +#define RG_HDMITX_PLL_MONCK_EN BIT(7) +#define RG_HDMITX_PLL_MONREF_EN BIT(6) +#define RG_HDMITX_PLL_TST_EN BIT(5) +#define RG_HDMITX_PLL_TST_CK_EN BIT(4) +#define RG_HDMITX_PLL_TST_SEL (0xf << 0) +#define HDMI_CON2 0x08 +#define RGS_HDMITX_PLL_AUTOK_BAND (0x7f << 8) +#define RGS_HDMITX_PLL_AUTOK_FAIL BIT(1) +#define RG_HDMITX_EN_TX_CKLDO BIT(0) +#define HDMI_CON3 0x0c +#define RG_HDMITX_SER_EN (0xf << 28) +#define RG_HDMITX_PRD_EN (0xf << 24) +#define RG_HDMITX_PRD_IMP_EN (0xf << 20) +#define RG_HDMITX_DRV_EN (0xf << 16) +#define RG_HDMITX_DRV_IMP_EN (0xf << 12) +#define DRV_IMP_EN_SHIFT 12 +#define RG_HDMITX_MHLCK_FORCE BIT(10) +#define RG_HDMITX_MHLCK_PPIX_EN BIT(9) +#define RG_HDMITX_MHLCK_EN BIT(8) +#define RG_HDMITX_SER_DIN_SEL (0xf << 4) +#define RG_HDMITX_SER_5T1_BIST_EN BIT(3) +#define RG_HDMITX_SER_BIST_TOG BIT(2) +#define RG_HDMITX_SER_DIN_TOG BIT(1) +#define RG_HDMITX_SER_CLKDIG_INV BIT(0) +#define HDMI_CON4 0x10 +#define RG_HDMITX_PRD_IBIAS_CLK (0xf << 24) +#define RG_HDMITX_PRD_IBIAS_D2 (0xf << 16) +#define RG_HDMITX_PRD_IBIAS_D1 (0xf << 8) +#define RG_HDMITX_PRD_IBIAS_D0 (0xf << 0) +#define PRD_IBIAS_CLK_SHIFT 24 +#define PRD_IBIAS_D2_SHIFT 16 +#define PRD_IBIAS_D1_SHIFT 8 +#define PRD_IBIAS_D0_SHIFT 0 +#define HDMI_CON5 0x14 +#define RG_HDMITX_DRV_IBIAS_CLK (0x3f << 24) +#define RG_HDMITX_DRV_IBIAS_D2 (0x3f << 16) +#define RG_HDMITX_DRV_IBIAS_D1 (0x3f << 8) +#define RG_HDMITX_DRV_IBIAS_D0 (0x3f << 0) +#define DRV_IBIAS_CLK_SHIFT 24 +#define DRV_IBIAS_D2_SHIFT 16 +#define DRV_IBIAS_D1_SHIFT 8 +#define DRV_IBIAS_D0_SHIFT 0 +#define HDMI_CON6 0x18 +#define RG_HDMITX_DRV_IMP_CLK (0x3f << 24) +#define RG_HDMITX_DRV_IMP_D2 (0x3f << 16) +#define RG_HDMITX_DRV_IMP_D1 (0x3f << 8) +#define RG_HDMITX_DRV_IMP_D0 (0x3f << 0) +#define DRV_IMP_CLK_SHIFT 24 +#define DRV_IMP_D2_SHIFT 16 +#define DRV_IMP_D1_SHIFT 8 +#define DRV_IMP_D0_SHIFT 0 +#define HDMI_CON7 0x1c +#define RG_HDMITX_MHLCK_DRV_IBIAS (0x1f << 27) +#define RG_HDMITX_SER_DIN (0x3ff << 16) +#define RG_HDMITX_CHLDC_TST (0xf << 12) +#define RG_HDMITX_CHLCK_TST (0xf << 8) +#define RG_HDMITX_RESERVE (0xff << 0) +#define HDMI_CON8 0x20 +#define RGS_HDMITX_2T1_LEV (0xf << 16) +#define RGS_HDMITX_2T1_EDG (0xf << 12) +#define RGS_HDMITX_5T1_LEV (0xf << 8) +#define RGS_HDMITX_5T1_EDG (0xf << 4) +#define RGS_HDMITX_PLUG_TST BIT(0) + +#endif diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h new file mode 100644 index 0000000..de7ee22 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_HDMI_REGS_H +#define _MTK_HDMI_REGS_H + +#define GRL_INT_MASK 0x18 +#define GRL_IFM_PORT 0x188 +#define GRL_CH_SWAP 0x198 +#define LR_SWAP BIT(0) +#define LFE_CC_SWAP BIT(1) +#define LSRS_SWAP BIT(2) +#define RLS_RRS_SWAP BIT(3) +#define LR_STATUS_SWAP BIT(4) +#define GRL_I2S_C_STA0 0x140 +#define GRL_I2S_C_STA1 0x144 +#define GRL_I2S_C_STA2 0x148 +#define GRL_I2S_C_STA3 0x14C +#define GRL_I2S_C_STA4 0x150 +#define GRL_I2S_UV 0x154 +#define GRL_ACP_ISRC_CTRL 0x158 +#define VS_EN BIT(0) +#define ACP_EN BIT(1) +#define ISRC1_EN BIT(2) +#define ISRC2_EN BIT(3) +#define GAMUT_EN BIT(4) +#define GRL_CTS_CTRL 0x160 +#define CTS_CTRL_SOFT BIT(0) +#define GRL_INT 0x14 +#define INT_MDI BIT(0) +#define INT_HDCP BIT(1) +#define INT_FIFO_O BIT(2) +#define INT_FIFO_U BIT(3) +#define INT_IFM_ERR BIT(4) +#define INT_INF_DONE BIT(5) +#define INT_NCTS_DONE BIT(6) +#define INT_CTRL_PKT_DONE BIT(7) +#define GRL_INT_MASK 0x18 +#define GRL_CTRL 0x1C +#define CTRL_GEN_EN BIT(2) +#define CTRL_SPD_EN BIT(3) +#define CTRL_MPEG_EN BIT(4) +#define CTRL_AUDIO_EN BIT(5) +#define CTRL_AVI_EN BIT(6) +#define CTRL_AVMUTE BIT(7) +#define GRL_STATUS 0x20 +#define STATUS_HTPLG BIT(0) +#define STATUS_PORD BIT(1) +#define GRL_DIVN 0x170 +#define NCTS_WRI_ANYTIME BIT(6) +#define GRL_AUDIO_CFG 0x17C +#define AUDIO_ZERO BIT(0) +#define HIGH_BIT_RATE BIT(1) +#define SACD_DST BIT(2) +#define DST_NORMAL_DOUBLE BIT(3) +#define DSD_INV BIT(4) +#define LR_INV BIT(5) +#define LR_MIX BIT(6) +#define SACD_SEL BIT(7) +#define GRL_NCTS 0x184 +#define GRL_CH_SW0 0x18C +#define GRL_CH_SW1 0x190 +#define GRL_CH_SW2 0x194 +#define GRL_INFOFRM_VER 0x19C +#define GRL_INFOFRM_TYPE 0x1A0 +#define GRL_INFOFRM_LNG 0x1A4 +#define GRL_MIX_CTRL 0x1B4 +#define MIX_CTRL_SRC_EN BIT(0) +#define BYPASS_VOLUME BIT(1) +#define MIX_CTRL_FLAT BIT(7) +#define GRL_AOUT_BNUM_SEL 0x1C4 +#define AOUT_24BIT 0x00 +#define AOUT_20BIT 0x02 +#define AOUT_16BIT 0x03 +#define HIGH_BIT_RATE_PACKET_ALIGN (0x3 << 6) +#define GRL_SHIFT_L1 0x1C0 +#define GRL_SHIFT_R2 0x1B0 +#define AUDIO_PACKET_OFF BIT(6) +#define GRL_CFG0 0x24 +#define CFG0_I2S_MODE_RTJ 0x1 +#define CFG0_I2S_MODE_LTJ 0x0 +#define CFG0_I2S_MODE_I2S 0x2 +#define CFG0_I2S_MODE_24BIT 0x00 +#define CFG0_I2S_MODE_16BIT 0x10 +#define GRL_CFG1 0x28 +#define CFG1_EDG_SEL BIT(0) +#define CFG1_SPDIF BIT(1) +#define CFG1_DVI BIT(2) +#define CFG1_HDCP_DEBUG BIT(3) +#define GRL_CFG2 0x2c +#define CFG2_NOTICE_EN BIT(6) +#define MHL_DE_SEL BIT(3) +#define GRL_CFG3 0x30 +#define CFG3_AES_KEY_INDEX_MASK 0x3f +#define CFG3_CONTROL_PACKET_DELAY BIT(6) +#define CFG3_KSV_LOAD_START BIT(7) +#define GRL_CFG4 0x34 +#define CFG4_AES_KEY_LOAD BIT(4) +#define CFG4_AV_UNMUTE_EN BIT(5) +#define CFG4_AV_UNMUTE_SET BIT(6) +#define CFG_MHL_MODE BIT(7) +#define GRL_CFG5 0x38 +#define CFG5_CD_RATIO_MASK 0x8F +#define CFG5_FS128 (0x1 << 4) +#define CFG5_FS256 (0x2 << 4) +#define CFG5_FS384 (0x3 << 4) +#define CFG5_FS512 (0x4 << 4) +#define CFG5_FS768 (0x6 << 4) +#define DUMMY_304 0x304 +#define CHMO_SEL (0x3<<2) +#define CHM1_SEL (0x3<<4) +#define CHM2_SEL (0x3<<6) +#define AUDIO_I2S_NCTS_SEL BIT(1) +#define AUDIO_I2S_NCTS_SEL_64 (1<<1) +#define AUDIO_I2S_NCTS_SEL_128 (0<<1) +#define NEW_GCP_CTRL BIT(0) +#define NEW_GCP_CTRL_MERGE BIT(0) +#define GRL_L_STATUS_0 0x200 +#define GRL_L_STATUS_1 0x204 +#define GRL_L_STATUS_2 0x208 +#define GRL_L_STATUS_3 0x20c +#define GRL_L_STATUS_4 0x210 +#define GRL_L_STATUS_5 0x214 +#define GRL_L_STATUS_6 0x218 +#define GRL_L_STATUS_7 0x21c +#define GRL_L_STATUS_8 0x220 +#define GRL_L_STATUS_9 0x224 +#define GRL_L_STATUS_10 0x228 +#define GRL_L_STATUS_11 0x22c +#define GRL_L_STATUS_12 0x230 +#define GRL_L_STATUS_13 0x234 +#define GRL_L_STATUS_14 0x238 +#define GRL_L_STATUS_15 0x23c +#define GRL_L_STATUS_16 0x240 +#define GRL_L_STATUS_17 0x244 +#define GRL_L_STATUS_18 0x248 +#define GRL_L_STATUS_19 0x24c +#define GRL_L_STATUS_20 0x250 +#define GRL_L_STATUS_21 0x254 +#define GRL_L_STATUS_22 0x258 +#define GRL_L_STATUS_23 0x25c +#define GRL_R_STATUS_0 0x260 +#define GRL_R_STATUS_1 0x264 +#define GRL_R_STATUS_2 0x268 +#define GRL_R_STATUS_3 0x26c +#define GRL_R_STATUS_4 0x270 +#define GRL_R_STATUS_5 0x274 +#define GRL_R_STATUS_6 0x278 +#define GRL_R_STATUS_7 0x27c +#define GRL_R_STATUS_8 0x280 +#define GRL_R_STATUS_9 0x284 +#define GRL_R_STATUS_10 0x288 +#define GRL_R_STATUS_11 0x28c +#define GRL_R_STATUS_12 0x290 +#define GRL_R_STATUS_13 0x294 +#define GRL_R_STATUS_14 0x298 +#define GRL_R_STATUS_15 0x29c +#define GRL_R_STATUS_16 0x2a0 +#define GRL_R_STATUS_17 0x2a4 +#define GRL_R_STATUS_18 0x2a8 +#define GRL_R_STATUS_19 0x2ac +#define GRL_R_STATUS_20 0x2b0 +#define GRL_R_STATUS_21 0x2b4 +#define GRL_R_STATUS_22 0x2b8 +#define GRL_R_STATUS_23 0x2bc +#define GRL_ABIST_CTRL0 0x2D4 +#define GRL_ABIST_CTRL1 0x2D8 +#define ABIST_EN BIT(7) +#define ABIST_DATA_FMT (0x7 << 0) +#define VIDEO_CFG_0 0x380 +#define VIDEO_CFG_1 0x384 +#define VIDEO_CFG_2 0x388 +#define VIDEO_CFG_3 0x38c +#define VIDEO_CFG_4 0x390 +#define VIDEO_SOURCE_SEL BIT(7) +#define NORMAL_PATH (1 << 7) +#define GEN_RGB (0 << 7) + +#define HDMI_SYS_CFG1C 0x000 +#define HDMI_ON BIT(0) +#define HDMI_RST BIT(1) +#define ANLG_ON BIT(2) +#define CFG10_DVI BIT(3) +#define HDMI_TST BIT(3) +#define SYS_KEYMASK1 (0xff << 8) +#define SYS_KEYMASK2 (0xff << 16) +#define AUD_OUTSYNC_EN BIT(24) +#define AUD_OUTSYNC_PRE_EN BIT(25) +#define I2CM_ON BIT(26) +#define E2PROM_TYPE_8BIT BIT(27) +#define MCM_E2PROM_ON BIT(28) +#define EXT_E2PROM_ON BIT(29) +#define HTPLG_PIN_SEL_OFF BIT(30) +#define AES_EFUSE_ENABLE BIT(31) +#define HDMI_SYS_CFG20 0x004 +#define DEEP_COLOR_MODE_MASK (3 << 1) +#define COLOR_8BIT_MODE (0 << 1) +#define COLOR_10BIT_MODE (1 << 1) +#define COLOR_12BIT_MODE (2 << 1) +#define COLOR_16BIT_MODE (3 << 1) +#define DEEP_COLOR_EN BIT(0) +#define HDMI_AUDIO_TEST_SEL BIT(8) +#define HDMI2P0_EN BIT(11) +#define HDMI_OUT_FIFO_EN BIT(16) +#define HDMI_OUT_FIFO_CLK_INV BIT(17) +#define MHL_MODE_ON BIT(28) +#define MHL_PP_MODE BIT(29) +#define MHL_SYNC_AUTO_EN BIT(30) +#define HDMI_PCLK_FREE_RUN BIT(31) + +#endif diff --git a/include/drm/mediatek/mtk_hdmi_audio.h b/include/drm/mediatek/mtk_hdmi_audio.h new file mode 100644 index 0000000..2ae64d8 --- /dev/null +++ b/include/drm/mediatek/mtk_hdmi_audio.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Jie Qiu jie.qiu@mediatek.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MTK_HDMI_AUDIO_H +#define _MTK_HDMI_AUDIO_H + +#include <linux/hdmi.h> +enum hdmi_aud_input_type { + HDMI_AUD_INPUT_I2S = 0, + HDMI_AUD_INPUT_SPDIF, +}; + +enum hdmi_aud_i2s_fmt { + HDMI_I2S_MODE_RJT_24BIT = 0, + HDMI_I2S_MODE_RJT_16BIT, + HDMI_I2S_MODE_LJT_24BIT, + HDMI_I2S_MODE_LJT_16BIT, + HDMI_I2S_MODE_I2S_24BIT, + HDMI_I2S_MODE_I2S_16BIT +}; + +enum hdmi_aud_mclk { + HDMI_AUD_MCLK_128FS, + HDMI_AUD_MCLK_192FS, + HDMI_AUD_MCLK_256FS, + HDMI_AUD_MCLK_384FS, + HDMI_AUD_MCLK_512FS, + HDMI_AUD_MCLK_768FS, + HDMI_AUD_MCLK_1152FS, +}; + +enum hdmi_aud_iec_frame_rate { + HDMI_IEC_32K = 0, + HDMI_IEC_96K, + HDMI_IEC_192K, + HDMI_IEC_768K, + HDMI_IEC_44K, + HDMI_IEC_88K, + HDMI_IEC_176K, + HDMI_IEC_705K, + HDMI_IEC_16K, + HDMI_IEC_22K, + HDMI_IEC_24K, + HDMI_IEC_48K, +}; + +enum hdmi_aud_channel_type { + HDMI_AUD_CHAN_TYPE_1_0 = 0, + HDMI_AUD_CHAN_TYPE_1_1, + HDMI_AUD_CHAN_TYPE_2_0, + HDMI_AUD_CHAN_TYPE_2_1, + HDMI_AUD_CHAN_TYPE_3_0, + HDMI_AUD_CHAN_TYPE_3_1, + HDMI_AUD_CHAN_TYPE_4_0, + HDMI_AUD_CHAN_TYPE_4_1, + HDMI_AUD_CHAN_TYPE_5_0, + HDMI_AUD_CHAN_TYPE_5_1, + HDMI_AUD_CHAN_TYPE_6_0, + HDMI_AUD_CHAN_TYPE_6_1, + HDMI_AUD_CHAN_TYPE_7_0, + HDMI_AUD_CHAN_TYPE_7_1, + HDMI_AUD_CHAN_TYPE_3_0_LRS, + HDMI_AUD_CHAN_TYPE_3_1_LRS, + HDMI_AUD_CHAN_TYPE_4_0_CLRS, + HDMI_AUD_CHAN_TYPE_4_1_CLRS, + HDMI_AUD_CHAN_TYPE_6_1_CS, + HDMI_AUD_CHAN_TYPE_6_1_CH, + HDMI_AUD_CHAN_TYPE_6_1_OH, + HDMI_AUD_CHAN_TYPE_6_1_CHR, + HDMI_AUD_CHAN_TYPE_7_1_LH_RH, + HDMI_AUD_CHAN_TYPE_7_1_LSR_RSR, + HDMI_AUD_CHAN_TYPE_7_1_LC_RC, + HDMI_AUD_CHAN_TYPE_7_1_LW_RW, + HDMI_AUD_CHAN_TYPE_7_1_LSD_RSD, + HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS, + HDMI_AUD_CHAN_TYPE_7_1_LHS_RHS, + HDMI_AUD_CHAN_TYPE_7_1_CS_CH, + HDMI_AUD_CHAN_TYPE_7_1_CS_OH, + HDMI_AUD_CHAN_TYPE_7_1_CS_CHR, + HDMI_AUD_CHAN_TYPE_7_1_CH_OH, + HDMI_AUD_CHAN_TYPE_7_1_CH_CHR, + HDMI_AUD_CHAN_TYPE_7_1_OH_CHR, + HDMI_AUD_CHAN_TYPE_7_1_LSS_RSS_LSR_RSR, + HDMI_AUD_CHAN_TYPE_6_0_CS, + HDMI_AUD_CHAN_TYPE_6_0_CH, + HDMI_AUD_CHAN_TYPE_6_0_OH, + HDMI_AUD_CHAN_TYPE_6_0_CHR, + HDMI_AUD_CHAN_TYPE_7_0_LH_RH, + HDMI_AUD_CHAN_TYPE_7_0_LSR_RSR, + HDMI_AUD_CHAN_TYPE_7_0_LC_RC, + HDMI_AUD_CHAN_TYPE_7_0_LW_RW, + HDMI_AUD_CHAN_TYPE_7_0_LSD_RSD, + HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS, + HDMI_AUD_CHAN_TYPE_7_0_LHS_RHS, + HDMI_AUD_CHAN_TYPE_7_0_CS_CH, + HDMI_AUD_CHAN_TYPE_7_0_CS_OH, + HDMI_AUD_CHAN_TYPE_7_0_CS_CHR, + HDMI_AUD_CHAN_TYPE_7_0_CH_OH, + HDMI_AUD_CHAN_TYPE_7_0_CH_CHR, + HDMI_AUD_CHAN_TYPE_7_0_OH_CHR, + HDMI_AUD_CHAN_TYPE_7_0_LSS_RSS_LSR_RSR, + HDMI_AUD_CHAN_TYPE_8_0_LH_RH_CS, + HDMI_AUD_CHAN_TYPE_UNKNOWN = 0xFF +}; + +enum hdmi_aud_channel_swap_type { + HDMI_AUD_SWAP_LR, + HDMI_AUD_SWAP_LFE_CC, + HDMI_AUD_SWAP_LSRS, + HDMI_AUD_SWAP_RLS_RRS, + HDMI_AUD_SWAP_LR_STATUS, +}; + +struct hdmi_audio_param { + enum hdmi_audio_coding_type aud_codec; + enum hdmi_audio_sample_frequency aud_hdmi_fs; + enum hdmi_audio_sample_size aud_sampe_size; + enum hdmi_aud_input_type aud_input_type; + enum hdmi_aud_i2s_fmt aud_i2s_fmt; + enum hdmi_aud_mclk aud_mclk; + enum hdmi_aud_iec_frame_rate iec_frame_fs; + enum hdmi_aud_channel_type aud_input_chan_type; + u8 hdmi_l_channel_state[6]; + u8 hdmi_r_channel_state[6]; +}; + +struct mtk_hdmi; + +struct mtk_hdmi_audio_data { + int irq; + struct mtk_hdmi *mtk_hdmi; + int (*detect_dvi_monitor)(struct mtk_hdmi *hctx); + int (*hpd_detect)(struct mtk_hdmi *hctx); + void (*enable)(struct mtk_hdmi *hctx); + void (*disable)(struct mtk_hdmi *hctx); + int (*set_audio_param)(struct mtk_hdmi *hctx, + struct hdmi_audio_param *param); +}; + +#endif /* _MTK_HDMI_AUDIO_H */
From: Jie Qiu jie.qiu@mediatek.com
MT8173 HDMI hardware has a output control bit to enable/disable HDMI output. Because of security reason, so this bit can ONLY be controlled in ARM supervisor mode. Now the only way to enter ARM supervisor is the ARM trusted firmware. So atf provides a API for HDMI driver to call to setup this HDMI control bit to enable HDMI output in supervisor mode.
Signed-off-by: Jie Qiu jie.qiu@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- drivers/gpu/drm/mediatek/mtk_hdmi_hw.c | 11 +++++++++++ drivers/gpu/drm/mediatek/mtk_hdmi_regs.h | 1 + 2 files changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c index 6e91706..fbf6ecb 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_hw.c @@ -19,8 +19,15 @@ #include <linux/delay.h> #include <linux/hdmi.h> #include <linux/io.h> +#include <linux/psci.h> #include <linux/regmap.h>
+static int (*invoke_psci_fn)(u64, u64, u64, u64); +typedef int (*psci_initcall_t)(const struct device_node *); + +asmlinkage int __invoke_psci_fn_hvc(u64, u64, u64, u64); +asmlinkage int __invoke_psci_fn_smc(u64, u64, u64, u64); + static u32 mtk_hdmi_read(struct mtk_hdmi *hdmi, u32 offset) { return readl(hdmi->regs + offset); @@ -170,6 +177,10 @@ void mtk_hdmi_hw_vid_black(struct mtk_hdmi *hdmi,
void mtk_hdmi_hw_make_reg_writable(struct mtk_hdmi *hdmi, bool enable) { + invoke_psci_fn = __invoke_psci_fn_smc; + invoke_psci_fn(MTK_SIP_SET_AUTHORIZED_SECURE_REG, + 0x14000904, 0x80000000, 0); + regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG20, HDMI_PCLK_FREE_RUN, enable ? HDMI_PCLK_FREE_RUN : 0); regmap_update_bits(hdmi->sys_regmap, hdmi->sys_offset + HDMI_SYS_CFG1C, diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h index de7ee22..8d7d60a 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs.h @@ -218,4 +218,5 @@ #define MHL_SYNC_AUTO_EN BIT(30) #define HDMI_PCLK_FREE_RUN BIT(31)
+#define MTK_SIP_SET_AUTHORIZED_SECURE_REG 0x82000001 #endif
From: CK Hu ck.hu@mediatek.com
This patch adds the device nodes for the DISP function blocks comprising the display subsystem.
--- TODO: - The power-domain property should be added to all blocks that are in the MM power domain. - The iommus property should be removed from the mmsys node.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: Cawa Cheng cawa.cheng@mediatek.com Signed-off-by: Jie Qiu jie.qiu@mediatek.com Signed-off-by: Daniel Kurtz djkurtz@chromium.org Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- arch/arm64/boot/dts/mediatek/mt8173.dtsi | 211 +++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi index 85ec24f..9874ab1 100644 --- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi @@ -26,6 +26,23 @@ #address-cells = <2>; #size-cells = <2>;
+ aliases { + ovl0 = &ovl0; + ovl1 = &ovl1; + rdma0 = &rdma0; + rdma1 = &rdma1; + rdma2 = &rdma2; + wdma0 = &wdma0; + wdma1 = &wdma1; + color0 = &color0; + color1 = &color1; + split0 = &split0; + split1 = &split1; + dpi0 = &dpi0; + dsi0 = &dsi0; + dsi1 = &dsi1; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -284,6 +301,18 @@ #clock-cells = <1>; };
+ mipi_tx0: mipi-dphy@10215000 { + compatible = "mediatek,mt8173-mipi-tx"; + reg = <0 0x10215000 0 0x1000>; + #phy-cells = <0>; + }; + + mipi_tx1: mipi-dphy@10216000 { + compatible = "mediatek,mt8173-mipi-tx"; + reg = <0 0x10216000 0 0x1000>; + #phy-cells = <0>; + }; + gic: interrupt-controller@10220000 { compatible = "arm,gic-400"; #interrupt-cells = <3>; @@ -417,6 +446,14 @@ status = "disabled"; };
+ hdmiddc0: i2c@11012000 { + compatible = "mediatek,mt8173-hdmi-ddc"; + interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>; + reg = <0 0x11012000 0 0x1C>; + clocks = <&pericfg CLK_PERI_I2C5>; + clock-names = "ddc-i2c"; + }; + i2c6: i2c6@11013000 { compatible = "mediatek,mt8173-i2c"; reg = <0 0x11013000 0 0x70>, @@ -553,7 +590,167 @@ mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; + power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>; #clock-cells = <1>; + + /* FIXME - remove iommus here */ + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_OVL0>, + <&iommu M4U_LARB4_ID M4U_PORT_DISP_OVL1>; + }; + + ovl0: ovl@1400c000 { + compatible = "mediatek,mt8173-disp-ovl"; + reg = <0 0x1400c000 0 0x1000>; + interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_OVL0>; + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_OVL0>; + mediatek,larb = <&larb0>; + }; + + ovl1: ovl@1400d000 { + compatible = "mediatek,mt8173-disp-ovl"; + reg = <0 0x1400d000 0 0x1000>; + interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_OVL1>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_OVL1>; + mediatek,larb = <&larb4>; + }; + + rdma0: rdma@1400e000 { + compatible = "mediatek,mt8173-disp-rdma"; + reg = <0 0x1400e000 0 0x1000>; + interrupts = <GIC_SPI 182 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_RDMA0>; + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_RDMA0>; + mediatek,larb = <&larb0>; + }; + + rdma1: rdma@1400f000 { + compatible = "mediatek,mt8173-disp-rdma"; + reg = <0 0x1400f000 0 0x1000>; + interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_RDMA1>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_RDMA1>; + mediatek,larb = <&larb4>; + }; + + rdma2: rdma@14010000 { + compatible = "mediatek,mt8173-disp-rdma"; + reg = <0 0x14010000 0 0x1000>; + interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_RDMA2>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_RDMA2>; + mediatek,larb = <&larb4>; + }; + + wdma0: wdma@14011000 { + compatible = "mediatek,mt8173-disp-wdma"; + reg = <0 0x14011000 0 0x1000>; + interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_WDMA0>; + iommus = <&iommu M4U_LARB0_ID M4U_PORT_DISP_WDMA0>; + mediatek,larb = <&larb0>; + }; + + wdma1: wdma@14012000 { + compatible = "mediatek,mt8173-disp-wdma"; + reg = <0 0x14012000 0 0x1000>; + interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_WDMA1>; + iommus = <&iommu M4U_LARB4_ID M4U_PORT_DISP_WDMA1>; + mediatek,larb = <&larb4>; + }; + + color0: color@14013000 { + compatible = "mediatek,mt8173-disp-color"; + reg = <0 0x14013000 0 0x1000>; + interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_COLOR0>; + }; + + color1: color@14014000 { + compatible = "mediatek,mt8173-disp-color"; + reg = <0 0x14014000 0 0x1000>; + interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_COLOR1>; + }; + + aal@14015000 { + compatible = "mediatek,mt8173-disp-aal"; + reg = <0 0x14015000 0 0x1000>; + interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_AAL>; + }; + + gamma@14016000 { + compatible = "mediatek,mt8173-disp-gamma"; + reg = <0 0x14016000 0 0x1000>; + interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_GAMMA>; + }; + + merge@14017000 { + compatible = "mediatek,mt8173-disp-merge"; + reg = <0 0x14017000 0 0x1000>; + clocks = <&mmsys CLK_MM_DISP_MERGE>; + }; + + split0: split@14018000 { + compatible = "mediatek,mt8173-disp-split"; + reg = <0 0x14018000 0 0x1000>; + clocks = <&mmsys CLK_MM_DISP_SPLIT0>; + }; + + split1: split@14019000 { + compatible = "mediatek,mt8173-disp-split"; + reg = <0 0x14019000 0 0x1000>; + clocks = <&mmsys CLK_MM_DISP_SPLIT1>; + }; + + ufoe@1401a000 { + compatible = "mediatek,mt8173-disp-ufoe"; + reg = <0 0x1401a000 0 0x1000>; + interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DISP_UFOE>; + }; + + dsi0: dsi@1401b000 { + compatible = "mediatek,mt8173-dsi"; + reg = <0 0x1401b000 0 0x1000>; + interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DSI0_ENGINE>, + <&mmsys CLK_MM_DSI0_DIGITAL>; + clock-names = "engine", "digital"; + phys = <&mipi_tx0>; + phy-names = "dphy"; + }; + + dsi1: dsi@1401c000 { + compatible = "mediatek,mt8173-dsi"; + reg = <0 0x1401c000 0 0x1000>; + interrupts = <GIC_SPI 193 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DSI1_ENGINE>, + <&mmsys CLK_MM_DSI1_DIGITAL>; + clock-names = "engine", "digital"; + phy = <&mipi_tx1>; + phy-names = "dphy"; + status = "disabled"; + }; + + dpi0: dpi@1401d000 { + compatible = "mediatek,mt8173-dpi"; + reg = <0 0x1401d000 0 0x1000>; + interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_DPI_PIXEL>, + <&mmsys CLK_MM_DPI_ENGINE>, + <&apmixedsys CLK_APMIXED_TVDPLL>; + clock-names = "pixel", "engine", "pll"; + + port { + dpi0_out: endpoint { + remote-endpoint = <&hdmi0_in>; + }; + }; };
pwm0: pwm@1401e000 { @@ -578,6 +775,20 @@ status = "disabled"; };
+ mutex: mutex@14020000 { + compatible = "mediatek,mt8173-disp-mutex"; + reg = <0 0x14020000 0 0x1000>; + interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_LOW>; + power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>; + clocks = <&mmsys CLK_MM_MUTEX_32K>; + }; + + od@14023000 { + compatible = "mediatek,mt8173-disp-od"; + reg = <0 0x14023000 0 0x1000>; + clocks = <&mmsys CLK_MM_DISP_OD>; + }; + larb0: larb@14021000 { compatible = "mediatek,mt8173-smi-larb"; reg = <0 0x14021000 0 0x1000>;
From: CK Hu ck.hu@mediatek.com
This patch adds the device nodes for the HDMI encoder, HDMI PHY, and HDMI CEC modules.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: Cawa Cheng cawa.cheng@mediatek.com Signed-off-by: Jie Qiu jie.qiu@mediatek.com Signed-off-by: Daniel Kurtz djkurtz@chromium.org Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- Changes since v4: - Drop mediatek,cec DT property - Add mediatek, prefix to ibias DT properties - Remove ddc-i2c-bus property, that goes into the board specific connector node --- arch/arm64/boot/dts/mediatek/mt8173.dtsi | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+)
diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi index 9874ab1..2eb5ca1 100644 --- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi @@ -199,6 +199,30 @@ <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>;
+ hdmi_pin: xxx { + + /*hdmi htplg pin*/ + pins1 { + pinmux = <MT8173_PIN_21_HTPLG__FUNC_HTPLG>; + input-enable; + bias-pull-down; + }; + + /*hdmi flt 5v pin*/ + pins2 { + pinmux = <MT8173_PIN_42_DSI_TE__FUNC_GPIO42>; + input-enable; + bias-pull-up; + }; + + /*hdmi 5v pin*/ + pins3 { + pinmux = <MT8173_PIN_127_LCM_RST__FUNC_GPIO127>; + output-enable; + bias-pull-up; + }; + }; + i2c0_pins_a: i2c0 { pins1 { pinmux = <MT8173_PIN_45_SDA0__FUNC_SDA0>, @@ -275,6 +299,13 @@ clock-names = "spi", "wrap"; };
+ cec: cec@10013000 { + compatible = "mediatek,mt8173-cec"; + reg = <0 0x10013000 0 0xbc>; + interrupts = <GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>; + clocks = <&infracfg CLK_INFRA_CEC>; + }; + sysirq: intpol-controller@10200620 { compatible = "mediatek,mt8173-sysirq", "mediatek,mt6577-sysirq"; @@ -301,6 +332,16 @@ #clock-cells = <1>; };
+ hdmi_phy: hdmi-phy@10209100 { + compatible = "mediatek,mt8173-hdmi-phy"; + reg = <0 0x10209100 0 0x24>; + clocks = <&apmixedsys CLK_APMIXED_HDMI_REF>; + clock-names = "pll_ref"; + mediatek,ibias = <0xa>; + mediatek,ibias_up = <0x1c>; + #phy-cells = <0>; + }; + mipi_tx0: mipi-dphy@10215000 { compatible = "mediatek,mt8173-mipi-tx"; reg = <0 0x10215000 0 0x1000>; @@ -808,6 +849,36 @@ clock-names = "apb", "smi"; };
+ hdmi0: hdmi@14025000 { + compatible = "mediatek,mt8173-hdmi"; + reg = <0 0x14025000 0 0x400>; + interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_LOW>; + clocks = <&mmsys CLK_MM_HDMI_PIXEL>, + <&mmsys CLK_MM_HDMI_PLLCK>, + <&mmsys CLK_MM_HDMI_AUDIO>, + <&mmsys CLK_MM_HDMI_SPDIF>; + clock-names = "pixel", "pll", "bclk", "spdif"; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_pin>; + phys = <&hdmi_phy>; + phy-names = "hdmi"; + mediatek,syscon-hdmi = <&mmsys 0x900>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + hdmi0_in: endpoint { + remote-endpoint = <&dpi0_out>; + }; + }; + }; + }; + larb4: larb@14027000 { compatible = "mediatek,mt8173-smi-larb"; reg = <0 0x14027000 0 0x1000>;
These muxes are supposed to select a fitting divider after the PLL is already set to the correct rate.
Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- drivers/clk/mediatek/clk-mt8173.c | 4 ++-- drivers/clk/mediatek/clk-mtk.h | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/mediatek/clk-mt8173.c b/drivers/clk/mediatek/clk-mt8173.c index 01f7365..e7b3997 100644 --- a/drivers/clk/mediatek/clk-mt8173.c +++ b/drivers/clk/mediatek/clk-mt8173.c @@ -550,7 +550,7 @@ static const struct mtk_composite top_muxes[] __initconst = { MUX_GATE(CLK_TOP_ATB_SEL, "atb_sel", atb_parents, 0x0090, 16, 2, 23), MUX_GATE(CLK_TOP_VENC_LT_SEL, "venclt_sel", venc_lt_parents, 0x0090, 24, 4, 31), /* CLK_CFG_6 */ - MUX_GATE(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents, 0x00a0, 0, 3, 7), + MUX_GATE_FLAGS(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents, 0x00a0, 0, 3, 7, 0), MUX_GATE(CLK_TOP_IRDA_SEL, "irda_sel", irda_parents, 0x00a0, 8, 2, 15), MUX_GATE(CLK_TOP_CCI400_SEL, "cci400_sel", cci400_parents, 0x00a0, 16, 3, 23), MUX_GATE(CLK_TOP_AUD_1_SEL, "aud_1_sel", aud_1_parents, 0x00a0, 24, 2, 31), @@ -561,7 +561,7 @@ static const struct mtk_composite top_muxes[] __initconst = { MUX_GATE(CLK_TOP_SCAM_SEL, "scam_sel", scam_parents, 0x00b0, 24, 2, 31), /* CLK_CFG_12 */ MUX_GATE(CLK_TOP_SPINFI_IFR_SEL, "spinfi_ifr_sel", spinfi_ifr_parents, 0x00c0, 0, 3, 7), - MUX_GATE(CLK_TOP_HDMI_SEL, "hdmi_sel", hdmi_parents, 0x00c0, 8, 2, 15), + MUX_GATE_FLAGS(CLK_TOP_HDMI_SEL, "hdmi_sel", hdmi_parents, 0x00c0, 8, 2, 15, 0), MUX_GATE(CLK_TOP_DPILVDS_SEL, "dpilvds_sel", dpilvds_parents, 0x00c0, 24, 3, 31), /* CLK_CFG_13 */ MUX_GATE(CLK_TOP_MSDC50_2_H_SEL, "msdc50_2_h_sel", msdc50_2_h_parents, 0x00d0, 0, 3, 7), diff --git a/drivers/clk/mediatek/clk-mtk.h b/drivers/clk/mediatek/clk-mtk.h index f390cb0..1acb046 100644 --- a/drivers/clk/mediatek/clk-mtk.h +++ b/drivers/clk/mediatek/clk-mtk.h @@ -66,7 +66,7 @@ struct mtk_composite { signed char num_parents; };
-#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate) { \ +#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, _flags) { \ .id = _id, \ .name = _name, \ .mux_reg = _reg, \ @@ -77,9 +77,12 @@ struct mtk_composite { .divider_shift = -1, \ .parent_names = _parents, \ .num_parents = ARRAY_SIZE(_parents), \ - .flags = CLK_SET_RATE_PARENT, \ + .flags = _flags, \ }
+#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate) \ + MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, CLK_SET_RATE_PARENT) + #define MUX(_id, _name, _parents, _reg, _shift, _width) { \ .id = _id, \ .name = _name, \
The configurable hdmi_ref output of the PLL block is derived from the tvdpll_594m clock signal via a configurable PLL post-divider. It is used as the PLL reference input to the HDMI PHY module.
Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- drivers/clk/mediatek/clk-mt8173.c | 5 +++++ include/dt-bindings/clock/mt8173-clk.h | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/mediatek/clk-mt8173.c b/drivers/clk/mediatek/clk-mt8173.c index e7b3997..fe7a91b 100644 --- a/drivers/clk/mediatek/clk-mt8173.c +++ b/drivers/clk/mediatek/clk-mt8173.c @@ -901,6 +901,11 @@ static void __init mtk_apmixedsys_init(struct device_node *node) clk_data->clks[cku->id] = clk; }
+ clk = clk_register_divider(NULL, "hdmi_ref", "tvdpll_594m", 0, + base + 0x40, 16, 3, CLK_DIVIDER_POWER_OF_TWO, + NULL); + clk_data->clks[CLK_APMIXED_HDMI_REF] = clk; + r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); if (r) pr_err("%s(): could not register clock provider: %d\n", diff --git a/include/dt-bindings/clock/mt8173-clk.h b/include/dt-bindings/clock/mt8173-clk.h index bf1302e..784e987 100644 --- a/include/dt-bindings/clock/mt8173-clk.h +++ b/include/dt-bindings/clock/mt8173-clk.h @@ -173,7 +173,8 @@ #define CLK_APMIXED_LVDSPLL 13 #define CLK_APMIXED_MSDCPLL2 14 #define CLK_APMIXED_REF2USB_TX 15 -#define CLK_APMIXED_NR_CLK 16 +#define CLK_APMIXED_HDMI_REF 16 +#define CLK_APMIXED_NR_CLK 17
/* INFRA_SYS */
Add an optional ddc-i2c-bus phandle property that points to an I2C master controller that handles the connector DDC pins.
Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- Documentation/devicetree/bindings/video/hdmi-connector.txt | 1 + 1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/video/hdmi-connector.txt b/Documentation/devicetree/bindings/video/hdmi-connector.txt index acd5668..508aee4 100644 --- a/Documentation/devicetree/bindings/video/hdmi-connector.txt +++ b/Documentation/devicetree/bindings/video/hdmi-connector.txt @@ -8,6 +8,7 @@ Required properties: Optional properties: - label: a symbolic name for the connector - hpd-gpios: HPD GPIO number +- ddc-i2c-bus: phandle link to the I2C controller used for DDC EDID probing
Required nodes: - Video port for HDMI input
On Wed, Nov 04, 2015 at 12:45:09PM +0100, Philipp Zabel wrote:
Add an optional ddc-i2c-bus phandle property that points to an I2C master controller that handles the connector DDC pins.
Signed-off-by: Philipp Zabel p.zabel@pengutronix.de
Documentation/devicetree/bindings/video/hdmi-connector.txt | 1 +
This one will have to move to bindings/display/connector/ as well. Otherwise:
Acked-by: Rob Herring robh@kernel.org
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/video/hdmi-connector.txt b/Documentation/devicetree/bindings/video/hdmi-connector.txt index acd5668..508aee4 100644 --- a/Documentation/devicetree/bindings/video/hdmi-connector.txt +++ b/Documentation/devicetree/bindings/video/hdmi-connector.txt @@ -8,6 +8,7 @@ Required properties: Optional properties:
- label: a symbolic name for the connector
- hpd-gpios: HPD GPIO number
+- ddc-i2c-bus: phandle link to the I2C controller used for DDC EDID probing
Required nodes:
- Video port for HDMI input
-- 2.6.1
dri-devel@lists.freedesktop.org