Hi,
this is an update to the May RFC to add MT8173 DRM support. These patches have a build dependency on two other patches on top of v4.3-rc1: https://patchwork.kernel.org/patch/6914941 ("iommu: Implement common IOMMU ops for DMA mapping"), and https://patchwork.kernel.org/patch/6928621 ("memory: mediatek: Add SMI driver").
The device tree bindings are changed to describe the actual hardware blocks, a display-subsystem node ties them together. This driver currently supports a single output stream (OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0) to a MIPI DSI device.
best regards Philipp Zabel
CK Hu (4): 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 drm/mediatek: Add DRM-based framebuffer device
.../bindings/drm/mediatek/mediatek,disp.txt | 131 +++ .../bindings/drm/mediatek/mediatek,dsi.txt | 29 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/mediatek/Kconfig | 28 + drivers/gpu/drm/mediatek/Makefile | 11 + drivers/gpu/drm/mediatek/mtk_drm_crtc.c | 515 +++++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.h | 84 ++ drivers/gpu/drm/mediatek/mtk_drm_ddp.c | 231 ++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.h | 39 + drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 378 +++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 61 ++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 484 +++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.h | 46 + drivers/gpu/drm/mediatek/mtk_drm_dsi.c | 1118 ++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_dsi.h | 51 + drivers/gpu/drm/mediatek/mtk_drm_fb.c | 343 ++++++ drivers/gpu/drm/mediatek/mtk_drm_fb.h | 30 + drivers/gpu/drm/mediatek/mtk_drm_gem.c | 207 ++++ drivers/gpu/drm/mediatek/mtk_drm_gem.h | 56 + drivers/gpu/drm/mediatek/mtk_drm_plane.c | 193 ++++ drivers/gpu/drm/mediatek/mtk_drm_plane.h | 38 + 22 files changed, 4076 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.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_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_dsi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_dsi.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
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 --- .../bindings/drm/mediatek/mediatek,disp.txt | 131 +++++++++++++++++++++ .../bindings/drm/mediatek/mediatek,dsi.txt | 29 +++++ 2 files changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..a3811bd --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt @@ -0,0 +1,131 @@ +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. + +The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem. + +Required properties: + +- compatible: "mediatek,<chip>-disp" +- components: Should contain a list of phandles pointing to the DISP function + block device nodes. +- component-names: Should contain the name of the function block pointed to + by the components phandle of the same index. +- mmsys-config: Should contain a phandle pointing to the MMSYS node. +- disp-mutex: Should contain a phandle pointing to the DISP_MUTEX node. + +Example: + +display-subsystem { + compatible = "mediatek,mt8173-disp"; + components = <&ovl0>, <&rdma0>, <&color0>, <&aal>, + <&ufoe>, <&dsi0>, <&od>; + component-names = "ovl0", "rdma0", "color0", "aal", + "ufoe", "dsi0", "od"; + mmsys-config = <&mmsys>; + disp-mutex = <&mutex>; +}; + +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 + +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-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>-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" +- power-domains: a phandle to DDP power domain node. + +Required properties (DMA function blocks): +- compatible: Should be one of + "mediatek,<chip>-disp-ovl" + "mediatek,<chip>-disp-rdma" +- larb: Should a phandles pointing to the local arbiter device as defined in + Documentation/devicetree/bindings/soc/mediatek/mediatek,smi-larb.txt +- iommus: required a iommu node + +Examples: + +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>; +}; + +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>; +}; + +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>; +}; + +aal: 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: 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: 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>; +}; + +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: od@14023000 { + compatible = "mediatek,mt8173-disp-od"; + reg = <0 0x14023000 0 0x1000>; + clocks = <&mmsys CLK_MM_DISP_OD>; +}; diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt new file mode 100644 index 0000000..e892ef1 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt @@ -0,0 +1,29 @@ +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 +- clocks: device clocks + See Documentation/devicetree/bindings/clock/clock-bindings.txt for details. +- clock-names: must contain "engine" and "digital". + +Example: + +dsi0: dsi@1401b000 { + compatible = "mediatek,mt8173-dsi"; + reg = <0 0x1401b000 0 0x1000>, /* DSI0 */ + <0 0x10215000 0 0x1000>; /* MIPI_TX0 */ + clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>; + clock-names = "engine", "digital"; + + port { + dsi0_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; +};
On Fri, Sep 18, 2015 at 11:11 AM, Philipp Zabel p.zabel@pengutronix.de 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
.../bindings/drm/mediatek/mediatek,disp.txt | 131 +++++++++++++++++++++ .../bindings/drm/mediatek/mediatek,dsi.txt | 29 +++++ 2 files changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..a3811bd --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt @@ -0,0 +1,131 @@ +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.
+The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem.
+Required properties:
+- compatible: "mediatek,<chip>-disp"
+- components: Should contain a list of phandles pointing to the DISP function
- block device nodes.
+- component-names: Should contain the name of the function block pointed to
- by the components phandle of the same index.
NAK. Group these nodes under a parent node, use of-graph or just don't put this into DT. Don't invent a new way.
+- mmsys-config: Should contain a phandle pointing to the MMSYS node. +- disp-mutex: Should contain a phandle pointing to the DISP_MUTEX node.
+Example:
+display-subsystem {
compatible = "mediatek,mt8173-disp";
components = <&ovl0>, <&rdma0>, <&color0>, <&aal>,
<&ufoe>, <&dsi0>, <&od>;
component-names = "ovl0", "rdma0", "color0", "aal",
"ufoe", "dsi0", "od";
mmsys-config = <&mmsys>;
disp-mutex = <&mutex>;
+};
+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
+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-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>-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" +- power-domains: a phandle to DDP power domain node.
+Required properties (DMA function blocks): +- compatible: Should be one of
"mediatek,<chip>-disp-ovl"
"mediatek,<chip>-disp-rdma"
+- larb: Should a phandles pointing to the local arbiter device as defined in
- Documentation/devicetree/bindings/soc/mediatek/mediatek,smi-larb.txt
+- iommus: required a iommu node
+Examples:
+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>;
+};
+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>;
+};
+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>;
+};
+aal: 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: 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: 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>;
+};
+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: od@14023000 {
compatible = "mediatek,mt8173-disp-od";
reg = <0 0x14023000 0 0x1000>;
clocks = <&mmsys CLK_MM_DISP_OD>;
+}; diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt new file mode 100644 index 0000000..e892ef1 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt @@ -0,0 +1,29 @@ +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 +- clocks: device clocks
- See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "engine" and "digital".
This leaves wondering which one is used for DSI bit clock.
+Example:
+dsi0: dsi@1401b000 {
compatible = "mediatek,mt8173-dsi";
reg = <0 0x1401b000 0 0x1000>, /* DSI0 */
<0 0x10215000 0 0x1000>; /* MIPI_TX0 */
clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>;
clock-names = "engine", "digital";
port {
Missing from the binding description.
dsi0_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
+};
2.5.1
Hi Rob,
thank you for the comments.
Am Freitag, den 18.09.2015, 15:33 -0500 schrieb Rob Herring: [...]
+The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem.
+Required properties:
+- compatible: "mediatek,<chip>-disp"
+- components: Should contain a list of phandles pointing to the DISP function
- block device nodes.
+- component-names: Should contain the name of the function block pointed to
- by the components phandle of the same index.
NAK. Group these nodes under a parent node, use of-graph or just don't put this into DT. Don't invent a new way.
Ok. The reason I haven't grouped all the display nodes together in the first place is that they aren't contiguous in the register space. So instead of:
ovl@1400c000 { compatible = "mediatek,mt8173-disp-ovl"; }; ... ufoe@1401a000 { compatible = "mediatek,mt8173-disp-ufoe"; };
pwm@1401e000 { compatible = "mediatek,mt8173-disp-pwm"; };
larb@14021000 { compatible = "mediatek,mt8173-smi-larb"; };
od@14023000 { compatible = "mediatek,mt8173-disp-od"; };
We'd have:
display-subsystem@1400c00 { compatible = "mediatek,mt8173-disp", "simple-bus";
ovl@1400c000 { compatible = "mediatek,mt8173-disp-ovl"; }; ... ufoe@1401a000 { compatible = "mediatek,mt8173-disp-ufoe"; };
od@14023000 { compatible = "mediatek,mt8173-disp-od"; }; };
pwm@1401e000 { compatible = "mediatek,mt8173-disp-pwm"; };
larb@14021000 { compatible = "mediatek,mt8173-smi-larb"; };
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
[...]
diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt new file mode 100644 index 0000000..e892ef1 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt @@ -0,0 +1,29 @@ +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 +- clocks: device clocks
- See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
+- clock-names: must contain "engine" and "digital".
This leaves wondering which one is used for DSI bit clock.
The MIPI_TX0 module controls the MIPI D-PHY. It contains a PLL that provides the bit clock to the DSI module.
From the documentation, it looks to me like the AP_PLL_CON0[6] bit in
the mediatek,mt8173-apmixedsys region gates the 26 MHz reference clock to MIPI_TX. It is enabled by default. Currently there is no gate clock registered for that bit. Can somebody confirm that this gate clock should be added to the mediatek,mt8173-apmixedsys bindings?
+Example:
+dsi0: dsi@1401b000 {
compatible = "mediatek,mt8173-dsi";
reg = <0 0x1401b000 0 0x1000>, /* DSI0 */
<0 0x10215000 0 0x1000>; /* MIPI_TX0 */
Thinking about it, MIPI_TX0 is for PHY control. Should this be moved into its own node and the phy bindings used to let the DSI driver find it?
clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>;
clock-names = "engine", "digital";
port {
Missing from the binding description.
Thanks, I'll fix that next round.
dsi0_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
+};
best regards Philipp
On Mon, Sep 21, 2015 at 3:11 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Hi Rob,
thank you for the comments.
Am Freitag, den 18.09.2015, 15:33 -0500 schrieb Rob Herring: [...]
+The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem.
+Required properties:
+- compatible: "mediatek,<chip>-disp"
+- components: Should contain a list of phandles pointing to the DISP function
- block device nodes.
+- component-names: Should contain the name of the function block pointed to
- by the components phandle of the same index.
NAK. Group these nodes under a parent node, use of-graph or just don't put this into DT. Don't invent a new way.
Ok. The reason I haven't grouped all the display nodes together in the first place is that they aren't contiguous in the register space. So instead of:
ovl@1400c000 { compatible = "mediatek,mt8173-disp-ovl"; }; ... ufoe@1401a000 { compatible = "mediatek,mt8173-disp-ufoe"; }; pwm@1401e000 { compatible = "mediatek,mt8173-disp-pwm"; }; larb@14021000 { compatible = "mediatek,mt8173-smi-larb"; }; od@14023000 { compatible = "mediatek,mt8173-disp-od"; };
We'd have:
display-subsystem@1400c00 { compatible = "mediatek,mt8173-disp", "simple-bus"; ovl@1400c000 { compatible = "mediatek,mt8173-disp-ovl"; }; ... ufoe@1401a000 { compatible = "mediatek,mt8173-disp-ufoe"; }; od@14023000 { compatible = "mediatek,mt8173-disp-od"; }; }; pwm@1401e000 { compatible = "mediatek,mt8173-disp-pwm"; }; larb@14021000 { compatible = "mediatek,mt8173-smi-larb"; };
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
Given what the graph looks like, perhaps. However, do you really need a container node? It only serves to provide a list of nodes (e.g. all the children) to include as components. There are other ways to determine this list. You could find all nodes just searching compatible strings for each component. You just need to bind the drm driver to some other DT node. Is there no node you can pick as the master component?
Rob
Am Mittwoch, den 30.09.2015, 12:13 -0500 schrieb Rob Herring:
On Mon, Sep 21, 2015 at 3:11 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
Given what the graph looks like, perhaps. However, do you really need a container node? It only serves to provide a list of nodes (e.g. all the children) to include as components. There are other ways to determine this list. You could find all nodes just searching compatible strings for each component. You just need to bind the drm driver to some other DT node. Is there no node you can pick as the master component?
There is the mmsys clock-controller node at the top of the MMSYS address space (0x14000000-0x14ffffff):
mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; #clock-cells = <1>; };
Its register space also contains the MMSYS_CONFIG region that controls the multiplexers between the display function blocks, so that would be a good candidate. No driver binds to this node yet, the clocks are registered with CLK_OF_DECLARE.
I'll try to bind to this node and have the driver find sibling nodes using their compatible strings.
thanks Philipp
On Thu, Oct 1, 2015 at 3:59 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Mittwoch, den 30.09.2015, 12:13 -0500 schrieb Rob Herring:
On Mon, Sep 21, 2015 at 3:11 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
Given what the graph looks like, perhaps. However, do you really need a container node? It only serves to provide a list of nodes (e.g. all the children) to include as components. There are other ways to determine this list. You could find all nodes just searching compatible strings for each component. You just need to bind the drm driver to some other DT node. Is there no node you can pick as the master component?
There is the mmsys clock-controller node at the top of the MMSYS address space (0x14000000-0x14ffffff):
mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; #clock-cells = <1>; };
Its register space also contains the MMSYS_CONFIG region that controls the multiplexers between the display function blocks, so that would be a good candidate. No driver binds to this node yet, the clocks are registered with CLK_OF_DECLARE.
I'll try to bind to this node and have the driver find sibling nodes using their compatible strings.
That doesn't seem like a good choice since there are other functions in the block. I was thinking one of the display related blocks like whatever block provides the main crtc functions.
Rob
On Thu, Oct 1, 2015 at 8:58 PM, Rob Herring robh@kernel.org wrote:
On Thu, Oct 1, 2015 at 3:59 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Mittwoch, den 30.09.2015, 12:13 -0500 schrieb Rob Herring:
On Mon, Sep 21, 2015 at 3:11 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
Given what the graph looks like, perhaps. However, do you really need a container node? It only serves to provide a list of nodes (e.g. all the children) to include as components. There are other ways to determine this list. You could find all nodes just searching compatible strings for each component. You just need to bind the drm driver to some other DT node. Is there no node you can pick as the master component?
There is the mmsys clock-controller node at the top of the MMSYS address space (0x14000000-0x14ffffff):
mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; #clock-cells = <1>; };
Its register space also contains the MMSYS_CONFIG region that controls the multiplexers between the display function blocks, so that would be a good candidate. No driver binds to this node yet, the clocks are registered with CLK_OF_DECLARE.
I'll try to bind to this node and have the driver find sibling nodes using their compatible strings.
That doesn't seem like a good choice since there are other functions in the block. I was thinking one of the display related blocks like whatever block provides the main crtc functions.
The two "OVL" nodes correspond to the "crtc" functions of the two display pipes in this SoC, we would setup the display-subsystem node like this:
display-subsystem { compatible = "mediatek,display-subsystem"; ports = <&ovl0>, <&ovl1>; };
And, any node that needs to poke around in the mmsys_config area, like an ovl, could access it using a phandle <&mmsys> to the common "mediatek,mt8173-mmsys" device, like this:
ovl0: ovl@1400c000 { compatible = "mediatek,mt8173-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,mmsys = <&mmsys>; status = "disabled"; };
One idea to reduce the size of the of_graph would be to just model the entrance/exit points from the MM subsystem in dt. So, instead of: ovl0 -> color0 -> aal -> od -> ufoe -> dsi0 -> dsi-edp-bridge ovl1 -> color1 -> gamma0 -> dpi0 -> hdmi
We can do something like this: ovl0 -> dsi0 -> dsi-edp-bridge ovl1 -> dpi0 -> hdmi
But ... this might be too limiting for some applications. So, I think we should just live with the big graph. I would, however, recommend that you move the graph to its own mt8173-display-graph.dtsi file which would be #included into a board specific .dts file.
If you let the board .dts include the graph, then we can possibly shrink the resulting board specific device tree - At first we would make the full graph in one .dtsi as above "mt8173-display-graph.dtsi". This is the full reference graph that will always work.
Then, if a particular board used a fixed configuration, the system integrator could create a smaller graph with just the graph nodes that they care about.
For example, if mt8173-evb only supports a single HDMI output, then perhaps you would just set up the endpoints for this single fixed function display pipe in mt8173-evb-display-graph.dtsi:
&ovl1 { status = "okay"; port { ovl1_to_color1: endpoint@0 { remote-endpoint = <&color1_from_ovl1>; }; }; };
&color1 { status = "okay"; port@0 { color1_from_ovl1: endpoint@1 { remote-endpoint = <&ovl1_to_color1>; }; };
port@1 { color1_to_gamma: endpoint@0 { remote-endpoint = <&gamma_from_color1>; }; }; };
&gamma { status = "okay"; port@0 { gamma_from_color1: endpoint { remote-endpoint = <&color1_to_gamma>; }; };
port@1 { gamma_to_dpi0: endpoint@3 { remote-endpoint = <&dpi0_from_gamma>; }; }; };
&dpi0 { status = "okay"; port@0 { dpi0_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dpi0>; }; };
port@1 { dpi0_to_hdmi: endpoint { remote-endpoint = <&hdmi_from_dpi0>; }; }; };
Notice that this also only enables the nodes that we care about (all of the other nodes would be disabled by default).
I this would allow full flexibility but also drastically cut down on the resulting .dts size.
The individual component drivers would then march down their graphs ensuring that all of the corresponding components have been bound, and then themselves appropriately based on their set of endpoints that are available.
What do you think?
-Dan
Rob _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Am Donnerstag, den 01.10.2015, 22:29 +0800 schrieb Daniel Kurtz:
On Thu, Oct 1, 2015 at 8:58 PM, Rob Herring robh@kernel.org wrote:
I was thinking one of the display related blocks like whatever block provides the main crtc functions.
The two "OVL" nodes correspond to the "crtc" functions of the two display pipes in this SoC, we would setup the display-subsystem node like this:
display-subsystem { compatible = "mediatek,display-subsystem";
Yes, the problem with the ovl nodes is that there are two equivalent ones. Having two equivalent ipu nodes on i.MX6 was the reason to introduce the display-subsystem node in the first place, but I'd very much prefer to avoid it, if possible.
[...]
And, any node that needs to poke around in the mmsys_config area, like an ovl, could access it using a phandle <&mmsys> to the common "mediatek,mt8173-mmsys" device, like this:
ovl0: ovl@1400c000 { compatible = "mediatek,mt8173-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,mmsys = <&mmsys>; status = "disabled"; };
I think the ovl node has no business linking to mmsys_config. It's the drm driver code that sets up the pipeline, including the crtc -> encoder connections
One idea to reduce the size of the of_graph would be to just model the entrance/exit points from the MM subsystem in dt. So, instead of: ovl0 -> color0 -> aal -> od -> ufoe -> dsi0 -> dsi-edp-bridge ovl1 -> color1 -> gamma0 -> dpi0 -> hdmi
We can do something like this: ovl0 -> dsi0 -> dsi-edp-bridge ovl1 -> dpi0 -> hdmi
But ... this might be too limiting for some applications.
I'm already worried about having left out the DISP_PATH0 and DISP_PATH1 multiplexers from my DT graph example. There are so many ways to do this slightly wrong and then having to live with the stable bindings, that I'd rather not put an approximation of the real hardware into the device tree. Now that I've heard it is acceptable for one driver to look for its components by their compatible value, I'm leaning towards not describing the graph in the DT.
So, I think we should just live with the big graph. I would, however, recommend that you move the graph to its own mt8173-display-graph.dtsi file which would be #included into a board specific .dts file.
Separating the graph out into its own include file would help to make the main dtsi more readable, though.
If you let the board .dts include the graph, then we can possibly shrink the resulting board specific device tree - At first we would make the full graph in one .dtsi as above "mt8173-display-graph.dtsi". This is the full reference graph that will always work.
Then, if a particular board used a fixed configuration, the system integrator could create a smaller graph with just the graph nodes that they care about.
For example, if mt8173-evb only supports a single HDMI output, then perhaps you would just set up the endpoints for this single fixed function display pipe in mt8173-evb-display-graph.dtsi:
[...]
Especially for development boards I'd rather keep all possibilites. Sure, if nothing is connected to the DSIs, disable them and drop thier possible connections, but I'd still keep ovl0 for example, because it could be connected to hdmi, even though the driver doesn't support it. I realize this was probably just an example, so yes, I think this is a valid method of reducing device tree size for a given board if necessary.
regards Philipp
On Fri, Oct 2, 2015 at 3:40 PM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Donnerstag, den 01.10.2015, 22:29 +0800 schrieb Daniel Kurtz:
On Thu, Oct 1, 2015 at 8:58 PM, Rob Herring robh@kernel.org wrote:
I was thinking one of the display related blocks like whatever block provides the main crtc functions.
The two "OVL" nodes correspond to the "crtc" functions of the two display pipes in this SoC, we would setup the display-subsystem node like this:
display-subsystem { compatible = "mediatek,display-subsystem";
Yes, the problem with the ovl nodes is that there are two equivalent ones. Having two equivalent ipu nodes on i.MX6 was the reason to introduce the display-subsystem node in the first place, but I'd very much prefer to avoid it, if possible.
Oh, interesting. How is it a problem that there are two equivalent ovl nodes? Why do you prefer to avoid the display-subsystem node?
BTW, what I really meant was for my example was that the 'display-subsystem' 'ports' node lists phandles representing the 'root' nodes for the for the display-subsystem graph. This matches what we did for rk3288.dtsi:
display-subsystem { compatible = "mediatek,display-subsystem"; ports = <&ovl0_out>, <&ovl1_out>; };
[...]
And, any node that needs to poke around in the mmsys_config area, like an ovl, could access it using a phandle <&mmsys> to the common "mediatek,mt8173-mmsys" device, like this:
ovl0: ovl@1400c000 { compatible = "mediatek,mt8173-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,mmsys = <&mmsys>; status = "disabled"; };
I think the ovl node has no business linking to mmsys_config. It's the drm driver code that sets up the pipeline, including the crtc -> encoder connections
My original thinking is that the "ovl" node should link to the mmsys_config since the mmsys_config has the ovl output select registers. An 'ovl' driver would then use the phandle to the syscon to find a regmap to safely access the registers it needs.
One idea to reduce the size of the of_graph would be to just model the entrance/exit points from the MM subsystem in dt. So, instead of: ovl0 -> color0 -> aal -> od -> ufoe -> dsi0 -> dsi-edp-bridge ovl1 -> color1 -> gamma0 -> dpi0 -> hdmi
We can do something like this: ovl0 -> dsi0 -> dsi-edp-bridge ovl1 -> dpi0 -> hdmi
But ... this might be too limiting for some applications.
I'm already worried about having left out the DISP_PATH0 and DISP_PATH1 multiplexers from my DT graph example. There are so many ways to do this slightly wrong and then having to live with the stable bindings, that I'd rather not put an approximation of the real hardware into the device tree. Now that I've heard it is acceptable for one driver to look for its components by their compatible value, I'm leaning towards not describing the graph in the DT.
Sure, it would be interesting to see how that would look, too.
-Dan
Hi Daniel,
Am Freitag, den 02.10.2015, 21:47 +0800 schrieb Daniel Kurtz:
On Fri, Oct 2, 2015 at 3:40 PM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Donnerstag, den 01.10.2015, 22:29 +0800 schrieb Daniel Kurtz:
On Thu, Oct 1, 2015 at 8:58 PM, Rob Herring robh@kernel.org wrote:
I was thinking one of the display related blocks like whatever block provides the main crtc functions.
The two "OVL" nodes correspond to the "crtc" functions of the two display pipes in this SoC, we would setup the display-subsystem node like this:
display-subsystem { compatible = "mediatek,display-subsystem";
Yes, the problem with the ovl nodes is that there are two equivalent ones. Having two equivalent ipu nodes on i.MX6 was the reason to introduce the display-subsystem node in the first place, but I'd very much prefer to avoid it, if possible.
Oh, interesting. How is it a problem that there are two equivalent ovl nodes?
Which one of the two should create the drm device?
Why do you prefer to avoid the display-subsystem node?
Because then we don't have to argue about what it should look like ;)
Mostly because nodes like this don't describe an actual hardware devices, but rather how the hardware designers intended other, actually existing hardware devices to work together (in the best case), or just how the Linux driver developers wanted to use the components together (in the worst case).
If the display-subsystem node is necessary or useful, I won't argue against it. If we can do without, I'll be happier.
BTW, what I really meant was for my example was that the 'display-subsystem' 'ports' node lists phandles representing the 'root' nodes for the for the display-subsystem graph. This matches what we did for rk3288.dtsi:
display-subsystem { compatible = "mediatek,display-subsystem"; ports = <&ovl0_out>, <&ovl1_out>; };
Oh, ok then. I'm worried again that we would be describing what the driver should load, not what the hardware looks like. Note that the ovl_out ports have no significance outside of the display subsystem. In the RK3288 and i.MX6 cases, the 'external' ports of the display core are listed, to which the encoders are connected. On MT8173 this would correspond to:
display-subsystem { compatible = "mediatek,display-subsystem"; ports = <&dsi0_out>, <&dsi1_out>, <&dpi0_out>; };
[...]
I think the ovl node has no business linking to mmsys_config. It's the drm driver code that sets up the pipeline, including the crtc -> encoder connections
My original thinking is that the "ovl" node should link to the mmsys_config since the mmsys_config has the ovl output select registers.
I see. If each function block takes care of the attached SEL input selectors and SOUT/MOUT output selectors, wouldn't all of them have to link to the mmsys_config node, then? That's certainly a possibility, but in this case I feel like having a single, central instance to handle building the paths and arbitrating register access with help of the disp_mutex would be easier to understand.
regards Philipp
Am Donnerstag, den 01.10.2015, 07:58 -0500 schrieb Rob Herring:
On Thu, Oct 1, 2015 at 3:59 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Mittwoch, den 30.09.2015, 12:13 -0500 schrieb Rob Herring:
On Mon, Sep 21, 2015 at 3:11 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
Given what the graph looks like, perhaps. However, do you really need a container node? It only serves to provide a list of nodes (e.g. all the children) to include as components. There are other ways to determine this list. You could find all nodes just searching compatible strings for each component. You just need to bind the drm driver to some other DT node. Is there no node you can pick as the master component?
There is the mmsys clock-controller node at the top of the MMSYS address space (0x14000000-0x14ffffff):
mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; #clock-cells = <1>; };
Its register space also contains the MMSYS_CONFIG region that controls the multiplexers between the display function blocks, so that would be a good candidate. No driver binds to this node yet, the clocks are registered with CLK_OF_DECLARE.
I'll try to bind to this node and have the driver find sibling nodes using their compatible strings.
That doesn't seem like a good choice since there are other functions in the block. I was thinking one of the display related blocks like whatever block provides the main crtc functions.
Why exactly isn't this a good choice? Since all display function blocks are spread throughout the MMSYS address space, using the mmsys driver to create the DRM/component master platform driver that collects its subdevices sounded logical.
Another candidate would be the mutex node, which can synchronize the configuration updates to the function blocks in the MMSYS region to frame borders.
The hardware blocks that most closely correspond to crtc functionality are the two OVL blocks, as Daniel points out. They provide DMA and plane composition. But they are equivalent, and there is no single central node, same as with the two IPUs on i.MX6. Also, I'd like to keep the possibility of not tying OVL blocks to crtc functionality too closely, because there are three more separate RDMA units, at least one of which could be used to implement a third pipe in the future.
Either way, if we go this path (one driver scanning DT for known component driver compatible values), whichever node the master driver binds to is a Linux implementation detail, and it should be completely hidden from the device tree and binding specs.
regards Philipp
On Fri, Oct 2, 2015 at 2:18 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Donnerstag, den 01.10.2015, 07:58 -0500 schrieb Rob Herring:
On Thu, Oct 1, 2015 at 3:59 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Mittwoch, den 30.09.2015, 12:13 -0500 schrieb Rob Herring:
On Mon, Sep 21, 2015 at 3:11 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Note how the display-subsystem node overlaps the larb node. Is that acceptable?
Given what the graph looks like, perhaps. However, do you really need a container node? It only serves to provide a list of nodes (e.g. all the children) to include as components. There are other ways to determine this list. You could find all nodes just searching compatible strings for each component. You just need to bind the drm driver to some other DT node. Is there no node you can pick as the master component?
There is the mmsys clock-controller node at the top of the MMSYS address space (0x14000000-0x14ffffff):
mmsys: clock-controller@14000000 { compatible = "mediatek,mt8173-mmsys", "syscon"; reg = <0 0x14000000 0 0x1000>; #clock-cells = <1>; };
Its register space also contains the MMSYS_CONFIG region that controls the multiplexers between the display function blocks, so that would be a good candidate. No driver binds to this node yet, the clocks are registered with CLK_OF_DECLARE.
I'll try to bind to this node and have the driver find sibling nodes using their compatible strings.
That doesn't seem like a good choice since there are other functions in the block. I was thinking one of the display related blocks like whatever block provides the main crtc functions.
Why exactly isn't this a good choice? Since all display function blocks are spread throughout the MMSYS address space, using the mmsys driver to create the DRM/component master platform driver that collects its subdevices sounded logical.
Given it is a syscon, I was assuming there are other unrelated functions. If everything in it is display related, then it would be a fine choice.
Another candidate would be the mutex node, which can synchronize the configuration updates to the function blocks in the MMSYS region to frame borders.
The hardware blocks that most closely correspond to crtc functionality are the two OVL blocks, as Daniel points out. They provide DMA and plane composition. But they are equivalent, and there is no single central node, same as with the two IPUs on i.MX6.
Just curious, are 2 IPUs represented as 1 or 2 struct drm_device?
Also, I'd like to keep the possibility of not tying OVL blocks to crtc functionality too closely, because there are three more separate RDMA units, at least one of which could be used to implement a third pipe in the future.
Either way, if we go this path (one driver scanning DT for known component driver compatible values), whichever node the master driver binds to is a Linux implementation detail, and it should be completely hidden from the device tree and binding specs.
Agreed.
Rob
Hi Rob,
Am Freitag, den 02.10.2015, 09:24 -0500 schrieb Rob Herring: [...]
I'll try to bind to this node and have the driver find sibling nodes using their compatible strings.
That doesn't seem like a good choice since there are other functions in the block. I was thinking one of the display related blocks like whatever block provides the main crtc functions.
Why exactly isn't this a good choice? Since all display function blocks are spread throughout the MMSYS address space, using the mmsys driver to create the DRM/component master platform driver that collects its subdevices sounded logical.
Given it is a syscon, I was assuming there are other unrelated functions. If everything in it is display related, then it would be a fine choice.
Oh, ok. Next to the display blocks there's also a similar graph of MDP ("multimedia data path") blocks that can perform memory-to-memory scaling and rotation that use the same multiplexers and mutex, and the HDMI encoder controls the FIFO between its parallel output and the HDMI PHY via (separate) registers in the MMSYS_CONFIG region. Still, I think these are sufficiently display related.
Another candidate would be the mutex node, which can synchronize the configuration updates to the function blocks in the MMSYS region to frame borders.
The hardware blocks that most closely correspond to crtc functionality are the two OVL blocks, as Daniel points out. They provide DMA and plane composition. But they are equivalent, and there is no single central node, same as with the two IPUs on i.MX6.
Just curious, are 2 IPUs represented as 1 or 2 struct drm_device?
One. There's bus multiplexer fabric between the IPU outputs and the LVDS/HDMI encoders, so it's all connected.
regards Philipp
Am Freitag, den 18.09.2015, 15:33 -0500 schrieb Rob Herring:
On Fri, Sep 18, 2015 at 11:11 AM, Philipp Zabel p.zabel@pengutronix.de 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
.../bindings/drm/mediatek/mediatek,disp.txt | 131 +++++++++++++++++++++ .../bindings/drm/mediatek/mediatek,dsi.txt | 29 +++++ 2 files changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..a3811bd --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt @@ -0,0 +1,131 @@ +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.
+The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem.
+Required properties:
+- compatible: "mediatek,<chip>-disp"
+- components: Should contain a list of phandles pointing to the DISP function
- block device nodes.
+- component-names: Should contain the name of the function block pointed to
- by the components phandle of the same index.
NAK. Group these nodes under a parent node, use of-graph or just don't put this into DT. Don't invent a new way.
If I connect the DISP nodes using of-graph bindings, the full graph will look somewhat like this (including the currently unused function blocks, all properties but ports and endpoints removed for brevity):
ovl0@1400c000 { port { ovl0_to_color0: endpoint@0 { remote-endpoint = <&color0_from_ovl0>; };
ovl0_to_wdma0: endpoint@1 { remote-endpoint = <&wdma0_from_ovl0>; }; }; };
ovl1@1400d000 { port { ovl1_to_color1: endpoint@0 { remote-endpoint = <&color1_from_ovl1>; };
ovl1_to_wdma1: endpoint@1 { remote-endpoint = <&wdma1_from_ovl1>; }; }; };
rdma0@1400e000 { port@0 { rdma0_from_od: endpoint { remote-endpoint = <&od_to_rdma0>; }; };
port@1 { rdma0_to_ufoe: endpoint@0 { remote-endpoint = <&ufoe_from_rdma0>; };
rdma0_to_split0: endpoint@1 { remote-endpoint = <&split0_from_rdma0>; };
rdma0_to_color0: endpoint@2 { remote-endpoint = <&color0_from_rdma0>; }; }; };
rdma1@1400f000 { port@0 { rdma1_from_gamma: endpoint { remote-endpoint = <&gamma_to_rdma1>; }; };
port@1 { rdma1_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_rdma1>; };
rdma1_to_dsi1: endpoint@1 { remote-endpoint = <&dsi1_from_rdma1>; };
rdma1_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_rdma1>; };
rdma1_to_color1: endpoint@3 { remote-endpoint = <&color1_from_rdma1>; }; }; };
rdma2@14010000 { port { rdma2_to_dsi1: endpoint@0 { remote-endpoint = <&dsi1_from_rdma2>; };
rdma2_to_dpi0: endpoint@1 { remote-endpoint = <&dpi0_from_rdma2>; }; }; };
wdma0@14011000 { port { wdma0_from_ovl0: endpoint@0 { remote-endpoint = <&ovl0_to_wdma0>; };
wdma0_from_od: endpoint@1 { remote-endpoint = <&od_to_wdma0>; };
wdma0_from_ufoe: endpoint@2 { remote-endpoint = <&ufoe_to_wdma0>; }; }; };
wdma1@14012000 { port { wdma1_from_ovl1: endpoint@0 { remote-endpoint = <&ovl1_to_wdma1>; };
wdma1_from_gamma: endpoint@1 { remote-endpoint = <&gamma_to_wdma1>; }; }; };
color0@14013000 { port@0 { color0_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_color0>; };
color0_from_ovl0: endpoint@1 { remote-endpoint = <&ovl0_to_color0>; }; };
port@1 { color0_to_aal: endpoint@0 { remote-endpoint = <&aal_from_color0>; };
color0_to_merge: endpoint@1 { remote-endpoint = <&merge_from_color0>; }; }; };
color1@14014000 { port@0 { color1_from_rdma1: endpoint@0 { remote-endpoint = <&rdma1_to_color1>; };
color1_from_ovl1: endpoint@1 { remote-endpoint = <&ovl1_to_color1>; }; };
port@1 { color1_to_gamma: endpoint@0 { remote-endpoint = <&gamma_from_color1>; };
color1_to_merge: endpoint@1 { remote-endpoint = <&merge_from_color1>; }; }; };
aal@14015000 { port@0 { aal_from_color0: endpoint@0 { remote-endpoint = <&color0_to_aal>; };
aal_from_merge: endpoint@1 { remote-endpoint = <&merge_to_aal>; }; };
port@1 { aal_to_od: endpoint { remote-endpoint = <&od_from_aal>; }; }; };
gamma@14016000 { port@0 { gamma_from_color1: endpoint { remote-endpoint = <&color1_to_gamma>; }; };
port@1 { gamma_to_rdma1: endpoint@0 { remote-endpoint = <&rdma1_from_gamma>; };
gamma_to_dsi0: endpoint@1 { remote-endpoint = <&dsi0_from_gamma>; };
gamma_to_dsi1: endpoint@2 { remote-endpoint = <&dsi1_from_gamma>; };
gamma_to_dpi0: endpoint@3 { remote-endpoint = <&dpi0_from_gamma>; };
gamma_to_wdma1: endpoint@4 { remote-endpoint = <&wdma1_from_gamma>; }; }; };
merge@14017000 { port@0 { merge_from_color0: endpoint { remote-endpoint = <&color0_to_merge>; }; };
port@1 { merge_from_color1: endpoint { remote-endpoint = <&color1_to_merge>; }; };
port@2 { merge_to_aal: endpoint { remote-endpoint = <&aal_from_merge>; }; }; };
split0@14018000 { port@0 { split0_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_split0>; };
split0_from_od: endpoint@1 { remote-endpoint = <&od_to_split0>; }; };
port@1 { split0_to_ufoe: endpoint { remote-endpoint = <&ufoe_from_split0>; }; };
port@2 { split0_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_split0>; };
split0_to_dsi1: endpoint@1 { remote-endpoint = <&dsi1_from_split0>; };
split0_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_split0>; }; }; };
split1@14019000 { port@0 { split1_from_ufoe: endpoint { remote-endpoint = <&ufoe_to_split1>; }; };
port@1 { split1_to_dsi0: endpoint { remote-endpoint = <&dsi0_from_split1>; }; };
port@2 { split1_to_dsi1: endpoint { remote-endpoint = <&dsi1_from_split1>; }; }; };
ufoe@1401a000 { port@0 { ufoe_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_ufoe>; };
ufoe_from_od: endpoint@1 { remote-endpoint = <&od_to_ufoe>; };
ufoe_from_split0: endpoint@2 { remote-endpoint = <&split0_to_ufoe>; }; };
port@1 { ufoe_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_ufoe>; };
ufoe_to_split1: endpoint@1 { remote-endpoint = <&split1_from_ufoe>; };
ufoe_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_ufoe>; };
ufoe_to_wdma0: endpoint@3 { remote-endpoint = <&wdma0_from_ufoe>; };
ufoe_to_dsi1: endpoint@4 { remote-endpoint = <&dsi1_from_ufoe>; }; }; };
dsi0: dsi0@1401b000 { port@0 { dsi0_from_ufoe: endpoint@0 { remote-endpoint = <&ufoe_to_dsi0>; };
dsi0_from_split1: endpoint@1 { remote-endpoint = <&split1_to_dsi0>; };
dsi0_from_rdma1: endpoint@2 { remote-endpoint = <&rdma1_to_dsi0>; };
dsi0_from_gamma: endpoint@3 { remote-endpoint = <&gamma_to_dsi0>; };
dsi0_from_split0: endpoint@4 { remote-endpoint = <&split0_to_dsi0>; }; }; };
dsi1: dsi1@1401c000 { port@0 { dsi1_from_split1: endpoint@0 { remote-endpoint = <&split1_to_dsi1>; };
dsi1_from_rdma1: endpoint@1 { remote-endpoint = <&rdma1_to_dsi1>; };
dsi1_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dsi1>; };
dsi1_from_split0: endpoint@3 { remote-endpoint = <&split0_to_dsi1>; };
dsi1_from_rdma2: endpoint@4 { remote-endpoint = <&rdma2_to_dsi1>; };
dsi1_from_ufoe: endpoint@5 { remote-endpoint = <&ufoe_to_dsi1>; }; }; };
dpi0: dpi0@1401d000 { port@0 { dpi0_from_ufoe: endpoint@0 { remote-endpoint = <&ufoe_to_dpi0>; };
dpi0_from_rdma1: endpoint@1 { remote-endpoint = <&rdma1_to_dpi0>; };
dpi0_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dpi0>; };
dpi0_from_split0: endpoint@3 { remote-endpoint = <&split0_to_dpi0>; };
dpi0_from_rdma2: endpoint@4 { remote-endpoint = <&rdma2_to_dpi0>; }; }; };
od@14023000 { port@0 { od_from_aal: endpoint { remote-endpoint = <&aal_to_od>; }; };
port@1 { od_to_rdma0: endpoint@0 { remote-endpoint = <&rdma0_from_od>; };
od_to_ufoe: endpoint@1 { remote-endpoint = <&ufoe_from_od>; };
od_to_split0: endpoint@2 { remote-endpoint = <&split0_from_od>; };
od_to_wdma0: endpoint@3 { remote-endpoint = <&wdma0_from_od>; }; }; };
Adding the graph to the mediatek oak-rev3 device tree increases the size from currently 33 KiB to 41.5 KiB. As a proponent of the of-graph bindings I certainly like to describe the hardware connections using DT, but I'm not sure if maybe this is a bit verbose.
regards Philipp
On Wed, Sep 30, 2015 at 10:30 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Freitag, den 18.09.2015, 15:33 -0500 schrieb Rob Herring:
On Fri, Sep 18, 2015 at 11:11 AM, Philipp Zabel p.zabel@pengutronix.de 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
.../bindings/drm/mediatek/mediatek,disp.txt | 131 +++++++++++++++++++++ .../bindings/drm/mediatek/mediatek,dsi.txt | 29 +++++ 2 files changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..a3811bd --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt @@ -0,0 +1,131 @@ +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.
+The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem.
+Required properties:
+- compatible: "mediatek,<chip>-disp"
+- components: Should contain a list of phandles pointing to the DISP function
- block device nodes.
+- component-names: Should contain the name of the function block pointed to
- by the components phandle of the same index.
NAK. Group these nodes under a parent node, use of-graph or just don't put this into DT. Don't invent a new way.
If I connect the DISP nodes using of-graph bindings, the full graph will look somewhat like this (including the currently unused function blocks, all properties but ports and endpoints removed for brevity):
ovl0@1400c000 { port { ovl0_to_color0: endpoint@0 { remote-endpoint = <&color0_from_ovl0>; }; ovl0_to_wdma0: endpoint@1 { remote-endpoint = <&wdma0_from_ovl0>; }; }; }; ovl1@1400d000 { port { ovl1_to_color1: endpoint@0 { remote-endpoint = <&color1_from_ovl1>; }; ovl1_to_wdma1: endpoint@1 { remote-endpoint = <&wdma1_from_ovl1>; }; }; }; rdma0@1400e000 { port@0 { rdma0_from_od: endpoint { remote-endpoint = <&od_to_rdma0>; }; }; port@1 { rdma0_to_ufoe: endpoint@0 { remote-endpoint = <&ufoe_from_rdma0>; }; rdma0_to_split0: endpoint@1 { remote-endpoint = <&split0_from_rdma0>; }; rdma0_to_color0: endpoint@2 { remote-endpoint = <&color0_from_rdma0>; }; }; }; rdma1@1400f000 { port@0 { rdma1_from_gamma: endpoint { remote-endpoint = <&gamma_to_rdma1>; }; }; port@1 { rdma1_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_rdma1>; }; rdma1_to_dsi1: endpoint@1 { remote-endpoint = <&dsi1_from_rdma1>; }; rdma1_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_rdma1>; }; rdma1_to_color1: endpoint@3 { remote-endpoint = <&color1_from_rdma1>; }; }; }; rdma2@14010000 { port { rdma2_to_dsi1: endpoint@0 { remote-endpoint = <&dsi1_from_rdma2>; }; rdma2_to_dpi0: endpoint@1 { remote-endpoint = <&dpi0_from_rdma2>; }; }; }; wdma0@14011000 { port { wdma0_from_ovl0: endpoint@0 { remote-endpoint = <&ovl0_to_wdma0>; }; wdma0_from_od: endpoint@1 { remote-endpoint = <&od_to_wdma0>; }; wdma0_from_ufoe: endpoint@2 { remote-endpoint = <&ufoe_to_wdma0>; }; }; }; wdma1@14012000 { port { wdma1_from_ovl1: endpoint@0 { remote-endpoint = <&ovl1_to_wdma1>; }; wdma1_from_gamma: endpoint@1 { remote-endpoint = <&gamma_to_wdma1>; }; }; }; color0@14013000 { port@0 { color0_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_color0>; }; color0_from_ovl0: endpoint@1 { remote-endpoint = <&ovl0_to_color0>; }; }; port@1 { color0_to_aal: endpoint@0 { remote-endpoint = <&aal_from_color0>; }; color0_to_merge: endpoint@1 { remote-endpoint = <&merge_from_color0>; }; }; }; color1@14014000 { port@0 { color1_from_rdma1: endpoint@0 { remote-endpoint = <&rdma1_to_color1>; }; color1_from_ovl1: endpoint@1 { remote-endpoint = <&ovl1_to_color1>; }; }; port@1 { color1_to_gamma: endpoint@0 { remote-endpoint = <&gamma_from_color1>; }; color1_to_merge: endpoint@1 { remote-endpoint = <&merge_from_color1>; }; }; }; aal@14015000 { port@0 { aal_from_color0: endpoint@0 { remote-endpoint = <&color0_to_aal>; }; aal_from_merge: endpoint@1 { remote-endpoint = <&merge_to_aal>; }; }; port@1 { aal_to_od: endpoint { remote-endpoint = <&od_from_aal>; }; }; }; gamma@14016000 { port@0 { gamma_from_color1: endpoint { remote-endpoint = <&color1_to_gamma>; }; }; port@1 { gamma_to_rdma1: endpoint@0 { remote-endpoint = <&rdma1_from_gamma>; }; gamma_to_dsi0: endpoint@1 { remote-endpoint = <&dsi0_from_gamma>; }; gamma_to_dsi1: endpoint@2 { remote-endpoint = <&dsi1_from_gamma>; }; gamma_to_dpi0: endpoint@3 { remote-endpoint = <&dpi0_from_gamma>; }; gamma_to_wdma1: endpoint@4 { remote-endpoint = <&wdma1_from_gamma>; }; }; }; merge@14017000 { port@0 { merge_from_color0: endpoint { remote-endpoint = <&color0_to_merge>; }; }; port@1 { merge_from_color1: endpoint { remote-endpoint = <&color1_to_merge>; }; }; port@2 { merge_to_aal: endpoint { remote-endpoint = <&aal_from_merge>; }; }; }; split0@14018000 { port@0 { split0_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_split0>; }; split0_from_od: endpoint@1 { remote-endpoint = <&od_to_split0>; }; }; port@1 { split0_to_ufoe: endpoint { remote-endpoint = <&ufoe_from_split0>; }; }; port@2 { split0_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_split0>; }; split0_to_dsi1: endpoint@1 { remote-endpoint = <&dsi1_from_split0>; }; split0_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_split0>; }; }; }; split1@14019000 { port@0 { split1_from_ufoe: endpoint { remote-endpoint = <&ufoe_to_split1>; }; }; port@1 { split1_to_dsi0: endpoint { remote-endpoint = <&dsi0_from_split1>; }; }; port@2 { split1_to_dsi1: endpoint { remote-endpoint = <&dsi1_from_split1>; }; }; }; ufoe@1401a000 { port@0 { ufoe_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_ufoe>; }; ufoe_from_od: endpoint@1 { remote-endpoint = <&od_to_ufoe>; }; ufoe_from_split0: endpoint@2 { remote-endpoint = <&split0_to_ufoe>; }; }; port@1 { ufoe_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_ufoe>; }; ufoe_to_split1: endpoint@1 { remote-endpoint = <&split1_from_ufoe>; }; ufoe_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_ufoe>; }; ufoe_to_wdma0: endpoint@3 { remote-endpoint = <&wdma0_from_ufoe>; }; ufoe_to_dsi1: endpoint@4 { remote-endpoint = <&dsi1_from_ufoe>; }; }; }; dsi0: dsi0@1401b000 { port@0 { dsi0_from_ufoe: endpoint@0 { remote-endpoint = <&ufoe_to_dsi0>; }; dsi0_from_split1: endpoint@1 { remote-endpoint = <&split1_to_dsi0>; }; dsi0_from_rdma1: endpoint@2 { remote-endpoint = <&rdma1_to_dsi0>; }; dsi0_from_gamma: endpoint@3 { remote-endpoint = <&gamma_to_dsi0>; }; dsi0_from_split0: endpoint@4 { remote-endpoint = <&split0_to_dsi0>; }; }; }; dsi1: dsi1@1401c000 { port@0 { dsi1_from_split1: endpoint@0 { remote-endpoint = <&split1_to_dsi1>; }; dsi1_from_rdma1: endpoint@1 { remote-endpoint = <&rdma1_to_dsi1>; }; dsi1_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dsi1>; }; dsi1_from_split0: endpoint@3 { remote-endpoint = <&split0_to_dsi1>; }; dsi1_from_rdma2: endpoint@4 { remote-endpoint = <&rdma2_to_dsi1>; }; dsi1_from_ufoe: endpoint@5 { remote-endpoint = <&ufoe_to_dsi1>; }; }; }; dpi0: dpi0@1401d000 { port@0 { dpi0_from_ufoe: endpoint@0 { remote-endpoint = <&ufoe_to_dpi0>; }; dpi0_from_rdma1: endpoint@1 { remote-endpoint = <&rdma1_to_dpi0>; }; dpi0_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dpi0>; }; dpi0_from_split0: endpoint@3 { remote-endpoint = <&split0_to_dpi0>; }; dpi0_from_rdma2: endpoint@4 { remote-endpoint = <&rdma2_to_dpi0>; }; }; }; od@14023000 { port@0 { od_from_aal: endpoint { remote-endpoint = <&aal_to_od>; }; }; port@1 { od_to_rdma0: endpoint@0 { remote-endpoint = <&rdma0_from_od>; }; od_to_ufoe: endpoint@1 { remote-endpoint = <&ufoe_from_od>; }; od_to_split0: endpoint@2 { remote-endpoint = <&split0_from_od>; }; od_to_wdma0: endpoint@3 { remote-endpoint = <&wdma0_from_od>; }; }; };
Adding the graph to the mediatek oak-rev3 device tree increases the size from currently 33 KiB to 41.5 KiB. As a proponent of the of-graph bindings I certainly like to describe the hardware connections using DT, but I'm not sure if maybe this is a bit verbose.
We need a graph visualizer...
I think there has to be a balance with how much is put into the DT. If the relationship between blocks is pretty fixed then it doesn't really help much to put all this into DT. If every SOC or board can have differing combinations of blocks or connections between blocks then the graph makes sense.
Rob
Am Mittwoch, den 30.09.2015, 11:57 -0500 schrieb Rob Herring:
On Wed, Sep 30, 2015 at 10:30 AM, Philipp Zabel p.zabel@pengutronix.de wrote:
Am Freitag, den 18.09.2015, 15:33 -0500 schrieb Rob Herring:
On Fri, Sep 18, 2015 at 11:11 AM, Philipp Zabel p.zabel@pengutronix.de 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
.../bindings/drm/mediatek/mediatek,disp.txt | 131 +++++++++++++++++++++ .../bindings/drm/mediatek/mediatek,dsi.txt | 29 +++++ 2 files changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt create mode 100644 Documentation/devicetree/bindings/drm/mediatek/mediatek,dsi.txt
diff --git a/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt new file mode 100644 index 0000000..a3811bd --- /dev/null +++ b/Documentation/devicetree/bindings/drm/mediatek/mediatek,disp.txt @@ -0,0 +1,131 @@ +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.
+The display-subsystem node binds together all individual device nodes that +comprise the DISP subsystem.
+Required properties:
+- compatible: "mediatek,<chip>-disp"
+- components: Should contain a list of phandles pointing to the DISP function
- block device nodes.
+- component-names: Should contain the name of the function block pointed to
- by the components phandle of the same index.
NAK. Group these nodes under a parent node, use of-graph or just don't put this into DT. Don't invent a new way.
If I connect the DISP nodes using of-graph bindings, the full graph will look somewhat like this (including the currently unused function blocks, all properties but ports and endpoints removed for brevity):
ovl0@1400c000 { port { ovl0_to_color0: endpoint@0 { remote-endpoint = <&color0_from_ovl0>; }; ovl0_to_wdma0: endpoint@1 { remote-endpoint = <&wdma0_from_ovl0>; }; }; }; ovl1@1400d000 { port { ovl1_to_color1: endpoint@0 { remote-endpoint = <&color1_from_ovl1>; }; ovl1_to_wdma1: endpoint@1 { remote-endpoint = <&wdma1_from_ovl1>; }; }; }; rdma0@1400e000 { port@0 { rdma0_from_od: endpoint { remote-endpoint = <&od_to_rdma0>; }; }; port@1 { rdma0_to_ufoe: endpoint@0 { remote-endpoint = <&ufoe_from_rdma0>; }; rdma0_to_split0: endpoint@1 { remote-endpoint = <&split0_from_rdma0>; }; rdma0_to_color0: endpoint@2 { remote-endpoint = <&color0_from_rdma0>; }; }; }; rdma1@1400f000 { port@0 { rdma1_from_gamma: endpoint { remote-endpoint = <&gamma_to_rdma1>; }; }; port@1 { rdma1_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_rdma1>; }; rdma1_to_dsi1: endpoint@1 { remote-endpoint = <&dsi1_from_rdma1>; }; rdma1_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_rdma1>; }; rdma1_to_color1: endpoint@3 { remote-endpoint = <&color1_from_rdma1>; }; }; }; rdma2@14010000 { port { rdma2_to_dsi1: endpoint@0 { remote-endpoint = <&dsi1_from_rdma2>; }; rdma2_to_dpi0: endpoint@1 { remote-endpoint = <&dpi0_from_rdma2>; }; }; }; wdma0@14011000 { port { wdma0_from_ovl0: endpoint@0 { remote-endpoint = <&ovl0_to_wdma0>; }; wdma0_from_od: endpoint@1 { remote-endpoint = <&od_to_wdma0>; }; wdma0_from_ufoe: endpoint@2 { remote-endpoint = <&ufoe_to_wdma0>; }; }; }; wdma1@14012000 { port { wdma1_from_ovl1: endpoint@0 { remote-endpoint = <&ovl1_to_wdma1>; }; wdma1_from_gamma: endpoint@1 { remote-endpoint = <&gamma_to_wdma1>; }; }; }; color0@14013000 { port@0 { color0_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_color0>; }; color0_from_ovl0: endpoint@1 { remote-endpoint = <&ovl0_to_color0>; }; }; port@1 { color0_to_aal: endpoint@0 { remote-endpoint = <&aal_from_color0>; }; color0_to_merge: endpoint@1 { remote-endpoint = <&merge_from_color0>; }; }; }; color1@14014000 { port@0 { color1_from_rdma1: endpoint@0 { remote-endpoint = <&rdma1_to_color1>; }; color1_from_ovl1: endpoint@1 { remote-endpoint = <&ovl1_to_color1>; }; }; port@1 { color1_to_gamma: endpoint@0 { remote-endpoint = <&gamma_from_color1>; }; color1_to_merge: endpoint@1 { remote-endpoint = <&merge_from_color1>; }; }; }; aal@14015000 { port@0 { aal_from_color0: endpoint@0 { remote-endpoint = <&color0_to_aal>; }; aal_from_merge: endpoint@1 { remote-endpoint = <&merge_to_aal>; }; }; port@1 { aal_to_od: endpoint { remote-endpoint = <&od_from_aal>; }; }; }; gamma@14016000 { port@0 { gamma_from_color1: endpoint { remote-endpoint = <&color1_to_gamma>; }; }; port@1 { gamma_to_rdma1: endpoint@0 { remote-endpoint = <&rdma1_from_gamma>; }; gamma_to_dsi0: endpoint@1 { remote-endpoint = <&dsi0_from_gamma>; }; gamma_to_dsi1: endpoint@2 { remote-endpoint = <&dsi1_from_gamma>; }; gamma_to_dpi0: endpoint@3 { remote-endpoint = <&dpi0_from_gamma>; }; gamma_to_wdma1: endpoint@4 { remote-endpoint = <&wdma1_from_gamma>; }; }; }; merge@14017000 { port@0 { merge_from_color0: endpoint { remote-endpoint = <&color0_to_merge>; }; }; port@1 { merge_from_color1: endpoint { remote-endpoint = <&color1_to_merge>; }; }; port@2 { merge_to_aal: endpoint { remote-endpoint = <&aal_from_merge>; }; }; }; split0@14018000 { port@0 { split0_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_split0>; }; split0_from_od: endpoint@1 { remote-endpoint = <&od_to_split0>; }; }; port@1 { split0_to_ufoe: endpoint { remote-endpoint = <&ufoe_from_split0>; }; }; port@2 { split0_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_split0>; }; split0_to_dsi1: endpoint@1 { remote-endpoint = <&dsi1_from_split0>; }; split0_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_split0>; }; }; }; split1@14019000 { port@0 { split1_from_ufoe: endpoint { remote-endpoint = <&ufoe_to_split1>; }; }; port@1 { split1_to_dsi0: endpoint { remote-endpoint = <&dsi0_from_split1>; }; }; port@2 { split1_to_dsi1: endpoint { remote-endpoint = <&dsi1_from_split1>; }; }; }; ufoe@1401a000 { port@0 { ufoe_from_rdma0: endpoint@0 { remote-endpoint = <&rdma0_to_ufoe>; }; ufoe_from_od: endpoint@1 { remote-endpoint = <&od_to_ufoe>; }; ufoe_from_split0: endpoint@2 { remote-endpoint = <&split0_to_ufoe>; }; }; port@1 { ufoe_to_dsi0: endpoint@0 { remote-endpoint = <&dsi0_from_ufoe>; }; ufoe_to_split1: endpoint@1 { remote-endpoint = <&split1_from_ufoe>; }; ufoe_to_dpi0: endpoint@2 { remote-endpoint = <&dpi0_from_ufoe>; }; ufoe_to_wdma0: endpoint@3 { remote-endpoint = <&wdma0_from_ufoe>; }; ufoe_to_dsi1: endpoint@4 { remote-endpoint = <&dsi1_from_ufoe>; }; }; }; dsi0: dsi0@1401b000 { port@0 { dsi0_from_ufoe: endpoint@0 { remote-endpoint = <&ufoe_to_dsi0>; }; dsi0_from_split1: endpoint@1 { remote-endpoint = <&split1_to_dsi0>; }; dsi0_from_rdma1: endpoint@2 { remote-endpoint = <&rdma1_to_dsi0>; }; dsi0_from_gamma: endpoint@3 { remote-endpoint = <&gamma_to_dsi0>; }; dsi0_from_split0: endpoint@4 { remote-endpoint = <&split0_to_dsi0>; }; }; }; dsi1: dsi1@1401c000 { port@0 { dsi1_from_split1: endpoint@0 { remote-endpoint = <&split1_to_dsi1>; }; dsi1_from_rdma1: endpoint@1 { remote-endpoint = <&rdma1_to_dsi1>; }; dsi1_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dsi1>; }; dsi1_from_split0: endpoint@3 { remote-endpoint = <&split0_to_dsi1>; }; dsi1_from_rdma2: endpoint@4 { remote-endpoint = <&rdma2_to_dsi1>; }; dsi1_from_ufoe: endpoint@5 { remote-endpoint = <&ufoe_to_dsi1>; }; }; }; dpi0: dpi0@1401d000 { port@0 { dpi0_from_ufoe: endpoint@0 { remote-endpoint = <&ufoe_to_dpi0>; }; dpi0_from_rdma1: endpoint@1 { remote-endpoint = <&rdma1_to_dpi0>; }; dpi0_from_gamma: endpoint@2 { remote-endpoint = <&gamma_to_dpi0>; }; dpi0_from_split0: endpoint@3 { remote-endpoint = <&split0_to_dpi0>; }; dpi0_from_rdma2: endpoint@4 { remote-endpoint = <&rdma2_to_dpi0>; }; }; }; od@14023000 { port@0 { od_from_aal: endpoint { remote-endpoint = <&aal_to_od>; }; }; port@1 { od_to_rdma0: endpoint@0 { remote-endpoint = <&rdma0_from_od>; }; od_to_ufoe: endpoint@1 { remote-endpoint = <&ufoe_from_od>; }; od_to_split0: endpoint@2 { remote-endpoint = <&split0_from_od>; }; od_to_wdma0: endpoint@3 { remote-endpoint = <&wdma0_from_od>; }; }; };
Adding the graph to the mediatek oak-rev3 device tree increases the size from currently 33 KiB to 41.5 KiB. As a proponent of the of-graph bindings I certainly like to describe the hardware connections using DT, but I'm not sure if maybe this is a bit verbose.
We need a graph visualizer...
Yes :)
I think there has to be a balance with how much is put into the DT. If the relationship between blocks is pretty fixed then it doesn't really help much to put all this into DT. If every SOC or board can have differing combinations of blocks or connections between blocks then the graph makes sense.
I expect the graph to be constant for a given SoC like MT8173.
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 --- 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 | 515 ++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.h | 84 +++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.c | 231 +++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.h | 39 +++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 378 ++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 61 ++++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 471 +++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.h | 46 +++ 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 | 207 +++++++++++ drivers/gpu/drm/mediatek/mtk_drm_gem.h | 56 +++ drivers/gpu/drm/mediatek/mtk_drm_plane.c | 193 +++++++++++ drivers/gpu/drm/mediatek/mtk_drm_plane.h | 38 ++ 18 files changed, 2528 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..d801572 --- /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..c06b7d4 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,515 @@ +/* + * 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" + +void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc) +{ + struct drm_device *dev = mtk_crtc->base.dev; + + drm_send_vblank_event(dev, mtk_crtc->event->pipe, mtk_crtc->event); + drm_crtc_vblank_put(&mtk_crtc->base); + mtk_crtc->event = NULL; +} + +static void mtk_drm_crtc_destroy(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); +} + +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_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx; + + if (WARN_ON(!crtc->state)) + return; + + ctx->pending_width = crtc->mode.hdisplay; + ctx->pending_height = crtc->mode.vdisplay; + ctx->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; + unsigned int width, height; + int i; + + if (ctx->crtc->base.state) { + width = crtc->base.state->adjusted_mode.hdisplay; + height = crtc->base.state->adjusted_mode.vdisplay; + } else { + width = 1920; + height = 1080; + } + + DRM_INFO("mtk_crtc_ddp_hw_init\n"); + 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_type, + ctx->ddp_comp[i+1].funcs->comp_type); + mtk_ddp_add_comp_to_mutex(ctx->mutex_dev, ctx->pipe, + ctx->ddp_comp[i].funcs->comp_type, + ctx->ddp_comp[i+1].funcs->comp_type); + } + + 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); + + 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; + + DRM_INFO("mtk_crtc_ddp_hw_fini\n"); + mtk_crtc_ddp_power_off(ctx); + mtk_ddp_clock_off(ctx->mutex_dev); +} + +static void mtk_drm_crtc_enable(struct drm_crtc *crtc) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + + if (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); + + DRM_INFO("mtk_drm_crtc_disable %d\n", crtc->base.id); + if (!mtk_crtc->enabled) + return; + + drm_crtc_vblank_put(crtc); + drm_crtc_vblank_off(crtc); + + mtk_crtc_ddp_hw_fini(mtk_crtc); + + mtk_crtc->enabled = false; +} + +static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct drm_crtc_state *state = crtc->state; + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + + if (state->event) { + state->event->pipe = drm_crtc_index(crtc); + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + mtk_crtc->event = state->event; + state->event = NULL; + } +} + +void mtk_drm_crtc_commit(struct mtk_drm_crtc *crtc) +{ + struct mtk_crtc_ddp_context *ctx = crtc->ctx; + unsigned int i; + + for (i = 0; i < OVL_LAYER_NR; i++) + if (ctx->pending_ovl_dirty[i]) { + ctx->pending_ovl_config[i] = true; + ctx->pending_ovl_dirty[i] = false; + } +} + +static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc); + + mtk_drm_crtc_commit(mtk_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 = drm_atomic_helper_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 mtk_drm_crtc *crtc, unsigned int idx, + bool enable, dma_addr_t addr) +{ + struct mtk_crtc_ddp_context *ctx = crtc->ctx; + struct drm_plane *plane = &ctx->planes[idx].base; + unsigned int pitch, format; + int x, y; + + if (!plane->fb || !plane->state) + return; + + if (plane->fb) { + pitch = plane->fb->pitches[0]; + format = plane->fb->pixel_format; + } + + if (plane->state) { + 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; + } + } + + ctx->pending_ovl_enable[idx] = enable; + ctx->pending_ovl_addr[idx] = addr; + ctx->pending_ovl_pitch[idx] = pitch; + ctx->pending_ovl_format[idx] = format; + ctx->pending_ovl_x[idx] = x; + ctx->pending_ovl_y[idx] = y; + ctx->pending_ovl_size[idx] = ctx->planes[idx].disp_size; + ctx->pending_ovl_dirty[idx] = true; +} + +static void mtk_crtc_ddp_irq(struct mtk_crtc_ddp_context *ctx) +{ + struct drm_device *dev = ctx->drm_dev; + struct mtk_drm_crtc *mtk_crtc = ctx->crtc; + struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0]; + unsigned int i; + unsigned long flags; + + if (ctx->pending_config) { + ctx->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, ctx->pending_width, + ctx->pending_height); + } + + for (i = 0; i < OVL_LAYER_NR; i++) { + if (ctx->pending_ovl_config[i]) { + if (!ctx->pending_ovl_enable[i]) + ovl->funcs->comp_layer_off(ovl->regs, i); + + ovl->funcs->comp_layer_config(ovl->regs, i, + ctx->pending_ovl_addr[i], + ctx->pending_ovl_pitch[i], + ctx->pending_ovl_format[i], + ctx->pending_ovl_x[i], + ctx->pending_ovl_y[i], + ctx->pending_ovl_size[i]); + + if (ctx->pending_ovl_enable[i]) + ovl->funcs->comp_layer_on(ovl->regs, i); + } + + ctx->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); + + for (i = 0; i < ctx->ddp_comp_nr; i++) { + struct mtk_ddp_comp *comp = &ctx->ddp_comp[i]; + + ret = mtk_ddp_comp_init(master, comp, priv->path[ctx->pipe][i]); + 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; + } + + mtk_crtc_ddp_hw_init(ctx->crtc); + ctx->ddp_comp[0].funcs->comp_layer_off(ctx->ddp_comp[0].regs, 0); + + 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; + + mtk_crtc_ddp_hw_fini(ctx->crtc); + + 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_crtc_ddp_driver_dt_match[] = { + { .compatible = "mediatek,mt8173-disp-ovl", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_crtc_ddp_driver_dt_match); + +struct platform_driver mtk_crtc_ddp_driver = { + .probe = mtk_crtc_ddp_probe, + .remove = mtk_crtc_ddp_remove, + .driver = { + .name = "mediatek-crtc-ddp", + .owner = THIS_MODULE, + .of_match_table = mtk_crtc_ddp_driver_dt_match, + }, +}; + +module_platform_driver(mtk_crtc_ddp_driver); 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..6313f92 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,84 @@ +/* + * 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 "mtk_drm_plane.h" + +#define OVL_LAYER_NR 4 + +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 + * @event: drm pending vblank event + * @pending_needs_vblank: Need to send event in vblank. + */ +struct mtk_drm_crtc { + struct drm_crtc base; + unsigned int pipe; + bool enabled; + struct mtk_crtc_ddp_context *ctx; + + struct drm_pending_vblank_event *event; + bool pending_needs_vblank; +}; + +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; + + bool pending_config; + unsigned int pending_width; + unsigned int pending_height; + + 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]; +}; + +#define to_mtk_crtc(x) container_of(x, struct mtk_drm_crtc, 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 mtk_drm_crtc *crtc, unsigned int idx, + bool enable, dma_addr_t addr); +void mtk_drm_crtc_commit(struct mtk_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..56da52a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,231 @@ +/* + * 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/pm_runtime.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_TYPE_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_type cur, + enum mtk_ddp_comp_type 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_type cur, + enum mtk_ddp_comp_type 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; + + /* disp_mtcmos */ + ret = pm_runtime_get_sync(dev); + if (ret < 0) + DRM_ERROR("failed to get_sync(%d)\n", 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); + + /* disp_mtcmos */ + pm_runtime_put_sync(dev); +} + +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); + + pm_runtime_enable(dev); + + return 0; +} + +static int mtk_ddp_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + 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..09c54b0 --- /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_type cur, + enum mtk_ddp_comp_type next); +void mtk_ddp_add_comp_to_mutex(struct device *dev, unsigned int pipe, + enum mtk_ddp_comp_type cur, + enum mtk_ddp_comp_type 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..2473f96 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,378 @@ +/* + * 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 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) +{ + 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) +{ + 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 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); + + writel(0x80F00008, 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_type = DDP_COMPONENT_AAL, +}; + +static const struct mtk_ddp_comp_funcs ddp_color0 = { + .comp_type = DDP_COMPONENT_COLOR0, + .comp_power_on = mtk_color_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_color1 = { + .comp_type = DDP_COMPONENT_COLOR1, + .comp_power_on = mtk_color_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_dpi0 = { + .comp_type = DDP_COMPONENT_DPI0, +}; + +static const struct mtk_ddp_comp_funcs ddp_dsi0 = { + .comp_type = DDP_COMPONENT_DSI0, +}; + +static const struct mtk_ddp_comp_funcs ddp_gamma = { + .comp_type = DDP_COMPONENT_GAMMA, +}; + +static const struct mtk_ddp_comp_funcs ddp_od = { + .comp_type = DDP_COMPONENT_OD, + .comp_config = mtk_od_config, + .comp_power_on = mtk_od_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_ovl0 = { + .comp_type = 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_type = 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_type = DDP_COMPONENT_PWM0, +}; + +static const struct mtk_ddp_comp_funcs ddp_rdma0 = { + .comp_type = DDP_COMPONENT_RDMA0, + .comp_config = mtk_rdma_config, + .comp_power_on = mtk_rdma_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_rdma1 = { + .comp_type = DDP_COMPONENT_RDMA1, + .comp_config = mtk_rdma_config, + .comp_power_on = mtk_rdma_start, +}; + +static const struct mtk_ddp_comp_funcs ddp_ufoe = { + .comp_type = DDP_COMPONENT_UFOE, + .comp_power_on = mtk_ufoe_start, +}; + +struct mtk_ddp_comp_match { + const char *name; + const struct mtk_ddp_comp_funcs *funcs; +}; + +static struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_TYPE_MAX] = { + [DDP_COMPONENT_AAL] = { "aal", &ddp_aal }, + [DDP_COMPONENT_COLOR0] = { "color0", &ddp_color0 }, + [DDP_COMPONENT_COLOR1] = { "color1", &ddp_color1 }, + [DDP_COMPONENT_DPI0] = { "dpi0", &ddp_dpi0 }, + [DDP_COMPONENT_DSI0] = { "dsi0", &ddp_dsi0 }, + [DDP_COMPONENT_GAMMA] = { "gamma", &ddp_gamma }, + [DDP_COMPONENT_OD] = { "od", &ddp_od }, + [DDP_COMPONENT_OVL0] = { "ovl0", &ddp_ovl0 }, + [DDP_COMPONENT_OVL1] = { "ovl1", &ddp_ovl1 }, + [DDP_COMPONENT_PWM0] = { "pwm0", &ddp_pwm0 }, + [DDP_COMPONENT_RDMA0] = { "rdma0", &ddp_rdma0 }, + [DDP_COMPONENT_RDMA1] = { "rdma1", &ddp_rdma1 }, + [DDP_COMPONENT_UFOE] = { "ufoe", &ddp_ufoe }, +}; + +enum mtk_ddp_comp_type mtk_ddp_comp_get_type(const char *comp_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) { + if (strcasecmp(comp_name, mtk_ddp_matches[i].name) == 0) + return i; + } + + return -EINVAL; +} + +int mtk_ddp_comp_init(struct device *dev, struct mtk_ddp_comp *comp, + enum mtk_ddp_comp_type comp_type) +{ + struct device_node *node; + int index; + + if (comp_type < 0 || comp_type >= DDP_COMPONENT_TYPE_MAX) + return -EINVAL; + + comp->funcs = mtk_ddp_matches[comp_type].funcs; + + if (comp_type == DDP_COMPONENT_DPI0 || + comp_type == DDP_COMPONENT_DSI0 || + comp_type == DDP_COMPONENT_PWM0) + return 0; + + index = of_property_match_string(dev->of_node, "component-names", + mtk_ddp_matches[comp_type].name); + if (index < 0) + return 0; + + node = of_parse_phandle(dev->of_node, "components", index); + if (!node) + 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; + + of_node_put(node); + + 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..9ebb969 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.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_DDP_COMP_H +#define MTK_DRM_DDP_COMP_H + +enum mtk_ddp_comp_type { + DDP_COMPONENT_AAL, + DDP_COMPONENT_COLOR0, + DDP_COMPONENT_COLOR1, + DDP_COMPONENT_DPI0, + DDP_COMPONENT_DSI0, + DDP_COMPONENT_GAMMA, + DDP_COMPONENT_OD, + DDP_COMPONENT_OVL0, + DDP_COMPONENT_OVL1, + DDP_COMPONENT_PWM0, + DDP_COMPONENT_RDMA0, + DDP_COMPONENT_RDMA1, + DDP_COMPONENT_UFOE, + DDP_COMPONENT_TYPE_MAX, +}; + +struct mtk_ddp_comp_funcs { + enum mtk_ddp_comp_type comp_type; + void (*comp_config)(void __iomem *ovl_base, + unsigned int w, unsigned int h); + 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; +}; + +enum mtk_ddp_comp_type mtk_ddp_comp_get_type(const char *comp_name); +int mtk_ddp_comp_init(struct device *dev, struct mtk_ddp_comp *comp, + enum mtk_ddp_comp_type comp_type); + +#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..fc071fe --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,471 @@ +/* + * 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_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 int mtk_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, + bool async) +{ + return drm_atomic_helper_commit(dev, state, false); +} + +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_type 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_type 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 *dev) +{ + struct mtk_drm_private *private = dev->dev_private; + struct device_node *node; + struct platform_device *pdev; + int i; + int err; + + drm_mode_config_init(dev); + + dev->mode_config.min_width = 64; + dev->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(). + */ + dev->mode_config.max_width = 4096; + dev->mode_config.max_height = 4096; + dev->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; + + err = component_bind_all(dev->dev, dev); + if (err) + 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. + */ + dev->irq_enabled = true; + err = drm_vblank_init(dev, MAX_CRTC); + if (err < 0) + goto err_unbind; + + for (i = 0; i < MAX_CRTC; i++) { + node = of_parse_phandle(dev->dev->of_node, "larb", i); + if (!node) + break; + + pdev = of_find_device_by_node(node); + of_node_put(node); + if (WARN_ON(!pdev)) + goto err_vblank_cleanup; + + private->larb_dev[i] = &pdev->dev; + } + + for (i = 0; i < MAX_CRTC; i++) { + if (!private->larb_dev[i]) + break; + + err = mtk_smi_larb_get(private->larb_dev[i]); + if (err) { + DRM_ERROR("mtk_smi_larb_get fail %d\n", err); + goto err_larb_get; + } + } + + drm_kms_helper_poll_init(dev); + drm_mode_config_reset(dev); + + return 0; + +err_larb_get: + for (i = i - 1; i >= 0; i--) + mtk_smi_larb_put(private->larb_dev[i]); +err_vblank_cleanup: + drm_kms_helper_poll_fini(dev); + drm_vblank_cleanup(dev); +err_unbind: + component_unbind_all(dev->dev, dev); +err_crtc: + drm_mode_config_cleanup(dev); + + return err; +} + +static void mtk_drm_kms_deinit(struct drm_device *dev) +{ + drm_kms_helper_poll_fini(dev); + + drm_vblank_cleanup(dev); + drm_mode_config_cleanup(dev); +} + +static int mtk_drm_load(struct drm_device *dev, unsigned long flags) +{ + struct mtk_drm_private *priv; + struct device_node *config_node, *mutex_node; + struct platform_device *mutex_pdev; + + priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev->dev_private = priv; + platform_set_drvdata(dev->platformdev, dev); + + config_node = of_parse_phandle(dev->dev->of_node, "mmsys-config", 0); + if (!config_node) { + dev_err(dev->dev, "Failed to get mmsys-config node\n"); + return -EINVAL; + } + + priv->config_regs = of_iomap(config_node, 0); + if (!priv->config_regs) { + dev_err(dev->dev, "Failed to map mmsys-config registers\n"); + return -EINVAL; + } + + mutex_node = of_parse_phandle(dev->dev->of_node, "disp-mutex", 0); + if (!mutex_node) { + dev_err(dev->dev, "Failed to get disp-mutex node\n"); + return -EINVAL; + } + + mutex_pdev = of_find_device_by_node(mutex_node); + if (!mutex_pdev) { + dev_err(dev->dev, "Failed to find disp-mutex device\n"); + return -EPROBE_DEFER; + } + priv->mutex_dev = &mutex_pdev->dev; + + return mtk_drm_kms_init(dev); +} + +static int mtk_drm_unload(struct drm_device *dev) +{ + mtk_drm_kms_deinit(dev); + dev->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, + .load = mtk_drm_load, + .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) +{ + return drm_platform_init(&mtk_drm_driver, to_platform_device(dev)); +} + +static void mtk_drm_unbind(struct device *dev) +{ + drm_put_dev(platform_get_drvdata(to_platform_device(dev))); +} + +static const struct component_master_ops mtk_drm_ops = { + .bind = mtk_drm_bind, + .unbind = mtk_drm_unbind, +}; + +static int mtk_drm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct component_match *match = NULL; + unsigned i; + int ret; + + for (i = 0; ; i++) { + struct device_node *node; + const char *comp_name; + int comp_type; + + node = of_parse_phandle(np, "components", i); + if (!node) + break; + + ret = of_property_read_string_index(np, "component-names", i, + &comp_name); + if (ret) { + dev_err(&pdev->dev, "Failed to get component name\n"); + of_node_put(node); + break; + } + + comp_type = mtk_ddp_comp_get_type(comp_name); + if (comp_type < 0) { + dev_warn(&pdev->dev, "Skipping unknown component %s\n", + comp_name); + of_node_put(node); + continue; + } + + if (!of_device_is_available(node)) { + dev_dbg(&pdev->dev, "Skipping disabled component %s\n", + comp_name); + of_node_put(node); + continue; + } + + /* + * Currently only the OVL and DSI blocks have separate + * component platform drivers. + */ + if (comp_type == DDP_COMPONENT_OVL0 || + comp_type == DDP_COMPONENT_OVL1 || + comp_type == DDP_COMPONENT_DSI0) { + dev_info(&pdev->dev, "Adding component match for %s\n", + comp_name); + component_match_add(&pdev->dev, &match, compare_of, + node); + } + of_node_put(node); + } + + for (i = 0; i < MAX_CONNECTOR; i++) { + struct device_node *node; + + node = of_parse_phandle(np, "connectors", i); + if (!node) + break; + + component_match_add(&pdev->dev, &match, compare_of, node); + of_node_put(node); + } + + for (i = 0; i < MAX_CRTC; i++) { + struct device_node *node; + + node = of_parse_phandle(np, "crtcs", i); + if (!node) + break; + + component_match_add(&pdev->dev, &match, compare_of, node); + of_node_put(node); + } + + return component_master_add_with_match(&pdev->dev, &mtk_drm_ops, match); +} + +static int mtk_drm_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &mtk_drm_ops); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_drm_sys_suspend(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct mtk_drm_private *private = ddev->dev_private; + struct drm_connector *conn; + int i; + + drm_kms_helper_poll_disable(ddev); + + drm_modeset_lock_all(ddev); + list_for_each_entry(conn, &ddev->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(ddev); + + 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 drm_device *ddev = dev_get_drvdata(dev); + struct mtk_drm_private *private = ddev->dev_private; + struct drm_connector *conn; + int i; + int err; + + for (i = 0; i < MAX_CRTC; i++) { + if (!private->larb_dev[i]) + break; + + err = mtk_smi_larb_get(private->larb_dev[i]); + if (err) { + DRM_ERROR("mtk_smi_larb_get fail %d\n", err); + return err; + } + } + + drm_modeset_lock_all(ddev); + list_for_each_entry(conn, &ddev->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(ddev); + + drm_kms_helper_poll_enable(ddev); + + 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-disp", }, + { } +}; + +static struct platform_driver mtk_drm_platform_driver = { + .probe = mtk_drm_probe, + .remove = mtk_drm_remove, + .driver = { + .owner = THIS_MODULE, + .name = "mediatek-drm", + .of_match_table = mtk_drm_of_ids, +#ifdef CONFIG_PM_SLEEP + .pm = &mtk_drm_pm_ops, +#endif + }, +}; + +module_platform_driver(mtk_drm_platform_driver); + +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..ded8d0e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,46 @@ +/* + * 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 "mtk_drm_ddp_comp.h" + +#define MAX_CRTC 2 +#define MAX_CONNECTOR 2 + +struct device; +struct drm_crtc; +struct drm_fb_helper; +struct drm_property; +struct regmap; + +struct mtk_drm_private { + struct drm_fb_helper *fb_helper; + + /* + * 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 *larb_dev[MAX_CRTC]; + void __iomem *config_regs; + struct device *mutex_dev; + unsigned int path_len[MAX_CRTC]; + const enum mtk_ddp_comp_type *path[MAX_CRTC]; +}; + +#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..d5adfd9 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c @@ -0,0 +1,207 @@ +/* + * 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_free(&mtk_gem->base.refcount); + 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; + + mutex_lock(&dev->struct_mutex); + + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + ret = -EINVAL; + goto unlock; + } + + 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(obj); +unlock: + mutex_unlock(&dev->struct_mutex); + 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_buf(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + struct drm_device *drm = obj->dev; + int ret; + + mutex_lock(&drm->struct_mutex); + ret = drm_gem_mmap_obj(obj, obj->size, vma); + mutex_unlock(&drm->struct_mutex); + if (ret) + return ret; + + return mtk_drm_gem_object_mmap(obj, vma); +} + +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..9bbf4da --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,193 @@ +/* + * 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 mtk_drm_plane *mtk_plane = to_mtk_plane(plane); + 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; + + if (!visible) + return 0; + + mtk_plane->disp_size = (dest.y2 - dest.y1) << 16 | (dest.x2 - dest.x1); + + 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 mtk_drm_crtc *mtk_crtc; + struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane); + + if (!state->crtc) + return; + + mtk_crtc = to_mtk_crtc(state->crtc); + + if (plane->fb) + drm_framebuffer_unreference(plane->fb); + if (state->fb) + drm_framebuffer_reference(state->fb); + 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->mtk_crtc = mtk_crtc; + + if (mtk_plane->flip_obj) + mtk_drm_crtc_plane_config(mtk_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 mtk_drm_crtc *mtk_crtc = to_mtk_crtc(old_state->crtc); + + if (!old_state->crtc) + return; + + if (mtk_crtc) + mtk_drm_crtc_plane_config(mtk_crtc, mtk_plane->idx, false, 0); + + mtk_drm_crtc_commit(mtk_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, +}; + +static void mtk_plane_attach_zpos_property(struct drm_plane *plane, + unsigned int zpos, unsigned int max_plane) +{ + struct drm_device *dev = plane->dev; + struct mtk_drm_private *dev_priv = dev->dev_private; + struct drm_property *prop; + + prop = dev_priv->plane_zpos_property; + if (!prop) { + prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE, + "zpos", 0, max_plane - 1); + if (!prop) + return; + + dev_priv->plane_zpos_property = prop; + } + + drm_object_attach_property(&plane->base, prop, zpos); +} + +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; + + if (type == DRM_PLANE_TYPE_OVERLAY) + mtk_plane_attach_zpos_property(&mtk_plane->base, + zpos, max_plane); + + 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..80075d493 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h @@ -0,0 +1,38 @@ +/* + * 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_ + +struct fence; +struct mtk_crtc; +struct mtk_drm_gem_obj; + +#define to_mtk_plane(x) container_of(x, struct mtk_drm_plane, base) + +struct mtk_drm_plane { + struct drm_plane base; + struct mtk_drm_crtc *mtk_crtc; + unsigned int idx; + unsigned int disp_size; + + struct mtk_drm_gem_obj *flip_obj; +}; + +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
On Fri, Sep 18, 2015 at 06:12:00PM +0200, Philipp Zabel 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
Bunch of comments related to atomic below. I didn't look at anything else really.
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 | 515 ++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_crtc.h | 84 +++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.c | 231 +++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp.h | 39 +++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c | 378 ++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.h | 61 ++++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 471 +++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_drv.h | 46 +++ 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 | 207 +++++++++++ drivers/gpu/drm/mediatek/mtk_drm_gem.h | 56 +++ drivers/gpu/drm/mediatek/mtk_drm_plane.c | 193 +++++++++++ drivers/gpu/drm/mediatek/mtk_drm_plane.h | 38 ++ 18 files changed, 2528 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..d801572 --- /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..c06b7d4 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -0,0 +1,515 @@ +/*
- 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"
+void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc) +{
- struct drm_device *dev = mtk_crtc->base.dev;
- drm_send_vblank_event(dev, mtk_crtc->event->pipe, mtk_crtc->event);
- drm_crtc_vblank_put(&mtk_crtc->base);
- mtk_crtc->event = NULL;
+}
+static void mtk_drm_crtc_destroy(struct drm_crtc *crtc) +{
- drm_crtc_cleanup(crtc);
+}
+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_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
- struct mtk_crtc_ddp_context *ctx = mtk_crtc->ctx;
- if (WARN_ON(!crtc->state))
return;
- ctx->pending_width = crtc->mode.hdisplay;
- ctx->pending_height = crtc->mode.vdisplay;
- ctx->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;
- unsigned int width, height;
- int i;
- if (ctx->crtc->base.state) {
width = crtc->base.state->adjusted_mode.hdisplay;
height = crtc->base.state->adjusted_mode.vdisplay;
- } else {
width = 1920;
height = 1080;
- }
- DRM_INFO("mtk_crtc_ddp_hw_init\n");
- 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_type,
ctx->ddp_comp[i+1].funcs->comp_type);
mtk_ddp_add_comp_to_mutex(ctx->mutex_dev, ctx->pipe,
ctx->ddp_comp[i].funcs->comp_type,
ctx->ddp_comp[i+1].funcs->comp_type);
- }
- 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);
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;
- DRM_INFO("mtk_crtc_ddp_hw_fini\n");
- mtk_crtc_ddp_power_off(ctx);
- mtk_ddp_clock_off(ctx->mutex_dev);
+}
+static void mtk_drm_crtc_enable(struct drm_crtc *crtc) +{
- struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
- if (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);
- DRM_INFO("mtk_drm_crtc_disable %d\n", crtc->base.id);
- if (!mtk_crtc->enabled)
return;
- drm_crtc_vblank_put(crtc);
- drm_crtc_vblank_off(crtc);
- mtk_crtc_ddp_hw_fini(mtk_crtc);
- mtk_crtc->enabled = false;
+}
+static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
+{
- struct drm_crtc_state *state = crtc->state;
- struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
- if (state->event) {
state->event->pipe = drm_crtc_index(crtc);
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
mtk_crtc->event = state->event;
state->event = NULL;
- }
+}
+void mtk_drm_crtc_commit(struct mtk_drm_crtc *crtc) +{
- struct mtk_crtc_ddp_context *ctx = crtc->ctx;
- unsigned int i;
- for (i = 0; i < OVL_LAYER_NR; i++)
if (ctx->pending_ovl_dirty[i]) {
ctx->pending_ovl_config[i] = true;
ctx->pending_ovl_dirty[i] = false;
}
+}
+static void mtk_drm_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
+{
- struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
- mtk_drm_crtc_commit(mtk_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 = drm_atomic_helper_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 mtk_drm_crtc *crtc, unsigned int idx,
bool enable, dma_addr_t addr)
+{
- struct mtk_crtc_ddp_context *ctx = crtc->ctx;
- struct drm_plane *plane = &ctx->planes[idx].base;
- unsigned int pitch, format;
- int x, y;
- if (!plane->fb || !plane->state)
return;
- if (plane->fb) {
pitch = plane->fb->pitches[0];
format = plane->fb->pixel_format;
- }
- if (plane->state) {
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;
}
- }
- ctx->pending_ovl_enable[idx] = enable;
- ctx->pending_ovl_addr[idx] = addr;
- ctx->pending_ovl_pitch[idx] = pitch;
- ctx->pending_ovl_format[idx] = format;
- ctx->pending_ovl_x[idx] = x;
- ctx->pending_ovl_y[idx] = y;
- ctx->pending_ovl_size[idx] = ctx->planes[idx].disp_size;
- ctx->pending_ovl_dirty[idx] = true;
+}
+static void mtk_crtc_ddp_irq(struct mtk_crtc_ddp_context *ctx) +{
- struct drm_device *dev = ctx->drm_dev;
- struct mtk_drm_crtc *mtk_crtc = ctx->crtc;
- struct mtk_ddp_comp *ovl = &ctx->ddp_comp[0];
- unsigned int i;
- unsigned long flags;
- if (ctx->pending_config) {
ctx->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, ctx->pending_width,
ctx->pending_height);
- }
- for (i = 0; i < OVL_LAYER_NR; i++) {
if (ctx->pending_ovl_config[i]) {
if (!ctx->pending_ovl_enable[i])
ovl->funcs->comp_layer_off(ovl->regs, i);
ovl->funcs->comp_layer_config(ovl->regs, i,
ctx->pending_ovl_addr[i],
ctx->pending_ovl_pitch[i],
ctx->pending_ovl_format[i],
ctx->pending_ovl_x[i],
ctx->pending_ovl_y[i],
ctx->pending_ovl_size[i]);
if (ctx->pending_ovl_enable[i])
ovl->funcs->comp_layer_on(ovl->regs, i);
}
ctx->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);
- for (i = 0; i < ctx->ddp_comp_nr; i++) {
struct mtk_ddp_comp *comp = &ctx->ddp_comp[i];
ret = mtk_ddp_comp_init(master, comp, priv->path[ctx->pipe][i]);
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;
- }
- mtk_crtc_ddp_hw_init(ctx->crtc);
- ctx->ddp_comp[0].funcs->comp_layer_off(ctx->ddp_comp[0].regs, 0);
- 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;
- mtk_crtc_ddp_hw_fini(ctx->crtc);
- 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_crtc_ddp_driver_dt_match[] = {
- { .compatible = "mediatek,mt8173-disp-ovl", },
- {},
+}; +MODULE_DEVICE_TABLE(of, mtk_crtc_ddp_driver_dt_match);
+struct platform_driver mtk_crtc_ddp_driver = {
- .probe = mtk_crtc_ddp_probe,
- .remove = mtk_crtc_ddp_remove,
- .driver = {
.name = "mediatek-crtc-ddp",
.owner = THIS_MODULE,
.of_match_table = mtk_crtc_ddp_driver_dt_match,
- },
+};
+module_platform_driver(mtk_crtc_ddp_driver); 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..6313f92 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.h @@ -0,0 +1,84 @@ +/*
- 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 "mtk_drm_plane.h"
+#define OVL_LAYER_NR 4
+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
- @event: drm pending vblank event
- @pending_needs_vblank: Need to send event in vblank.
- */
+struct mtk_drm_crtc {
- struct drm_crtc base;
- unsigned int pipe;
- bool enabled;
- struct mtk_crtc_ddp_context *ctx;
- struct drm_pending_vblank_event *event;
- bool pending_needs_vblank;
+};
+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;
- bool pending_config;
- unsigned int pending_width;
- unsigned int pending_height;
- 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];
+};
+#define to_mtk_crtc(x) container_of(x, struct mtk_drm_crtc, 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 mtk_drm_crtc *crtc, unsigned int idx,
bool enable, dma_addr_t addr);
+void mtk_drm_crtc_commit(struct mtk_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..56da52a --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp.c @@ -0,0 +1,231 @@ +/*
- 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/pm_runtime.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_TYPE_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_type cur,
enum mtk_ddp_comp_type 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_type cur,
enum mtk_ddp_comp_type 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;
- /* disp_mtcmos */
- ret = pm_runtime_get_sync(dev);
- if (ret < 0)
DRM_ERROR("failed to get_sync(%d)\n", 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);
- /* disp_mtcmos */
- pm_runtime_put_sync(dev);
+}
+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);
- pm_runtime_enable(dev);
- return 0;
+}
+static int mtk_ddp_remove(struct platform_device *pdev) +{
- pm_runtime_disable(&pdev->dev);
- 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..09c54b0 --- /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_type cur,
enum mtk_ddp_comp_type next);
+void mtk_ddp_add_comp_to_mutex(struct device *dev, unsigned int pipe,
enum mtk_ddp_comp_type cur,
enum mtk_ddp_comp_type 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..2473f96 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.c @@ -0,0 +1,378 @@ +/*
- 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 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) +{
- 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)
+{
- 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 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);
- writel(0x80F00008, 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_type = DDP_COMPONENT_AAL,
+};
+static const struct mtk_ddp_comp_funcs ddp_color0 = {
- .comp_type = DDP_COMPONENT_COLOR0,
- .comp_power_on = mtk_color_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_color1 = {
- .comp_type = DDP_COMPONENT_COLOR1,
- .comp_power_on = mtk_color_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_dpi0 = {
- .comp_type = DDP_COMPONENT_DPI0,
+};
+static const struct mtk_ddp_comp_funcs ddp_dsi0 = {
- .comp_type = DDP_COMPONENT_DSI0,
+};
+static const struct mtk_ddp_comp_funcs ddp_gamma = {
- .comp_type = DDP_COMPONENT_GAMMA,
+};
+static const struct mtk_ddp_comp_funcs ddp_od = {
- .comp_type = DDP_COMPONENT_OD,
- .comp_config = mtk_od_config,
- .comp_power_on = mtk_od_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_ovl0 = {
- .comp_type = 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_type = 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_type = DDP_COMPONENT_PWM0,
+};
+static const struct mtk_ddp_comp_funcs ddp_rdma0 = {
- .comp_type = DDP_COMPONENT_RDMA0,
- .comp_config = mtk_rdma_config,
- .comp_power_on = mtk_rdma_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_rdma1 = {
- .comp_type = DDP_COMPONENT_RDMA1,
- .comp_config = mtk_rdma_config,
- .comp_power_on = mtk_rdma_start,
+};
+static const struct mtk_ddp_comp_funcs ddp_ufoe = {
- .comp_type = DDP_COMPONENT_UFOE,
- .comp_power_on = mtk_ufoe_start,
+};
+struct mtk_ddp_comp_match {
- const char *name;
- const struct mtk_ddp_comp_funcs *funcs;
+};
+static struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_TYPE_MAX] = {
- [DDP_COMPONENT_AAL] = { "aal", &ddp_aal },
- [DDP_COMPONENT_COLOR0] = { "color0", &ddp_color0 },
- [DDP_COMPONENT_COLOR1] = { "color1", &ddp_color1 },
- [DDP_COMPONENT_DPI0] = { "dpi0", &ddp_dpi0 },
- [DDP_COMPONENT_DSI0] = { "dsi0", &ddp_dsi0 },
- [DDP_COMPONENT_GAMMA] = { "gamma", &ddp_gamma },
- [DDP_COMPONENT_OD] = { "od", &ddp_od },
- [DDP_COMPONENT_OVL0] = { "ovl0", &ddp_ovl0 },
- [DDP_COMPONENT_OVL1] = { "ovl1", &ddp_ovl1 },
- [DDP_COMPONENT_PWM0] = { "pwm0", &ddp_pwm0 },
- [DDP_COMPONENT_RDMA0] = { "rdma0", &ddp_rdma0 },
- [DDP_COMPONENT_RDMA1] = { "rdma1", &ddp_rdma1 },
- [DDP_COMPONENT_UFOE] = { "ufoe", &ddp_ufoe },
+};
+enum mtk_ddp_comp_type mtk_ddp_comp_get_type(const char *comp_name) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) {
if (strcasecmp(comp_name, mtk_ddp_matches[i].name) == 0)
return i;
- }
- return -EINVAL;
+}
+int mtk_ddp_comp_init(struct device *dev, struct mtk_ddp_comp *comp,
enum mtk_ddp_comp_type comp_type)
+{
- struct device_node *node;
- int index;
- if (comp_type < 0 || comp_type >= DDP_COMPONENT_TYPE_MAX)
return -EINVAL;
- comp->funcs = mtk_ddp_matches[comp_type].funcs;
- if (comp_type == DDP_COMPONENT_DPI0 ||
comp_type == DDP_COMPONENT_DSI0 ||
comp_type == DDP_COMPONENT_PWM0)
return 0;
- index = of_property_match_string(dev->of_node, "component-names",
mtk_ddp_matches[comp_type].name);
- if (index < 0)
return 0;
- node = of_parse_phandle(dev->of_node, "components", index);
- if (!node)
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;
- of_node_put(node);
- 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..9ebb969 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_ddp_comp.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_DDP_COMP_H +#define MTK_DRM_DDP_COMP_H
+enum mtk_ddp_comp_type {
- DDP_COMPONENT_AAL,
- DDP_COMPONENT_COLOR0,
- DDP_COMPONENT_COLOR1,
- DDP_COMPONENT_DPI0,
- DDP_COMPONENT_DSI0,
- DDP_COMPONENT_GAMMA,
- DDP_COMPONENT_OD,
- DDP_COMPONENT_OVL0,
- DDP_COMPONENT_OVL1,
- DDP_COMPONENT_PWM0,
- DDP_COMPONENT_RDMA0,
- DDP_COMPONENT_RDMA1,
- DDP_COMPONENT_UFOE,
- DDP_COMPONENT_TYPE_MAX,
+};
+struct mtk_ddp_comp_funcs {
- enum mtk_ddp_comp_type comp_type;
- void (*comp_config)(void __iomem *ovl_base,
unsigned int w, unsigned int h);
- 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;
+};
+enum mtk_ddp_comp_type mtk_ddp_comp_get_type(const char *comp_name); +int mtk_ddp_comp_init(struct device *dev, struct mtk_ddp_comp *comp,
enum mtk_ddp_comp_type comp_type);
+#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..fc071fe --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,471 @@ +/*
- 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_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 int mtk_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool async)
+{
- return drm_atomic_helper_commit(dev, state, false);
This isn't a proper async commit operation, it will still block userspace unecessarily. See e.g. the vc4 patches for a proper one.
+}
+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_type 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_type 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 *dev) +{
- struct mtk_drm_private *private = dev->dev_private;
- struct device_node *node;
- struct platform_device *pdev;
- int i;
- int err;
- drm_mode_config_init(dev);
- dev->mode_config.min_width = 64;
- dev->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().
*/
- dev->mode_config.max_width = 4096;
- dev->mode_config.max_height = 4096;
- dev->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;
- err = component_bind_all(dev->dev, dev);
- if (err)
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.
*/
- dev->irq_enabled = true;
- err = drm_vblank_init(dev, MAX_CRTC);
- if (err < 0)
goto err_unbind;
- for (i = 0; i < MAX_CRTC; i++) {
node = of_parse_phandle(dev->dev->of_node, "larb", i);
if (!node)
break;
pdev = of_find_device_by_node(node);
of_node_put(node);
if (WARN_ON(!pdev))
goto err_vblank_cleanup;
private->larb_dev[i] = &pdev->dev;
- }
- for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_dev[i])
break;
err = mtk_smi_larb_get(private->larb_dev[i]);
if (err) {
DRM_ERROR("mtk_smi_larb_get fail %d\n", err);
goto err_larb_get;
}
- }
- drm_kms_helper_poll_init(dev);
- drm_mode_config_reset(dev);
- return 0;
+err_larb_get:
- for (i = i - 1; i >= 0; i--)
mtk_smi_larb_put(private->larb_dev[i]);
+err_vblank_cleanup:
- drm_kms_helper_poll_fini(dev);
- drm_vblank_cleanup(dev);
+err_unbind:
- component_unbind_all(dev->dev, dev);
+err_crtc:
- drm_mode_config_cleanup(dev);
- return err;
+}
+static void mtk_drm_kms_deinit(struct drm_device *dev) +{
- drm_kms_helper_poll_fini(dev);
- drm_vblank_cleanup(dev);
- drm_mode_config_cleanup(dev);
+}
+static int mtk_drm_load(struct drm_device *dev, unsigned long flags) +{
- struct mtk_drm_private *priv;
- struct device_node *config_node, *mutex_node;
- struct platform_device *mutex_pdev;
- priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
return -ENOMEM;
- dev->dev_private = priv;
- platform_set_drvdata(dev->platformdev, dev);
- config_node = of_parse_phandle(dev->dev->of_node, "mmsys-config", 0);
- if (!config_node) {
dev_err(dev->dev, "Failed to get mmsys-config node\n");
return -EINVAL;
- }
- priv->config_regs = of_iomap(config_node, 0);
- if (!priv->config_regs) {
dev_err(dev->dev, "Failed to map mmsys-config registers\n");
return -EINVAL;
- }
- mutex_node = of_parse_phandle(dev->dev->of_node, "disp-mutex", 0);
- if (!mutex_node) {
dev_err(dev->dev, "Failed to get disp-mutex node\n");
return -EINVAL;
- }
- mutex_pdev = of_find_device_by_node(mutex_node);
- if (!mutex_pdev) {
dev_err(dev->dev, "Failed to find disp-mutex device\n");
return -EPROBE_DEFER;
- }
- priv->mutex_dev = &mutex_pdev->dev;
- return mtk_drm_kms_init(dev);
+}
+static int mtk_drm_unload(struct drm_device *dev) +{
- mtk_drm_kms_deinit(dev);
- dev->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,
- .load = mtk_drm_load,
- .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) +{
- return drm_platform_init(&mtk_drm_driver, to_platform_device(dev));
+}
+static void mtk_drm_unbind(struct device *dev) +{
- drm_put_dev(platform_get_drvdata(to_platform_device(dev)));
+}
+static const struct component_master_ops mtk_drm_ops = {
- .bind = mtk_drm_bind,
- .unbind = mtk_drm_unbind,
+};
+static int mtk_drm_probe(struct platform_device *pdev) +{
- struct device_node *np = pdev->dev.of_node;
- struct component_match *match = NULL;
- unsigned i;
- int ret;
- for (i = 0; ; i++) {
struct device_node *node;
const char *comp_name;
int comp_type;
node = of_parse_phandle(np, "components", i);
if (!node)
break;
ret = of_property_read_string_index(np, "component-names", i,
&comp_name);
if (ret) {
dev_err(&pdev->dev, "Failed to get component name\n");
of_node_put(node);
break;
}
comp_type = mtk_ddp_comp_get_type(comp_name);
if (comp_type < 0) {
dev_warn(&pdev->dev, "Skipping unknown component %s\n",
comp_name);
of_node_put(node);
continue;
}
if (!of_device_is_available(node)) {
dev_dbg(&pdev->dev, "Skipping disabled component %s\n",
comp_name);
of_node_put(node);
continue;
}
/*
* Currently only the OVL and DSI blocks have separate
* component platform drivers.
*/
if (comp_type == DDP_COMPONENT_OVL0 ||
comp_type == DDP_COMPONENT_OVL1 ||
comp_type == DDP_COMPONENT_DSI0) {
dev_info(&pdev->dev, "Adding component match for %s\n",
comp_name);
component_match_add(&pdev->dev, &match, compare_of,
node);
}
of_node_put(node);
- }
- for (i = 0; i < MAX_CONNECTOR; i++) {
struct device_node *node;
node = of_parse_phandle(np, "connectors", i);
if (!node)
break;
component_match_add(&pdev->dev, &match, compare_of, node);
of_node_put(node);
- }
- for (i = 0; i < MAX_CRTC; i++) {
struct device_node *node;
node = of_parse_phandle(np, "crtcs", i);
if (!node)
break;
component_match_add(&pdev->dev, &match, compare_of, node);
of_node_put(node);
- }
- return component_master_add_with_match(&pdev->dev, &mtk_drm_ops, match);
+}
+static int mtk_drm_remove(struct platform_device *pdev) +{
- component_master_del(&pdev->dev, &mtk_drm_ops);
- return 0;
+}
+#ifdef CONFIG_PM_SLEEP +static int mtk_drm_sys_suspend(struct device *dev) +{
- struct drm_device *ddev = dev_get_drvdata(dev);
- struct mtk_drm_private *private = ddev->dev_private;
- struct drm_connector *conn;
- int i;
- drm_kms_helper_poll_disable(ddev);
- drm_modeset_lock_all(ddev);
- list_for_each_entry(conn, &ddev->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(ddev);
- 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 drm_device *ddev = dev_get_drvdata(dev);
- struct mtk_drm_private *private = ddev->dev_private;
- struct drm_connector *conn;
- int i;
- int err;
- for (i = 0; i < MAX_CRTC; i++) {
if (!private->larb_dev[i])
break;
err = mtk_smi_larb_get(private->larb_dev[i]);
if (err) {
DRM_ERROR("mtk_smi_larb_get fail %d\n", err);
return err;
}
- }
- drm_modeset_lock_all(ddev);
- list_for_each_entry(conn, &ddev->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(ddev);
- drm_kms_helper_poll_enable(ddev);
- 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-disp", },
- { }
+};
+static struct platform_driver mtk_drm_platform_driver = {
- .probe = mtk_drm_probe,
- .remove = mtk_drm_remove,
- .driver = {
.owner = THIS_MODULE,
.name = "mediatek-drm",
.of_match_table = mtk_drm_of_ids,
+#ifdef CONFIG_PM_SLEEP
.pm = &mtk_drm_pm_ops,
+#endif
- },
+};
+module_platform_driver(mtk_drm_platform_driver);
+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..ded8d0e --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.h @@ -0,0 +1,46 @@ +/*
- 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 "mtk_drm_ddp_comp.h"
+#define MAX_CRTC 2 +#define MAX_CONNECTOR 2
+struct device; +struct drm_crtc; +struct drm_fb_helper; +struct drm_property; +struct regmap;
+struct mtk_drm_private {
- struct drm_fb_helper *fb_helper;
- /*
* 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 *larb_dev[MAX_CRTC];
- void __iomem *config_regs;
- struct device *mutex_dev;
- unsigned int path_len[MAX_CRTC];
- const enum mtk_ddp_comp_type *path[MAX_CRTC];
+};
+#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..d5adfd9 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_gem.c @@ -0,0 +1,207 @@ +/*
- 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_free(&mtk_gem->base.refcount);
- 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;
- mutex_lock(&dev->struct_mutex);
- obj = drm_gem_object_lookup(dev, file_priv, handle);
- if (!obj) {
DRM_ERROR("failed to lookup gem object.\n");
ret = -EINVAL;
goto unlock;
- }
- 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(obj);
+unlock:
- mutex_unlock(&dev->struct_mutex);
- 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_buf(struct drm_gem_object *obj, struct vm_area_struct *vma) +{
- struct drm_device *drm = obj->dev;
- int ret;
- mutex_lock(&drm->struct_mutex);
- ret = drm_gem_mmap_obj(obj, obj->size, vma);
- mutex_unlock(&drm->struct_mutex);
- if (ret)
return ret;
- return mtk_drm_gem_object_mmap(obj, vma);
+}
+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..9bbf4da --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.c @@ -0,0 +1,193 @@ +/*
- 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 mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
- 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;
- if (!visible)
return 0;
- mtk_plane->disp_size = (dest.y2 - dest.y1) << 16 | (dest.x2 - dest.x1);
I think it might work out ok but it's very fragile to update object state from your atomic_check hooks - atomic allows a TEST_ONLY mode and if that's used (generic userspace will do that a few times for each frame at least) then you clobber shared state. Instead it's better to store that in your own mtk_plane_state which subclasses drm_plane_state.
- 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 mtk_drm_crtc *mtk_crtc;
- struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
- if (!state->crtc)
return;
- mtk_crtc = to_mtk_crtc(state->crtc);
- if (plane->fb)
drm_framebuffer_unreference(plane->fb);
- if (state->fb)
drm_framebuffer_reference(state->fb);
- 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->mtk_crtc = mtk_crtc;
- if (mtk_plane->flip_obj)
mtk_drm_crtc_plane_config(mtk_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 mtk_drm_crtc *mtk_crtc = to_mtk_crtc(old_state->crtc);
- if (!old_state->crtc)
return;
- if (mtk_crtc)
mtk_drm_crtc_plane_config(mtk_crtc, mtk_plane->idx, false, 0);
- mtk_drm_crtc_commit(mtk_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,
+};
+static void mtk_plane_attach_zpos_property(struct drm_plane *plane,
unsigned int zpos, unsigned int max_plane)
+{
- struct drm_device *dev = plane->dev;
- struct mtk_drm_private *dev_priv = dev->dev_private;
- struct drm_property *prop;
- prop = dev_priv->plane_zpos_property;
- if (!prop) {
prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
"zpos", 0, max_plane - 1);
I know that there's lots of other drivers exposing zpos already, but I really think we should standardize this properly and document what it means. So - add a bit more text to the kerneldoc/doobook, especially what should happen when there's a conflict in zpos. - move zpos registration/decoding into drm core, which means adding it to drm_plane_state - have an open-source implementation using this somewhere (ddx, wayland, hwc, ...).
I think for now it's better to drop the zpos property from initial mediatek enabling.
Cheers, Daniel
if (!prop)
return;
dev_priv->plane_zpos_property = prop;
- }
- drm_object_attach_property(&plane->base, prop, zpos);
+}
+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;
- if (type == DRM_PLANE_TYPE_OVERLAY)
mtk_plane_attach_zpos_property(&mtk_plane->base,
zpos, max_plane);
- 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..80075d493 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_plane.h @@ -0,0 +1,38 @@ +/*
- 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_
+struct fence; +struct mtk_crtc; +struct mtk_drm_gem_obj;
+#define to_mtk_plane(x) container_of(x, struct mtk_drm_plane, base)
+struct mtk_drm_plane {
- struct drm_plane base;
- struct mtk_drm_crtc *mtk_crtc;
- unsigned int idx;
- unsigned int disp_size;
- struct mtk_drm_gem_obj *flip_obj;
+};
+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
2.5.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi Daniel,
thank you for the comments.
Am Dienstag, den 22.09.2015, 11:38 +0200 schrieb Daniel Vetter: [...]
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..fc071fe --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -0,0 +1,471 @@
[...]
+static int mtk_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool async)
+{
- return drm_atomic_helper_commit(dev, state, false);
This isn't a proper async commit operation, it will still block userspace unecessarily. See e.g. the vc4 patches for a proper one.
I'll drop this function and assign .atomic_commit to drm_atomic_helper_commit directly.
[...]
+static int mtk_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
+{
[...]
- 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;
- if (!visible)
return 0;
- mtk_plane->disp_size = (dest.y2 - dest.y1) << 16 | (dest.x2 - dest.x1);
I think it might work out ok but it's very fragile to update object state from your atomic_check hooks - atomic allows a TEST_ONLY mode and if that's used (generic userspace will do that a few times for each frame at least) then you clobber shared state. Instead it's better to store that in your own mtk_plane_state which subclasses drm_plane_state.
Ok, will do.
[...]
+static void mtk_plane_attach_zpos_property(struct drm_plane *plane,
unsigned int zpos, unsigned int max_plane)
+{
- struct drm_device *dev = plane->dev;
- struct mtk_drm_private *dev_priv = dev->dev_private;
- struct drm_property *prop;
- prop = dev_priv->plane_zpos_property;
- if (!prop) {
prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
"zpos", 0, max_plane - 1);
I know that there's lots of other drivers exposing zpos already, but I really think we should standardize this properly and document what it means. So
- add a bit more text to the kerneldoc/doobook, especially what should happen when there's a conflict in zpos.
- move zpos registration/decoding into drm core, which means adding it to drm_plane_state
- have an open-source implementation using this somewhere (ddx, wayland, hwc, ...).
I think for now it's better to drop the zpos property from initial mediatek enabling.
Alright, I'll separate this from the initial patchset.
best regards Philipp
From: CK Hu ck.hu@mediatek.com
Add a driver for the MIPI DSI function block of the Mediatek display subsystem.
Signed-off-by: Jitao Shi jitao.shi@mediatek.com Signed-off-by: Philipp Zabel p.zabel@pengutronix.de --- drivers/gpu/drm/mediatek/Makefile | 1 + drivers/gpu/drm/mediatek/mtk_drm_dsi.c | 1118 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_dsi.h | 51 ++ 3 files changed, 1170 insertions(+) create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_dsi.c create mode 100644 drivers/gpu/drm/mediatek/mtk_drm_dsi.h
diff --git a/drivers/gpu/drm/mediatek/Makefile b/drivers/gpu/drm/mediatek/Makefile index d801572..213450c 100644 --- a/drivers/gpu/drm/mediatek/Makefile +++ b/drivers/gpu/drm/mediatek/Makefile @@ -2,6 +2,7 @@ mediatek-drm-y := mtk_drm_drv.o \ mtk_drm_crtc.o \ mtk_drm_ddp.o \ mtk_drm_ddp_comp.o \ + mtk_drm_dsi.o \ mtk_drm_fb.o \ mtk_drm_gem.o \ mtk_drm_plane.o diff --git a/drivers/gpu/drm/mediatek/mtk_drm_dsi.c b/drivers/gpu/drm/mediatek/mtk_drm_dsi.c new file mode 100644 index 0000000..44cab33 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_dsi.c @@ -0,0 +1,1118 @@ +/* + * 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 <drm/drm_crtc_helper.h> +#include <drm/drm_atomic_helper.h> + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <linux/of_gpio.h> +#include <linux/gpio.h> + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/of_graph.h> +#include <linux/component.h> + +#include <linux/regulator/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> +#include <video/videomode.h> + +#include "mtk_drm_drv.h" +#include "mtk_drm_crtc.h" + +#include "mtk_drm_ddp.h" + +#include "mtk_drm_gem.h" +#include "mtk_drm_dsi.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 MIPITX_DSI0_CON 0x00 +#define RG_DSI0_LDOCORE_EN BIT(0) +#define RG_DSI0_CKG_LDOOUT_EN BIT(1) +#define RG_DSI0_BCLK_SEL (3 << 2) +#define RG_DSI0_LD_IDX_SEL (7 << 4) +#define RG_DSI0_PHYCLK_SEL (2 << 8) +#define RG_DSI0_DSICLK_FREQ_SEL BIT(10) +#define RG_DSI0_LPTX_CLMP_EN BIT(11) + +#define MIPITX_DSI0_CLOCK_LANE 0x04 +#define RG_DSI0_LNTC_LDOOUT_EN BIT(0) +#define RG_DSI0_LNTC_CKLANE_EN BIT(1) +#define RG_DSI0_LNTC_LPTX_IPLUS1 BIT(2) +#define RG_DSI0_LNTC_LPTX_IPLUS2 BIT(3) +#define RG_DSI0_LNTC_LPTX_IMINUS BIT(4) +#define RG_DSI0_LNTC_LPCD_IPLUS BIT(5) +#define RG_DSI0_LNTC_LPCD_IMLUS BIT(6) +#define RG_DSI0_LNTC_RT_CODE (0xf << 8) + +#define MIPITX_DSI0_DATA_LANE0 0x08 +#define RG_DSI0_LNT0_LDOOUT_EN BIT(0) +#define RG_DSI0_LNT0_CKLANE_EN BIT(1) +#define RG_DSI0_LNT0_LPTX_IPLUS1 BIT(2) +#define RG_DSI0_LNT0_LPTX_IPLUS2 BIT(3) +#define RG_DSI0_LNT0_LPTX_IMINUS BIT(4) +#define RG_DSI0_LNT0_LPCD_IPLUS BIT(5) +#define RG_DSI0_LNT0_LPCD_IMINUS BIT(6) +#define RG_DSI0_LNT0_RT_CODE (0xf << 8) + +#define MIPITX_DSI0_DATA_LANE1 0x0c +#define RG_DSI0_LNT1_LDOOUT_EN BIT(0) +#define RG_DSI0_LNT1_CKLANE_EN BIT(1) +#define RG_DSI0_LNT1_LPTX_IPLUS1 BIT(2) +#define RG_DSI0_LNT1_LPTX_IPLUS2 BIT(3) +#define RG_DSI0_LNT1_LPTX_IMINUS BIT(4) +#define RG_DSI0_LNT1_LPCD_IPLUS BIT(5) +#define RG_DSI0_LNT1_LPCD_IMINUS BIT(6) +#define RG_DSI0_LNT1_RT_CODE (0xf << 8) + +#define MIPITX_DSI0_DATA_LANE2 0x10 +#define RG_DSI0_LNT2_LDOOUT_EN BIT(0) +#define RG_DSI0_LNT2_CKLANE_EN BIT(1) +#define RG_DSI0_LNT2_LPTX_IPLUS1 BIT(2) +#define RG_DSI0_LNT2_LPTX_IPLUS2 BIT(3) +#define RG_DSI0_LNT2_LPTX_IMINUS BIT(4) +#define RG_DSI0_LNT2_LPCD_IPLUS BIT(5) +#define RG_DSI0_LNT2_LPCD_IMINUS BIT(6) +#define RG_DSI0_LNT2_RT_CODE (0xf << 8) + +#define MIPITX_DSI0_DATA_LANE3 0x14 +#define RG_DSI0_LNT3_LDOOUT_EN BIT(0) +#define RG_DSI0_LNT3_CKLANE_EN BIT(1) +#define RG_DSI0_LNT3_LPTX_IPLUS1 BIT(2) +#define RG_DSI0_LNT3_LPTX_IPLUS2 BIT(3) +#define RG_DSI0_LNT3_LPTX_IMINUS BIT(4) +#define RG_DSI0_LNT3_LPCD_IPLUS BIT(5) +#define RG_DSI0_LNT3_LPCD_IMINUS BIT(6) +#define RG_DSI0_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_DSI0_MPPLL_PLL_EN BIT(0) +#define RG_DSI0_MPPLL_DIV_MSK (0x1ff << 1) +#define RG_DSI0_MPPLL_PREDIV (3 << 1) +#define RG_DSI0_MPPLL_TXDIV0 (3 << 3) +#define RG_DSI0_MPPLL_TXDIV1 (3 << 5) +#define RG_DSI0_MPPLL_POSDIV (7 << 7) +#define RG_DSI0_MPPLL_MONVC_EN BIT(10) +#define RG_DSI0_MPPLL_MONREF_EN BIT(11) +#define RG_DSI0_MPPLL_VOD_EN BIT(12) + +#define MIPITX_DSI_PLL_CON1 0x54 +#define RG_DSI0_MPPLL_SDM_FRA_EN BIT(0) +#define RG_DSI0_MPPLL_SDM_SSC_PH_INIT BIT(1) +#define RG_DSI0_MPPLL_SDM_SSC_EN BIT(2) +#define RG_DSI0_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) + +#define NS_TO_CYCLE(n, c) ((n) / c + (((n) % c) ? 1 : 0)) + +static void mtk_dsi_tx_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data) +{ + u32 temp = readl(dsi->dsi_tx_reg_base + offset); + + writel((temp & ~mask) | (data & mask), dsi->dsi_tx_reg_base + offset); +} + +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_clk_switch_off(struct mtk_dsi *dsi) +{ + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_CON0, RG_DSI0_MPPLL_PLL_EN, 0); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN, + RG_DSI_PAD_TIE_LOW_EN); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_CLOCK_LANE, RG_DSI0_LNTC_LDOOUT_EN, 0); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE0, RG_DSI0_LNT0_LDOOUT_EN, 0); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE1, RG_DSI0_LNT1_LDOOUT_EN, 0); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE2, RG_DSI0_LNT2_LDOOUT_EN, 0); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE3, RG_DSI0_LNT3_LDOOUT_EN, 0); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_PWR, + RG_DSI_MPPLL_SDM_ISO_EN | RG_DSI_MPPLL_SDM_PWR_ON, + RG_DSI_MPPLL_SDM_ISO_EN); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_TOP_CON, RG_DSI_LNT_HS_BIAS_EN, 0); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_CON, + RG_DSI0_CKG_LDOOUT_EN | RG_DSI0_LDOCORE_EN, 0); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_BG_CON, + RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN, 0); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_CON0, RG_DSI0_MPPLL_DIV_MSK, 0); +} + +static void dsi_phy_clk_setting(struct mtk_dsi *dsi) +{ + unsigned int txdiv; + u64 pcw; + u32 reg; + + mtk_dsi_tx_mask(dsi, 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_dsi_tx_mask(dsi, 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_dsi_tx_mask(dsi, MIPITX_DSI0_CON, + RG_DSI0_CKG_LDOOUT_EN | RG_DSI0_LDOCORE_EN, + RG_DSI0_CKG_LDOOUT_EN | RG_DSI0_LDOCORE_EN); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_PWR, + RG_DSI_MPPLL_SDM_PWR_ON | RG_DSI_MPPLL_SDM_ISO_EN, + RG_DSI_MPPLL_SDM_PWR_ON); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_CON0, RG_DSI0_MPPLL_PLL_EN, 0); + + /** + * 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); + if (dsi->data_rate > 1250) { + DRM_ERROR("Wrong mipi dsi data rate, pls check it\n"); + return; + } else if (dsi->data_rate >= 500) { + txdiv = 1; + } else if (dsi->data_rate >= 250) { + txdiv = 2; + } else if (dsi->data_rate >= 125) { + txdiv = 4; + } else if (dsi->data_rate > 62) { + txdiv = 8; + } else if (dsi->data_rate >= 50) { + txdiv = 16; + } else { + DRM_ERROR("Wrong pixel clk, pls check pixel clk\n"); + return; + } + + reg = readl(dsi->dsi_tx_reg_base + MIPITX_DSI_PLL_CON0); + + switch (txdiv) { + case 1: + reg &= ~RG_DSI0_MPPLL_TXDIV0; + reg &= ~RG_DSI0_MPPLL_TXDIV1; + break; + case 2: + reg = (reg & (~RG_DSI0_MPPLL_TXDIV0)) | (1 << 3); + reg &= ~RG_DSI0_MPPLL_TXDIV1; + break; + case 4: + reg = (reg & (~RG_DSI0_MPPLL_TXDIV0)) | (2 << 3); + reg &= ~RG_DSI0_MPPLL_TXDIV1; + break; + case 8: + reg = (reg & (~RG_DSI0_MPPLL_TXDIV0)) | (2 << 3); + reg = (reg & (~RG_DSI0_MPPLL_TXDIV1)) | (1 << 5); + break; + case 16: + reg = (reg & (~RG_DSI0_MPPLL_TXDIV0)) | (2 << 3); + reg = (reg & (~RG_DSI0_MPPLL_TXDIV1)) | (2 << 5); + break; + default: + break; + } + reg &= ~RG_DSI0_MPPLL_PREDIV; + writel(reg, dsi->dsi_tx_reg_base + MIPITX_DSI_PLL_CON0); + + /** + * 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) dsi->data_rate * txdiv) << 24; + pcw /= 13; + writel(pcw, dsi->dsi_tx_reg_base + MIPITX_DSI_PLL_CON2); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_CON1, + RG_DSI0_MPPLL_SDM_FRA_EN, RG_DSI0_MPPLL_SDM_FRA_EN); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_CLOCK_LANE, + RG_DSI0_LNTC_LDOOUT_EN, RG_DSI0_LNTC_LDOOUT_EN); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE0, + RG_DSI0_LNT0_LDOOUT_EN, RG_DSI0_LNT0_LDOOUT_EN); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE1, + RG_DSI0_LNT1_LDOOUT_EN, RG_DSI0_LNT1_LDOOUT_EN); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE2, + RG_DSI0_LNT2_LDOOUT_EN, RG_DSI0_LNT2_LDOOUT_EN); + mtk_dsi_tx_mask(dsi, MIPITX_DSI0_DATA_LANE3, + RG_DSI0_LNT3_LDOOUT_EN, RG_DSI0_LNT3_LDOOUT_EN); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_CON0, + RG_DSI0_MPPLL_PLL_EN, RG_DSI0_MPPLL_PLL_EN); + + usleep_range(20, 100); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_PLL_CON1, RG_DSI0_MPPLL_SDM_SSC_EN, 0); + + mtk_dsi_tx_mask(dsi, MIPITX_DSI_TOP_CON, RG_DSI_PAD_TIE_LOW_EN, 0); +} + +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; + + dsi_phy_clk_setting(dsi); + + 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); + + dsi_phy_clk_switch_off(dsi); +} + +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_phy_clk_switch_off(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 void mtk_dsi_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + switch (mode) { + case DRM_MODE_DPMS_ON: + mtk_output_dsi_enable(dsi); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + mtk_output_dsi_disable(dsi); + break; + default: + break; + } +} + +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_prepare(struct drm_encoder *encoder) +{ + struct mtk_dsi *dsi = encoder_to_dsi(encoder); + + mtk_output_dsi_disable(dsi); +} + +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_commit(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) +{ + enum drm_connector_status status = connector_status_unknown; + + status = connector_status_connected; /* FIXME? */ + + return status; +} + +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 = { + .dpms = mtk_dsi_encoder_dpms, + .mode_fixup = mtk_dsi_encoder_mode_fixup, + .prepare = mtk_dsi_encoder_prepare, + .mode_set = mtk_dsi_encoder_mode_set, + .commit = mtk_dsi_encoder_commit, +}; + +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; + + dsi = platform_get_drvdata(to_platform_device(dev)); + if (!dsi) { + ret = -EFAULT; + DRM_ERROR("Failed to get driver data from dsi device\n"); + return ret; + } + + 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) + return -EPROBE_DEFER; + + 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) + return -EPROBE_DEFER; + } + + dsi->dsi0_engine_clk_cg = devm_clk_get(dev, "engine"); + if (IS_ERR(dsi->dsi0_engine_clk_cg)) { + dev_err(dev, "cannot get dsi0_engine_clk_cg\n"); + return PTR_ERR(dsi->dsi0_engine_clk_cg); + } + + dsi->dsi0_digital_clk_cg = devm_clk_get(dev, "digital"); + if (IS_ERR(dsi->dsi0_digital_clk_cg)) { + dev_err(dev, "cannot get dsi0_digital_disp_ck\n"); + return PTR_ERR(dsi->dsi0_digital_clk_cg); + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->dsi_reg_base = devm_ioremap_resource(dev, regs); + if (IS_ERR(dsi->dsi_reg_base)) { + dev_err(dev, "cannot get dsi->dsi_reg_base\n"); + return PTR_ERR(dsi->dsi_reg_base); + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + dsi->dsi_tx_reg_base = devm_ioremap_resource(dev, regs); + if (IS_ERR(dsi->dsi_tx_reg_base)) { + dev_err(dev, "cannot get dsi->dsi_tx_reg_base\n"); + return PTR_ERR(dsi->dsi_tx_reg_base); + } + + platform_set_drvdata(pdev, dsi); + + ret = component_add(&pdev->dev, &mtk_dsi_component_ops); + if (ret) { + dev_err(dev, "cannot get dsi->dsi_tx_reg_base\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, + .owner = THIS_MODULE, + .pm = &mtk_dsi_pm_ops, + }, +}; + +module_platform_driver(mtk_dsi_driver); diff --git a/drivers/gpu/drm/mediatek/mtk_drm_dsi.h b/drivers/gpu/drm/mediatek/mtk_drm_dsi.h new file mode 100644 index 0000000..27183b2 --- /dev/null +++ b/drivers/gpu/drm/mediatek/mtk_drm_dsi.h @@ -0,0 +1,51 @@ +/* + * 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_DSI_H_ +#define _MTK_DRM_DSI_H_ + +#include <drm/drm_crtc.h> + +struct mtk_dsi { + struct drm_device *drm_dev; + struct drm_encoder encoder; + struct drm_connector conn; + struct drm_panel *panel; + struct drm_bridge *bridge; + + 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
From: CK Hu ck.hu@mediatek.com
Add Mediatek legacy framebuffer support.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: YT Shen yt.shen@mediatek.com --- drivers/gpu/drm/mediatek/Kconfig | 12 +++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 13 +++ drivers/gpu/drm/mediatek/mtk_drm_fb.c | 192 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_fb.h | 1 + 4 files changed, 218 insertions(+)
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig index 5343cf1..fa581fb 100644 --- a/drivers/gpu/drm/mediatek/Kconfig +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -14,3 +14,15 @@ config DRM_MEDIATEK This driver provides kernel mode setting and buffer management to userspace.
+config DRM_MEDIATEK_FBDEV + bool "Enable legacy fbdev support for Mediatek DRM" + depends on DRM_MEDIATEK + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select DRM_KMS_FB_HELPER + help + Choose this option if you have a need for the legacy + fbdev support. Note that this support also provides + the Linux console on top of the Mediatek DRM mode + setting driver. diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index fc071fe..b67c582 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -46,6 +46,9 @@ 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, +#ifdef CONFIG_DRM_MEDIATEK_FBDEV + .output_poll_changed = mtk_drm_mode_output_poll_changed, +#endif };
static const enum mtk_ddp_comp_type mtk_ddp_main[] = { @@ -140,6 +143,12 @@ static int mtk_drm_kms_init(struct drm_device *dev) drm_kms_helper_poll_init(dev); drm_mode_config_reset(dev);
+#ifdef CONFIG_DRM_MEDIATEK_FBDEV + err = mtk_fbdev_create(dev); + if (err) + goto err_larb_get; +#endif + return 0;
err_larb_get: @@ -160,6 +169,10 @@ static void mtk_drm_kms_deinit(struct drm_device *dev) { drm_kms_helper_poll_fini(dev);
+#ifdef CONFIG_DRM_MEDIATEK_FBDEV + mtk_fbdev_destroy(dev); +#endif + drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev); } diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c index dfa931b..9295ad3 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_fb.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c @@ -102,6 +102,198 @@ static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev, return mtk_fb; }
+#ifdef CONFIG_DRM_MEDIATEK_FBDEV +static int mtk_drm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fb_helper *helper = info->par; + struct mtk_drm_fb *mtk_fb = to_mtk_fb(helper->fb); + + return mtk_drm_gem_mmap_buf(mtk_fb->gem_obj[0], vma); +} + +static struct fb_ops mtk_fb_ops = { + .owner = THIS_MODULE, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_blank = drm_fb_helper_blank, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_mmap = mtk_drm_fb_mmap, +}; + +static int mtk_fbdev_probe(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_device *dev = helper->dev; + struct drm_mode_fb_cmd2 mode = { 0 }; + struct mtk_drm_fb *mtk_fb; + struct mtk_drm_gem_obj *mtk_gem; + struct drm_gem_object *gem; + struct fb_info *info; + struct drm_framebuffer *fb; + unsigned long offset; + size_t size; + int err; + + mode.width = sizes->surface_width; + mode.height = sizes->surface_height; + mode.pitches[0] = sizes->surface_width * ((sizes->surface_bpp + 7) / 8); + mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + mode.height = mode.height;/* << 1; for fb use? */ + size = mode.pitches[0] * mode.height; + dev_info(dev->dev, "mtk_fbdev_probe %dx%d bpp %d pitch %d size %zu\n", + mode.width, mode.height, sizes->surface_bpp, mode.pitches[0], + size); + + mtk_gem = mtk_drm_gem_create(dev, size, true); + if (IS_ERR(mtk_gem)) { + err = PTR_ERR(mtk_gem); + goto fini; + } + + gem = &mtk_gem->base; + + mtk_fb = mtk_drm_framebuffer_init(dev, &mode, &gem); + if (IS_ERR(mtk_fb)) { + dev_err(dev->dev, "failed to allocate DRM framebuffer\n"); + err = PTR_ERR(mtk_fb); + goto free; + } + fb = &mtk_fb->base; + + info = framebuffer_alloc(0, dev->dev); + if (!info) { + dev_err(dev->dev, "failed to allocate framebuffer info\n"); + err = PTR_ERR(info); + goto release; + } + + helper->fb = fb; + helper->fbdev = info; + + info->par = helper; + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &mtk_fb_ops; + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err < 0) { + dev_err(dev->dev, "failed to allocate color map: %d\n", err); + goto destroy; + } + + drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(info, helper, fb->width, fb->height); + + offset = info->var.xoffset * (fb->bits_per_pixel + 7) / 8; + offset += info->var.yoffset * fb->pitches[0]; + + strcpy(info->fix.id, "mtk"); + /* dev->mode_config.fb_base = (resource_size_t)bo->paddr; */ + info->var.yres = info->var.yres_virtual;/* >> 1; for fb use? */ + info->fix.smem_start = mtk_gem->dma_addr + offset; + info->fix.smem_len = size; + info->screen_base = mtk_gem->kvaddr + offset; + info->screen_size = size; + + return 0; + +destroy: + drm_framebuffer_unregister_private(fb); + mtk_drm_fb_destroy(fb); +release: + framebuffer_release(info); +free: + mtk_drm_gem_free_object(&mtk_gem->base); +fini: + dev_err(dev->dev, "mtk_fbdev_probe fail\n"); + return err; +} + +static const struct drm_fb_helper_funcs mtk_drm_fb_helper_funcs = { + .fb_probe = mtk_fbdev_probe, +}; + +int mtk_fbdev_create(struct drm_device *dev) +{ + struct mtk_drm_private *priv = dev->dev_private; + struct drm_fb_helper *fbdev; + int ret; + + fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) + return -ENOMEM; + + drm_fb_helper_prepare(dev, fbdev, &mtk_drm_fb_helper_funcs); + + ret = drm_fb_helper_init(dev, fbdev, dev->mode_config.num_crtc, + dev->mode_config.num_connector); + if (ret) { + dev_err(dev->dev, "failed to initialize DRM FB helper\n"); + goto fini; + } + + ret = drm_fb_helper_single_add_all_connectors(fbdev); + if (ret) { + dev_err(dev->dev, "failed to add connectors\n"); + goto fini; + } + + ret = drm_fb_helper_initial_config(fbdev, FBDEV_BPP); + if (ret) { + dev_err(dev->dev, "failed to set initial configuration\n"); + goto fini; + } + priv->fb_helper = fbdev; + + return 0; + +fini: + drm_fb_helper_fini(fbdev); + + return ret; +} + +void mtk_fbdev_destroy(struct drm_device *dev) +{ + struct mtk_drm_private *priv = dev->dev_private; + struct drm_fb_helper *helper = priv->fb_helper; + struct fb_info *info = helper->fbdev; + + if (info) { + int err; + + err = unregister_framebuffer(info); + if (err < 0) + DRM_DEBUG_KMS("failed to unregister framebuffer\n"); + + if (info->cmap.len) + fb_dealloc_cmap(&info->cmap); + + framebuffer_release(info); + } + + if (helper->fb) { + drm_framebuffer_unregister_private(helper->fb); + mtk_drm_fb_destroy(helper->fb); + } + + drm_fb_helper_fini(helper); +} + +void mtk_drm_mode_output_poll_changed(struct drm_device *dev) +{ + struct mtk_drm_private *priv = dev->dev_private; + + if (priv->fb_helper) + drm_fb_helper_hotplug_event(priv->fb_helper); +} +#endif + struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, struct drm_file *file, struct drm_mode_fb_cmd2 *cmd) diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h index 9ce7307..acbfecf 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_fb.h +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h @@ -15,6 +15,7 @@ #define MTK_DRM_FB_H
#define MAX_FB_OBJ 3 +#define FBDEV_BPP 16
struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb, unsigned int plane);
On Fri, Sep 18, 2015 at 06:12:02PM +0200, Philipp Zabel wrote:
From: CK Hu ck.hu@mediatek.com
Add Mediatek legacy framebuffer support.
Signed-off-by: CK Hu ck.hu@mediatek.com Signed-off-by: YT Shen yt.shen@mediatek.com
drivers/gpu/drm/mediatek/Kconfig | 12 +++ drivers/gpu/drm/mediatek/mtk_drm_drv.c | 13 +++ drivers/gpu/drm/mediatek/mtk_drm_fb.c | 192 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/mediatek/mtk_drm_fb.h | 1 + 4 files changed, 218 insertions(+)
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig index 5343cf1..fa581fb 100644 --- a/drivers/gpu/drm/mediatek/Kconfig +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -14,3 +14,15 @@ config DRM_MEDIATEK This driver provides kernel mode setting and buffer management to userspace.
+config DRM_MEDIATEK_FBDEV
- bool "Enable legacy fbdev support for Mediatek DRM"
- depends on DRM_MEDIATEK
- select FB_SYS_FILLRECT
- select FB_SYS_COPYAREA
- select FB_SYS_IMAGEBLIT
- select DRM_KMS_FB_HELPER
- help
Choose this option if you have a need for the legacy
fbdev support. Note that this support also provides
the Linux console on top of the Mediatek DRM mode
setting driver.
With the new Kconfig for fbdev emulation in 4.3 and the module option to disable it queued up for 4.4 driver-private Kconfig entries for fbdev aren't needed any more. -Daniel
diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index fc071fe..b67c582 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -46,6 +46,9 @@ 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, +#ifdef CONFIG_DRM_MEDIATEK_FBDEV
- .output_poll_changed = mtk_drm_mode_output_poll_changed,
+#endif };
static const enum mtk_ddp_comp_type mtk_ddp_main[] = { @@ -140,6 +143,12 @@ static int mtk_drm_kms_init(struct drm_device *dev) drm_kms_helper_poll_init(dev); drm_mode_config_reset(dev);
+#ifdef CONFIG_DRM_MEDIATEK_FBDEV
- err = mtk_fbdev_create(dev);
- if (err)
goto err_larb_get;
+#endif
- return 0;
err_larb_get: @@ -160,6 +169,10 @@ static void mtk_drm_kms_deinit(struct drm_device *dev) { drm_kms_helper_poll_fini(dev);
+#ifdef CONFIG_DRM_MEDIATEK_FBDEV
- mtk_fbdev_destroy(dev);
+#endif
- drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev);
} diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.c b/drivers/gpu/drm/mediatek/mtk_drm_fb.c index dfa931b..9295ad3 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_fb.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.c @@ -102,6 +102,198 @@ static struct mtk_drm_fb *mtk_drm_framebuffer_init(struct drm_device *dev, return mtk_fb; }
+#ifdef CONFIG_DRM_MEDIATEK_FBDEV +static int mtk_drm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{
- struct drm_fb_helper *helper = info->par;
- struct mtk_drm_fb *mtk_fb = to_mtk_fb(helper->fb);
- return mtk_drm_gem_mmap_buf(mtk_fb->gem_obj[0], vma);
+}
+static struct fb_ops mtk_fb_ops = {
- .owner = THIS_MODULE,
- .fb_fillrect = sys_fillrect,
- .fb_copyarea = sys_copyarea,
- .fb_imageblit = sys_imageblit,
- .fb_check_var = drm_fb_helper_check_var,
- .fb_set_par = drm_fb_helper_set_par,
- .fb_blank = drm_fb_helper_blank,
- .fb_pan_display = drm_fb_helper_pan_display,
- .fb_setcmap = drm_fb_helper_setcmap,
- .fb_mmap = mtk_drm_fb_mmap,
+};
+static int mtk_fbdev_probe(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
+{
- struct drm_device *dev = helper->dev;
- struct drm_mode_fb_cmd2 mode = { 0 };
- struct mtk_drm_fb *mtk_fb;
- struct mtk_drm_gem_obj *mtk_gem;
- struct drm_gem_object *gem;
- struct fb_info *info;
- struct drm_framebuffer *fb;
- unsigned long offset;
- size_t size;
- int err;
- mode.width = sizes->surface_width;
- mode.height = sizes->surface_height;
- mode.pitches[0] = sizes->surface_width * ((sizes->surface_bpp + 7) / 8);
- mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
sizes->surface_depth);
- mode.height = mode.height;/* << 1; for fb use? */
- size = mode.pitches[0] * mode.height;
- dev_info(dev->dev, "mtk_fbdev_probe %dx%d bpp %d pitch %d size %zu\n",
mode.width, mode.height, sizes->surface_bpp, mode.pitches[0],
size);
- mtk_gem = mtk_drm_gem_create(dev, size, true);
- if (IS_ERR(mtk_gem)) {
err = PTR_ERR(mtk_gem);
goto fini;
- }
- gem = &mtk_gem->base;
- mtk_fb = mtk_drm_framebuffer_init(dev, &mode, &gem);
- if (IS_ERR(mtk_fb)) {
dev_err(dev->dev, "failed to allocate DRM framebuffer\n");
err = PTR_ERR(mtk_fb);
goto free;
- }
- fb = &mtk_fb->base;
- info = framebuffer_alloc(0, dev->dev);
- if (!info) {
dev_err(dev->dev, "failed to allocate framebuffer info\n");
err = PTR_ERR(info);
goto release;
- }
- helper->fb = fb;
- helper->fbdev = info;
- info->par = helper;
- info->flags = FBINFO_FLAG_DEFAULT;
- info->fbops = &mtk_fb_ops;
- err = fb_alloc_cmap(&info->cmap, 256, 0);
- if (err < 0) {
dev_err(dev->dev, "failed to allocate color map: %d\n", err);
goto destroy;
- }
- drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
- drm_fb_helper_fill_var(info, helper, fb->width, fb->height);
- offset = info->var.xoffset * (fb->bits_per_pixel + 7) / 8;
- offset += info->var.yoffset * fb->pitches[0];
- strcpy(info->fix.id, "mtk");
- /* dev->mode_config.fb_base = (resource_size_t)bo->paddr; */
- info->var.yres = info->var.yres_virtual;/* >> 1; for fb use? */
- info->fix.smem_start = mtk_gem->dma_addr + offset;
- info->fix.smem_len = size;
- info->screen_base = mtk_gem->kvaddr + offset;
- info->screen_size = size;
- return 0;
+destroy:
- drm_framebuffer_unregister_private(fb);
- mtk_drm_fb_destroy(fb);
+release:
- framebuffer_release(info);
+free:
- mtk_drm_gem_free_object(&mtk_gem->base);
+fini:
- dev_err(dev->dev, "mtk_fbdev_probe fail\n");
- return err;
+}
+static const struct drm_fb_helper_funcs mtk_drm_fb_helper_funcs = {
- .fb_probe = mtk_fbdev_probe,
+};
+int mtk_fbdev_create(struct drm_device *dev) +{
- struct mtk_drm_private *priv = dev->dev_private;
- struct drm_fb_helper *fbdev;
- int ret;
- fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL);
- if (!fbdev)
return -ENOMEM;
- drm_fb_helper_prepare(dev, fbdev, &mtk_drm_fb_helper_funcs);
- ret = drm_fb_helper_init(dev, fbdev, dev->mode_config.num_crtc,
dev->mode_config.num_connector);
- if (ret) {
dev_err(dev->dev, "failed to initialize DRM FB helper\n");
goto fini;
- }
- ret = drm_fb_helper_single_add_all_connectors(fbdev);
- if (ret) {
dev_err(dev->dev, "failed to add connectors\n");
goto fini;
- }
- ret = drm_fb_helper_initial_config(fbdev, FBDEV_BPP);
- if (ret) {
dev_err(dev->dev, "failed to set initial configuration\n");
goto fini;
- }
- priv->fb_helper = fbdev;
- return 0;
+fini:
- drm_fb_helper_fini(fbdev);
- return ret;
+}
+void mtk_fbdev_destroy(struct drm_device *dev) +{
- struct mtk_drm_private *priv = dev->dev_private;
- struct drm_fb_helper *helper = priv->fb_helper;
- struct fb_info *info = helper->fbdev;
- if (info) {
int err;
err = unregister_framebuffer(info);
if (err < 0)
DRM_DEBUG_KMS("failed to unregister framebuffer\n");
if (info->cmap.len)
fb_dealloc_cmap(&info->cmap);
framebuffer_release(info);
- }
- if (helper->fb) {
drm_framebuffer_unregister_private(helper->fb);
mtk_drm_fb_destroy(helper->fb);
- }
- drm_fb_helper_fini(helper);
+}
+void mtk_drm_mode_output_poll_changed(struct drm_device *dev) +{
- struct mtk_drm_private *priv = dev->dev_private;
- if (priv->fb_helper)
drm_fb_helper_hotplug_event(priv->fb_helper);
+} +#endif
struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev, struct drm_file *file, struct drm_mode_fb_cmd2 *cmd) diff --git a/drivers/gpu/drm/mediatek/mtk_drm_fb.h b/drivers/gpu/drm/mediatek/mtk_drm_fb.h index 9ce7307..acbfecf 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_fb.h +++ b/drivers/gpu/drm/mediatek/mtk_drm_fb.h @@ -15,6 +15,7 @@ #define MTK_DRM_FB_H
#define MAX_FB_OBJ 3 +#define FBDEV_BPP 16
struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb, unsigned int plane); -- 2.5.1
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi Daniel,
Am Dienstag, den 22.09.2015, 11:29 +0200 schrieb Daniel Vetter:
diff --git a/drivers/gpu/drm/mediatek/Kconfig b/drivers/gpu/drm/mediatek/Kconfig index 5343cf1..fa581fb 100644 --- a/drivers/gpu/drm/mediatek/Kconfig +++ b/drivers/gpu/drm/mediatek/Kconfig @@ -14,3 +14,15 @@ config DRM_MEDIATEK This driver provides kernel mode setting and buffer management to userspace.
+config DRM_MEDIATEK_FBDEV
- bool "Enable legacy fbdev support for Mediatek DRM"
- depends on DRM_MEDIATEK
- select FB_SYS_FILLRECT
- select FB_SYS_COPYAREA
- select FB_SYS_IMAGEBLIT
- select DRM_KMS_FB_HELPER
- help
Choose this option if you have a need for the legacy
fbdev support. Note that this support also provides
the Linux console on top of the Mediatek DRM mode
setting driver.
With the new Kconfig for fbdev emulation in 4.3 and the module option to disable it queued up for 4.4 driver-private Kconfig entries for fbdev aren't needed any more. -Daniel
I'll drop it for the next patch submission, thanks.
regards Philipp
dri-devel@lists.freedesktop.org