mirror of https://gitee.com/openkylin/linux.git
MT8173 DRM support
- device tree binding documentation for all MT8173 display subsystem components - basic mediatek-drm driver for MT8173 with two optional, currently fixed output paths: - DSI encoder support for DSI and (via bridge) eDP panels - DPI encoder support for output to HDMI bridge - necessary clock tree changes for the DPI->HDMI path - export mtk-smi functions used by mediatek-drm -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJXMDw/AAoJEFDCiBxwnmDrw7oP/iIJNcj24JokDlRIFmVQIA+A /mLpvZcTgfCx3mZLSFG4N5VfB8CTg0/o2tj3e0C+GcVFWBbLpPemXR8gFxxdjeL2 TfmO73TVZjdcf3u6kSqBRLxEn1nbzYeZwcWKzsxgjts5Vo0vHnC3R2l2VCGPl8ay 9drqbFhOxGlGpkIia35z0mLb5dIJLQF6qkC5gFkTwY6ZKIV623/W8MQRbAU0wwwf NSAPwjEszFtFHDM2YIbROpEbFtmCpz6yZdblDARUbnK6mw6PW1g+FOrV+BiJWtzE A6c9FEKfSD3tExjH1DbsXx6jEIPjZ/wM979IrPU5kxOm6CuRAHa8KaRT6HraM8K4 CPYolbkg1rPOERIG5EOV0Cu7Fcpx6hB9eyp8m4cINw907+govMc1kDdsYv+U19tz ALkZ9qs3py4tv0Zo12LoXfLUfqGLzMPwP/BTgGpeL8sJiq4KYpXlp5IIDCYbis9d UBuozAzrF8pDsE9XHZ98LEE+mlsVVbItavY3+2JqlRcfZS5THTUoBDCVLGPtSR9g IS9giTOQeEX/8vXCrz6rAQNHwCWo+7gwBPCMH0Drak+1WsetYbC+Xe8q3bCYQqXg 4/z2AgE5O4vqVxWSuGGn8FsLmdu9wiNNND+k22Igq1hkBNGFjMthvDeIcIDNUYsX jWJtVZuK1jn6cbFhOmUe =vcoi -----END PGP SIGNATURE----- Merge tag 'mediatek-drm-2016-05-09' of git://git.pengutronix.de/git/pza/linux into drm-next MT8173 DRM support - device tree binding documentation for all MT8173 display subsystem components - basic mediatek-drm driver for MT8173 with two optional, currently fixed output paths: - DSI encoder support for DSI and (via bridge) eDP panels - DPI encoder support for output to HDMI bridge - necessary clock tree changes for the DPI->HDMI path - export mtk-smi functions used by mediatek-drm * tag 'mediatek-drm-2016-05-09' of git://git.pengutronix.de/git/pza/linux: clk: mediatek: remove hdmitx_dig_cts from TOP clocks clk: mediatek: Add hdmi_ref HDMI PHY PLL reference clock output clk: mediatek: make dpi0_sel propagate rate changes drm/mediatek: Add DPI sub driver drm/mediatek: Add DSI sub driver drm/mediatek: Add DRM Driver for Mediatek SoC MT8173. dt-bindings: drm/mediatek: Add Mediatek display subsystem dts binding memory: mtk-smi: export mtk_smi_larb_get/put
This commit is contained in:
commit
2e726dc4b4
|
@ -0,0 +1,203 @@
|
|||
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. Pixel clock and start
|
||||
of frame signal are distributed to the other function blocks by a DISP_MUTEX
|
||||
function block.
|
||||
|
||||
All DISP device tree nodes must be siblings to the central MMSYS_CONFIG node.
|
||||
For a description of the MMSYS_CONFIG binding, see
|
||||
Documentation/devicetree/bindings/arm/mediatek/mediatek,mmsys.txt.
|
||||
|
||||
DISP function blocks
|
||||
====================
|
||||
|
||||
A display stream starts at a source function block that reads pixel data from
|
||||
memory and ends with a sink function block that drives pixels on a display
|
||||
interface, or writes pixels back to memory. All DISP function blocks have
|
||||
their own register space, interrupt, and clock gate. The blocks that can
|
||||
access memory additionally have to list the IOMMU and local arbiter they are
|
||||
connected to.
|
||||
|
||||
For a description of the display interface sink function blocks, see
|
||||
Documentation/devicetree/bindings/display/mediatek/mediatek,dsi.txt and
|
||||
Documentation/devicetree/bindings/display/mediatek/mediatek,dpi.txt.
|
||||
|
||||
Required properties (all function blocks):
|
||||
- compatible: "mediatek,<chip>-disp-<function>", one of
|
||||
"mediatek,<chip>-disp-ovl" - overlay (4 layers, blending, csc)
|
||||
"mediatek,<chip>-disp-rdma" - read DMA / line buffer
|
||||
"mediatek,<chip>-disp-wdma" - write DMA
|
||||
"mediatek,<chip>-disp-color" - color processor
|
||||
"mediatek,<chip>-disp-aal" - adaptive ambient light controller
|
||||
"mediatek,<chip>-disp-gamma" - gamma correction
|
||||
"mediatek,<chip>-disp-merge" - merge streams from two RDMA sources
|
||||
"mediatek,<chip>-disp-split" - split stream to two encoders
|
||||
"mediatek,<chip>-disp-ufoe" - data compression engine
|
||||
"mediatek,<chip>-dsi" - DSI controller, see mediatek,dsi.txt
|
||||
"mediatek,<chip>-dpi" - DPI controller, see mediatek,dpi.txt
|
||||
"mediatek,<chip>-disp-mutex" - display mutex
|
||||
"mediatek,<chip>-disp-od" - overdrive
|
||||
- reg: Physical base address and length of the function block register space
|
||||
- interrupts: The interrupt signal from the function block (required, except for
|
||||
merge and split function blocks).
|
||||
- clocks: device clocks
|
||||
See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
|
||||
For most function blocks this is just a single clock input. Only the DSI and
|
||||
DPI controller nodes have multiple clock inputs. These are documented in
|
||||
mediatek,dsi.txt and mediatek,dpi.txt, respectively.
|
||||
|
||||
Required properties (DMA function blocks):
|
||||
- compatible: Should be one of
|
||||
"mediatek,<chip>-disp-ovl"
|
||||
"mediatek,<chip>-disp-rdma"
|
||||
"mediatek,<chip>-disp-wdma"
|
||||
- larb: Should contain a phandle pointing to the local arbiter device as defined
|
||||
in Documentation/devicetree/bindings/soc/mediatek/mediatek,smi-larb.txt
|
||||
- iommus: Should point to the respective IOMMU block with master port as
|
||||
argument, see Documentation/devicetree/bindings/iommu/mediatek,iommu.txt
|
||||
for details.
|
||||
|
||||
Examples:
|
||||
|
||||
mmsys: clock-controller@14000000 {
|
||||
compatible = "mediatek,mt8173-mmsys", "syscon";
|
||||
reg = <0 0x14000000 0 0x1000>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
#clock-cells = <1>;
|
||||
};
|
||||
|
||||
ovl0: ovl@1400c000 {
|
||||
compatible = "mediatek,mt8173-disp-ovl";
|
||||
reg = <0 0x1400c000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_OVL0>;
|
||||
iommus = <&iommu M4U_PORT_DISP_OVL0>;
|
||||
mediatek,larb = <&larb0>;
|
||||
};
|
||||
|
||||
ovl1: ovl@1400d000 {
|
||||
compatible = "mediatek,mt8173-disp-ovl";
|
||||
reg = <0 0x1400d000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 181 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_OVL1>;
|
||||
iommus = <&iommu M4U_PORT_DISP_OVL1>;
|
||||
mediatek,larb = <&larb4>;
|
||||
};
|
||||
|
||||
rdma0: rdma@1400e000 {
|
||||
compatible = "mediatek,mt8173-disp-rdma";
|
||||
reg = <0 0x1400e000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 182 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_RDMA0>;
|
||||
iommus = <&iommu M4U_PORT_DISP_RDMA0>;
|
||||
mediatek,larb = <&larb0>;
|
||||
};
|
||||
|
||||
rdma1: rdma@1400f000 {
|
||||
compatible = "mediatek,mt8173-disp-rdma";
|
||||
reg = <0 0x1400f000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_RDMA1>;
|
||||
iommus = <&iommu M4U_PORT_DISP_RDMA1>;
|
||||
mediatek,larb = <&larb4>;
|
||||
};
|
||||
|
||||
rdma2: rdma@14010000 {
|
||||
compatible = "mediatek,mt8173-disp-rdma";
|
||||
reg = <0 0x14010000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_RDMA2>;
|
||||
iommus = <&iommu M4U_PORT_DISP_RDMA2>;
|
||||
mediatek,larb = <&larb4>;
|
||||
};
|
||||
|
||||
wdma0: wdma@14011000 {
|
||||
compatible = "mediatek,mt8173-disp-wdma";
|
||||
reg = <0 0x14011000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_WDMA0>;
|
||||
iommus = <&iommu M4U_PORT_DISP_WDMA0>;
|
||||
mediatek,larb = <&larb0>;
|
||||
};
|
||||
|
||||
wdma1: wdma@14012000 {
|
||||
compatible = "mediatek,mt8173-disp-wdma";
|
||||
reg = <0 0x14012000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_WDMA1>;
|
||||
iommus = <&iommu M4U_PORT_DISP_WDMA1>;
|
||||
mediatek,larb = <&larb4>;
|
||||
};
|
||||
|
||||
color0: color@14013000 {
|
||||
compatible = "mediatek,mt8173-disp-color";
|
||||
reg = <0 0x14013000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_COLOR0>;
|
||||
};
|
||||
|
||||
color1: color@14014000 {
|
||||
compatible = "mediatek,mt8173-disp-color";
|
||||
reg = <0 0x14014000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_COLOR1>;
|
||||
};
|
||||
|
||||
aal@14015000 {
|
||||
compatible = "mediatek,mt8173-disp-aal";
|
||||
reg = <0 0x14015000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_AAL>;
|
||||
};
|
||||
|
||||
gamma@14016000 {
|
||||
compatible = "mediatek,mt8173-disp-gamma";
|
||||
reg = <0 0x14016000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_GAMMA>;
|
||||
};
|
||||
|
||||
ufoe@1401a000 {
|
||||
compatible = "mediatek,mt8173-disp-ufoe";
|
||||
reg = <0 0x1401a000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_UFOE>;
|
||||
};
|
||||
|
||||
dsi0: dsi@1401b000 {
|
||||
/* See mediatek,dsi.txt for details */
|
||||
};
|
||||
|
||||
dpi0: dpi@1401d000 {
|
||||
/* See mediatek,dpi.txt for details */
|
||||
};
|
||||
|
||||
mutex: mutex@14020000 {
|
||||
compatible = "mediatek,mt8173-disp-mutex";
|
||||
reg = <0 0x14020000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_MUTEX_32K>;
|
||||
};
|
||||
|
||||
od@14023000 {
|
||||
compatible = "mediatek,mt8173-disp-od";
|
||||
reg = <0 0x14023000 0 0x1000>;
|
||||
power-domains = <&scpsys MT8173_POWER_DOMAIN_MM>;
|
||||
clocks = <&mmsys CLK_MM_DISP_OD>;
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
Mediatek DPI Device
|
||||
===================
|
||||
|
||||
The Mediatek DPI function block is a sink of the display subsystem and
|
||||
provides 8-bit RGB/YUV444 or 8/10/10-bit YUV422 pixel data on a parallel
|
||||
output bus.
|
||||
|
||||
Required properties:
|
||||
- compatible: "mediatek,<chip>-dpi"
|
||||
- reg: Physical base address and length of the controller's registers
|
||||
- interrupts: The interrupt signal from the function block.
|
||||
- clocks: device clocks
|
||||
See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
|
||||
- clock-names: must contain "pixel", "engine", and "pll"
|
||||
- port: Output port node with endpoint definitions as described in
|
||||
Documentation/devicetree/bindings/graph.txt. This port should be connected
|
||||
to the input port of an attached HDMI or LVDS encoder chip.
|
||||
|
||||
Example:
|
||||
|
||||
dpi0: dpi@1401d000 {
|
||||
compatible = "mediatek,mt8173-dpi";
|
||||
reg = <0 0x1401d000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_LOW>;
|
||||
clocks = <&mmsys CLK_MM_DPI_PIXEL>,
|
||||
<&mmsys CLK_MM_DPI_ENGINE>,
|
||||
<&apmixedsys CLK_APMIXED_TVDPLL>;
|
||||
clock-names = "pixel", "engine", "pll";
|
||||
|
||||
port {
|
||||
dpi0_out: endpoint {
|
||||
remote-endpoint = <&hdmi0_in>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
Mediatek DSI Device
|
||||
===================
|
||||
|
||||
The Mediatek DSI function block is a sink of the display subsystem and can
|
||||
drive up to 4-lane MIPI DSI output. Two DSIs can be synchronized for dual-
|
||||
channel output.
|
||||
|
||||
Required properties:
|
||||
- compatible: "mediatek,<chip>-dsi"
|
||||
- reg: Physical base address and length of the controller's registers
|
||||
- interrupts: The interrupt signal from the function block.
|
||||
- clocks: device clocks
|
||||
See Documentation/devicetree/bindings/clock/clock-bindings.txt for details.
|
||||
- clock-names: must contain "engine", "digital", and "hs"
|
||||
- phys: phandle link to the MIPI D-PHY controller.
|
||||
- phy-names: must contain "dphy"
|
||||
- port: Output port node with endpoint definitions as described in
|
||||
Documentation/devicetree/bindings/graph.txt. This port should be connected
|
||||
to the input port of an attached DSI panel or DSI-to-eDP encoder chip.
|
||||
|
||||
MIPI TX Configuration Module
|
||||
============================
|
||||
|
||||
The MIPI TX configuration module controls the MIPI D-PHY.
|
||||
|
||||
Required properties:
|
||||
- compatible: "mediatek,<chip>-mipi-tx"
|
||||
- reg: Physical base address and length of the controller's registers
|
||||
- clocks: PLL reference clock
|
||||
- clock-output-names: name of the output clock line to the DSI encoder
|
||||
- #clock-cells: must be <0>;
|
||||
- #phy-cells: must be <0>.
|
||||
|
||||
Example:
|
||||
|
||||
mipi_tx0: mipi-dphy@10215000 {
|
||||
compatible = "mediatek,mt8173-mipi-tx";
|
||||
reg = <0 0x10215000 0 0x1000>;
|
||||
clocks = <&clk26m>;
|
||||
clock-output-names = "mipi_tx0_pll";
|
||||
#clock-cells = <0>;
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
|
||||
dsi0: dsi@1401b000 {
|
||||
compatible = "mediatek,mt8173-dsi";
|
||||
reg = <0 0x1401b000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_LOW>;
|
||||
clocks = <&mmsys MM_DSI0_ENGINE>, <&mmsys MM_DSI0_DIGITAL>,
|
||||
<&mipi_tx0>;
|
||||
clock-names = "engine", "digital", "hs";
|
||||
phys = <&mipi_tx0>;
|
||||
phy-names = "dphy";
|
||||
|
||||
port {
|
||||
dsi0_out: endpoint {
|
||||
remote-endpoint = <&panel_in>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -61,7 +61,6 @@ static const struct mtk_fixed_factor top_divs[] __initconst = {
|
|||
FACTOR(CLK_TOP_CLKRTC_INT, "clkrtc_int", "clk26m", 1, 793),
|
||||
FACTOR(CLK_TOP_FPC, "fpc_ck", "clk26m", 1, 1),
|
||||
|
||||
FACTOR(CLK_TOP_HDMITX_DIG_CTS, "hdmitx_dig_cts", "tvdpll_445p5m", 1, 3),
|
||||
FACTOR(CLK_TOP_HDMITXPLL_D2, "hdmitxpll_d2", "hdmitx_dig_cts", 1, 2),
|
||||
FACTOR(CLK_TOP_HDMITXPLL_D3, "hdmitxpll_d3", "hdmitx_dig_cts", 1, 3),
|
||||
|
||||
|
@ -558,7 +557,11 @@ static const struct mtk_composite top_muxes[] __initconst = {
|
|||
MUX_GATE(CLK_TOP_ATB_SEL, "atb_sel", atb_parents, 0x0090, 16, 2, 23),
|
||||
MUX_GATE(CLK_TOP_VENC_LT_SEL, "venclt_sel", venc_lt_parents, 0x0090, 24, 4, 31),
|
||||
/* CLK_CFG_6 */
|
||||
MUX_GATE(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents, 0x00a0, 0, 3, 7),
|
||||
/*
|
||||
* The dpi0_sel clock should not propagate rate changes to its parent
|
||||
* clock so the dpi driver can have full control over PLL and divider.
|
||||
*/
|
||||
MUX_GATE_FLAGS(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents, 0x00a0, 0, 3, 7, 0),
|
||||
MUX_GATE(CLK_TOP_IRDA_SEL, "irda_sel", irda_parents, 0x00a0, 8, 2, 15),
|
||||
MUX_GATE(CLK_TOP_CCI400_SEL, "cci400_sel", cci400_parents, 0x00a0, 16, 3, 23),
|
||||
MUX_GATE(CLK_TOP_AUD_1_SEL, "aud_1_sel", aud_1_parents, 0x00a0, 24, 2, 31),
|
||||
|
@ -1091,6 +1094,11 @@ static void __init mtk_apmixedsys_init(struct device_node *node)
|
|||
clk_data->clks[cku->id] = clk;
|
||||
}
|
||||
|
||||
clk = clk_register_divider(NULL, "hdmi_ref", "tvdpll_594m", 0,
|
||||
base + 0x40, 16, 3, CLK_DIVIDER_POWER_OF_TWO,
|
||||
NULL);
|
||||
clk_data->clks[CLK_APMIXED_HDMI_REF] = clk;
|
||||
|
||||
r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
|
||||
if (r)
|
||||
pr_err("%s(): could not register clock provider: %d\n",
|
||||
|
|
|
@ -83,7 +83,11 @@ struct mtk_composite {
|
|||
signed char num_parents;
|
||||
};
|
||||
|
||||
#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate) { \
|
||||
/*
|
||||
* In case the rate change propagation to parent clocks is undesirable,
|
||||
* this macro allows to specify the clock flags manually.
|
||||
*/
|
||||
#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, _flags) { \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.mux_reg = _reg, \
|
||||
|
@ -94,9 +98,16 @@ struct mtk_composite {
|
|||
.divider_shift = -1, \
|
||||
.parent_names = _parents, \
|
||||
.num_parents = ARRAY_SIZE(_parents), \
|
||||
.flags = CLK_SET_RATE_PARENT, \
|
||||
.flags = _flags, \
|
||||
}
|
||||
|
||||
/*
|
||||
* Unless necessary, all MUX_GATE clocks propagate rate changes to their
|
||||
* parent clock by default.
|
||||
*/
|
||||
#define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate) \
|
||||
MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, CLK_SET_RATE_PARENT)
|
||||
|
||||
#define MUX(_id, _name, _parents, _reg, _shift, _width) { \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
|
|
|
@ -288,3 +288,5 @@ source "drivers/gpu/drm/etnaviv/Kconfig"
|
|||
source "drivers/gpu/drm/arc/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/hisilicon/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/mediatek/Kconfig"
|
||||
|
|
|
@ -74,6 +74,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/
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
config DRM_MEDIATEK
|
||||
tristate "DRM Support for Mediatek SoCs"
|
||||
depends on DRM
|
||||
depends on ARCH_MEDIATEK || (ARM && COMPILE_TEST)
|
||||
select DRM_GEM_CMA_HELPER
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_MIPI_DSI
|
||||
select DRM_PANEL
|
||||
select IOMMU_DMA
|
||||
select MEMORY
|
||||
select MTK_SMI
|
||||
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.
|
|
@ -0,0 +1,14 @@
|
|||
mediatek-drm-y := mtk_disp_ovl.o \
|
||||
mtk_disp_rdma.o \
|
||||
mtk_drm_crtc.o \
|
||||
mtk_drm_ddp.o \
|
||||
mtk_drm_ddp_comp.o \
|
||||
mtk_drm_drv.o \
|
||||
mtk_drm_fb.o \
|
||||
mtk_drm_gem.o \
|
||||
mtk_drm_plane.o \
|
||||
mtk_dsi.o \
|
||||
mtk_mipi_tx.o \
|
||||
mtk_dpi.o
|
||||
|
||||
obj-$(CONFIG_DRM_MEDIATEK) += mediatek-drm.o
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "mtk_drm_crtc.h"
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
|
||||
#define DISP_REG_OVL_INTEN 0x0004
|
||||
#define OVL_FME_CPL_INT BIT(1)
|
||||
#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 OVL_RDMA_MEM_GMC 0x40402020
|
||||
|
||||
#define OVL_CON_BYTE_SWAP BIT(24)
|
||||
#define OVL_CON_CLRFMT_RGB565 (0 << 12)
|
||||
#define OVL_CON_CLRFMT_RGB888 (1 << 12)
|
||||
#define OVL_CON_CLRFMT_RGBA8888 (2 << 12)
|
||||
#define OVL_CON_CLRFMT_ARGB8888 (3 << 12)
|
||||
#define OVL_CON_AEN BIT(8)
|
||||
#define OVL_CON_ALPHA 0xff
|
||||
|
||||
/**
|
||||
* struct mtk_disp_ovl - DISP_OVL driver structure
|
||||
* @ddp_comp - structure containing type enum and hardware resources
|
||||
* @crtc - associated crtc to report vblank events to
|
||||
*/
|
||||
struct mtk_disp_ovl {
|
||||
struct mtk_ddp_comp ddp_comp;
|
||||
struct drm_crtc *crtc;
|
||||
};
|
||||
|
||||
static irqreturn_t mtk_disp_ovl_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct mtk_disp_ovl *priv = dev_id;
|
||||
struct mtk_ddp_comp *ovl = &priv->ddp_comp;
|
||||
|
||||
/* Clear frame completion interrupt */
|
||||
writel(0x0, ovl->regs + DISP_REG_OVL_INTSTA);
|
||||
|
||||
if (!priv->crtc)
|
||||
return IRQ_NONE;
|
||||
|
||||
mtk_crtc_ddp_irq(priv->crtc, ovl);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void mtk_ovl_enable_vblank(struct mtk_ddp_comp *comp,
|
||||
struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_disp_ovl *priv = container_of(comp, struct mtk_disp_ovl,
|
||||
ddp_comp);
|
||||
|
||||
priv->crtc = crtc;
|
||||
writel_relaxed(OVL_FME_CPL_INT, comp->regs + DISP_REG_OVL_INTEN);
|
||||
}
|
||||
|
||||
static void mtk_ovl_disable_vblank(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_disp_ovl *priv = container_of(comp, struct mtk_disp_ovl,
|
||||
ddp_comp);
|
||||
|
||||
priv->crtc = NULL;
|
||||
writel_relaxed(0x0, comp->regs + DISP_REG_OVL_INTEN);
|
||||
}
|
||||
|
||||
static void mtk_ovl_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
writel_relaxed(0x1, comp->regs + DISP_REG_OVL_EN);
|
||||
}
|
||||
|
||||
static void mtk_ovl_stop(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
writel_relaxed(0x0, comp->regs + DISP_REG_OVL_EN);
|
||||
}
|
||||
|
||||
static void mtk_ovl_config(struct mtk_ddp_comp *comp, unsigned int w,
|
||||
unsigned int h, unsigned int vrefresh)
|
||||
{
|
||||
if (w != 0 && h != 0)
|
||||
writel_relaxed(h << 16 | w, comp->regs + DISP_REG_OVL_ROI_SIZE);
|
||||
writel_relaxed(0x0, comp->regs + DISP_REG_OVL_ROI_BGCLR);
|
||||
|
||||
writel(0x1, comp->regs + DISP_REG_OVL_RST);
|
||||
writel(0x0, comp->regs + DISP_REG_OVL_RST);
|
||||
}
|
||||
|
||||
static void mtk_ovl_layer_on(struct mtk_ddp_comp *comp, unsigned int idx)
|
||||
{
|
||||
unsigned int reg;
|
||||
|
||||
writel(0x1, comp->regs + DISP_REG_OVL_RDMA_CTRL(idx));
|
||||
writel(OVL_RDMA_MEM_GMC, comp->regs + DISP_REG_OVL_RDMA_GMC(idx));
|
||||
|
||||
reg = readl(comp->regs + DISP_REG_OVL_SRC_CON);
|
||||
reg = reg | BIT(idx);
|
||||
writel(reg, comp->regs + DISP_REG_OVL_SRC_CON);
|
||||
}
|
||||
|
||||
static void mtk_ovl_layer_off(struct mtk_ddp_comp *comp, unsigned int idx)
|
||||
{
|
||||
unsigned int reg;
|
||||
|
||||
reg = readl(comp->regs + DISP_REG_OVL_SRC_CON);
|
||||
reg = reg & ~BIT(idx);
|
||||
writel(reg, comp->regs + DISP_REG_OVL_SRC_CON);
|
||||
|
||||
writel(0x0, comp->regs + DISP_REG_OVL_RDMA_CTRL(idx));
|
||||
}
|
||||
|
||||
static unsigned int ovl_fmt_convert(unsigned int fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
default:
|
||||
case DRM_FORMAT_RGB565:
|
||||
return OVL_CON_CLRFMT_RGB565;
|
||||
case DRM_FORMAT_BGR565:
|
||||
return OVL_CON_CLRFMT_RGB565 | OVL_CON_BYTE_SWAP;
|
||||
case DRM_FORMAT_RGB888:
|
||||
return OVL_CON_CLRFMT_RGB888;
|
||||
case DRM_FORMAT_BGR888:
|
||||
return OVL_CON_CLRFMT_RGB888 | OVL_CON_BYTE_SWAP;
|
||||
case DRM_FORMAT_RGBX8888:
|
||||
case DRM_FORMAT_RGBA8888:
|
||||
return OVL_CON_CLRFMT_ARGB8888;
|
||||
case DRM_FORMAT_BGRX8888:
|
||||
case DRM_FORMAT_BGRA8888:
|
||||
return OVL_CON_CLRFMT_ARGB8888 | OVL_CON_BYTE_SWAP;
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_ARGB8888:
|
||||
return OVL_CON_CLRFMT_RGBA8888;
|
||||
case DRM_FORMAT_XBGR8888:
|
||||
case DRM_FORMAT_ABGR8888:
|
||||
return OVL_CON_CLRFMT_RGBA8888 | OVL_CON_BYTE_SWAP;
|
||||
}
|
||||
}
|
||||
|
||||
static void mtk_ovl_layer_config(struct mtk_ddp_comp *comp, unsigned int idx,
|
||||
struct mtk_plane_state *state)
|
||||
{
|
||||
struct mtk_plane_pending_state *pending = &state->pending;
|
||||
unsigned int addr = pending->addr;
|
||||
unsigned int pitch = pending->pitch & 0xffff;
|
||||
unsigned int fmt = pending->format;
|
||||
unsigned int offset = (pending->y << 16) | pending->x;
|
||||
unsigned int src_size = (pending->height << 16) | pending->width;
|
||||
unsigned int con;
|
||||
|
||||
if (!pending->enable)
|
||||
mtk_ovl_layer_off(comp, idx);
|
||||
|
||||
con = ovl_fmt_convert(fmt);
|
||||
if (idx != 0)
|
||||
con |= OVL_CON_AEN | OVL_CON_ALPHA;
|
||||
|
||||
writel_relaxed(con, comp->regs + DISP_REG_OVL_CON(idx));
|
||||
writel_relaxed(pitch, comp->regs + DISP_REG_OVL_PITCH(idx));
|
||||
writel_relaxed(src_size, comp->regs + DISP_REG_OVL_SRC_SIZE(idx));
|
||||
writel_relaxed(offset, comp->regs + DISP_REG_OVL_OFFSET(idx));
|
||||
writel_relaxed(addr, comp->regs + DISP_REG_OVL_ADDR(idx));
|
||||
|
||||
if (pending->enable)
|
||||
mtk_ovl_layer_on(comp, idx);
|
||||
}
|
||||
|
||||
static const struct mtk_ddp_comp_funcs mtk_disp_ovl_funcs = {
|
||||
.config = mtk_ovl_config,
|
||||
.start = mtk_ovl_start,
|
||||
.stop = mtk_ovl_stop,
|
||||
.enable_vblank = mtk_ovl_enable_vblank,
|
||||
.disable_vblank = mtk_ovl_disable_vblank,
|
||||
.layer_on = mtk_ovl_layer_on,
|
||||
.layer_off = mtk_ovl_layer_off,
|
||||
.layer_config = mtk_ovl_layer_config,
|
||||
};
|
||||
|
||||
static int mtk_disp_ovl_bind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct mtk_disp_ovl *priv = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
int ret;
|
||||
|
||||
ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register component %s: %d\n",
|
||||
dev->of_node->full_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_disp_ovl_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct mtk_disp_ovl *priv = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
|
||||
mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp);
|
||||
}
|
||||
|
||||
static const struct component_ops mtk_disp_ovl_component_ops = {
|
||||
.bind = mtk_disp_ovl_bind,
|
||||
.unbind = mtk_disp_ovl_unbind,
|
||||
};
|
||||
|
||||
static int mtk_disp_ovl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_disp_ovl *priv;
|
||||
int comp_id;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
ret = devm_request_irq(dev, irq, mtk_disp_ovl_irq_handler,
|
||||
IRQF_TRIGGER_NONE, dev_name(dev), priv);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to request irq %d: %d\n", irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_OVL);
|
||||
if (comp_id < 0) {
|
||||
dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
|
||||
return comp_id;
|
||||
}
|
||||
|
||||
ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id,
|
||||
&mtk_disp_ovl_funcs);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize component: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
ret = component_add(dev, &mtk_disp_ovl_component_ops);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to add component: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_disp_ovl_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &mtk_disp_ovl_component_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mtk_disp_ovl_driver_dt_match[] = {
|
||||
{ .compatible = "mediatek,mt8173-disp-ovl", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mtk_disp_ovl_driver_dt_match);
|
||||
|
||||
struct platform_driver mtk_disp_ovl_driver = {
|
||||
.probe = mtk_disp_ovl_probe,
|
||||
.remove = mtk_disp_ovl_remove,
|
||||
.driver = {
|
||||
.name = "mediatek-disp-ovl",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mtk_disp_ovl_driver_dt_match,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "mtk_drm_crtc.h"
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
|
||||
#define DISP_REG_RDMA_INT_ENABLE 0x0000
|
||||
#define DISP_REG_RDMA_INT_STATUS 0x0004
|
||||
#define RDMA_TARGET_LINE_INT BIT(5)
|
||||
#define RDMA_FIFO_UNDERFLOW_INT BIT(4)
|
||||
#define RDMA_EOF_ABNORMAL_INT BIT(3)
|
||||
#define RDMA_FRAME_END_INT BIT(2)
|
||||
#define RDMA_FRAME_START_INT BIT(1)
|
||||
#define RDMA_REG_UPDATE_INT BIT(0)
|
||||
#define DISP_REG_RDMA_GLOBAL_CON 0x0010
|
||||
#define RDMA_ENGINE_EN BIT(0)
|
||||
#define DISP_REG_RDMA_SIZE_CON_0 0x0014
|
||||
#define DISP_REG_RDMA_SIZE_CON_1 0x0018
|
||||
#define DISP_REG_RDMA_TARGET_LINE 0x001c
|
||||
#define DISP_REG_RDMA_FIFO_CON 0x0040
|
||||
#define RDMA_FIFO_UNDERFLOW_EN BIT(31)
|
||||
#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16)
|
||||
#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16)
|
||||
|
||||
/**
|
||||
* struct mtk_disp_rdma - DISP_RDMA driver structure
|
||||
* @ddp_comp - structure containing type enum and hardware resources
|
||||
* @crtc - associated crtc to report irq events to
|
||||
*/
|
||||
struct mtk_disp_rdma {
|
||||
struct mtk_ddp_comp ddp_comp;
|
||||
struct drm_crtc *crtc;
|
||||
};
|
||||
|
||||
static irqreturn_t mtk_disp_rdma_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct mtk_disp_rdma *priv = dev_id;
|
||||
struct mtk_ddp_comp *rdma = &priv->ddp_comp;
|
||||
|
||||
/* Clear frame completion interrupt */
|
||||
writel(0x0, rdma->regs + DISP_REG_RDMA_INT_STATUS);
|
||||
|
||||
if (!priv->crtc)
|
||||
return IRQ_NONE;
|
||||
|
||||
mtk_crtc_ddp_irq(priv->crtc, rdma);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void rdma_update_bits(struct mtk_ddp_comp *comp, unsigned int reg,
|
||||
unsigned int mask, unsigned int val)
|
||||
{
|
||||
unsigned int tmp = readl(comp->regs + reg);
|
||||
|
||||
tmp = (tmp & ~mask) | (val & mask);
|
||||
writel(tmp, comp->regs + reg);
|
||||
}
|
||||
|
||||
static void mtk_rdma_enable_vblank(struct mtk_ddp_comp *comp,
|
||||
struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_disp_rdma *priv = container_of(comp, struct mtk_disp_rdma,
|
||||
ddp_comp);
|
||||
|
||||
priv->crtc = crtc;
|
||||
rdma_update_bits(comp, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT,
|
||||
RDMA_FRAME_END_INT);
|
||||
}
|
||||
|
||||
static void mtk_rdma_disable_vblank(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_disp_rdma *priv = container_of(comp, struct mtk_disp_rdma,
|
||||
ddp_comp);
|
||||
|
||||
priv->crtc = NULL;
|
||||
rdma_update_bits(comp, DISP_REG_RDMA_INT_ENABLE, RDMA_FRAME_END_INT, 0);
|
||||
}
|
||||
|
||||
static void mtk_rdma_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
rdma_update_bits(comp, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN,
|
||||
RDMA_ENGINE_EN);
|
||||
}
|
||||
|
||||
static void mtk_rdma_stop(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
rdma_update_bits(comp, DISP_REG_RDMA_GLOBAL_CON, RDMA_ENGINE_EN, 0);
|
||||
}
|
||||
|
||||
static void mtk_rdma_config(struct mtk_ddp_comp *comp, unsigned int width,
|
||||
unsigned int height, unsigned int vrefresh)
|
||||
{
|
||||
unsigned int threshold;
|
||||
unsigned int reg;
|
||||
|
||||
rdma_update_bits(comp, DISP_REG_RDMA_SIZE_CON_0, 0xfff, width);
|
||||
rdma_update_bits(comp, DISP_REG_RDMA_SIZE_CON_1, 0xfffff, height);
|
||||
|
||||
/*
|
||||
* Enable FIFO underflow since DSI and DPI can't be blocked.
|
||||
* Keep the FIFO pseudo size reset default of 8 KiB. Set the
|
||||
* output threshold to 6 microseconds with 7/6 overhead to
|
||||
* account for blanking, and with a pixel depth of 4 bytes:
|
||||
*/
|
||||
threshold = width * height * vrefresh * 4 * 7 / 1000000;
|
||||
reg = RDMA_FIFO_UNDERFLOW_EN |
|
||||
RDMA_FIFO_PSEUDO_SIZE(SZ_8K) |
|
||||
RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold);
|
||||
writel(reg, comp->regs + DISP_REG_RDMA_FIFO_CON);
|
||||
}
|
||||
|
||||
static const struct mtk_ddp_comp_funcs mtk_disp_rdma_funcs = {
|
||||
.config = mtk_rdma_config,
|
||||
.start = mtk_rdma_start,
|
||||
.stop = mtk_rdma_stop,
|
||||
.enable_vblank = mtk_rdma_enable_vblank,
|
||||
.disable_vblank = mtk_rdma_disable_vblank,
|
||||
};
|
||||
|
||||
static int mtk_disp_rdma_bind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct mtk_disp_rdma *priv = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
int ret;
|
||||
|
||||
ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register component %s: %d\n",
|
||||
dev->of_node->full_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void mtk_disp_rdma_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct mtk_disp_rdma *priv = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
|
||||
mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp);
|
||||
}
|
||||
|
||||
static const struct component_ops mtk_disp_rdma_component_ops = {
|
||||
.bind = mtk_disp_rdma_bind,
|
||||
.unbind = mtk_disp_rdma_unbind,
|
||||
};
|
||||
|
||||
static int mtk_disp_rdma_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_disp_rdma *priv;
|
||||
int comp_id;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_RDMA);
|
||||
if (comp_id < 0) {
|
||||
dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
|
||||
return comp_id;
|
||||
}
|
||||
|
||||
ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id,
|
||||
&mtk_disp_rdma_funcs);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize component: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable and clear pending interrupts */
|
||||
writel(0x0, priv->ddp_comp.regs + DISP_REG_RDMA_INT_ENABLE);
|
||||
writel(0x0, priv->ddp_comp.regs + DISP_REG_RDMA_INT_STATUS);
|
||||
|
||||
ret = devm_request_irq(dev, irq, mtk_disp_rdma_irq_handler,
|
||||
IRQF_TRIGGER_NONE, dev_name(dev), priv);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to request irq %d: %d\n", irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
ret = component_add(dev, &mtk_disp_rdma_component_ops);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to add component: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_disp_rdma_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &mtk_disp_rdma_component_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mtk_disp_rdma_driver_dt_match[] = {
|
||||
{ .compatible = "mediatek,mt8173-disp-rdma", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mtk_disp_rdma_driver_dt_match);
|
||||
|
||||
struct platform_driver mtk_disp_rdma_driver = {
|
||||
.probe = mtk_disp_rdma_probe,
|
||||
.remove = mtk_disp_rdma_remove,
|
||||
.driver = {
|
||||
.name = "mediatek-disp-rdma",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mtk_disp_rdma_driver_dt_match,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,769 @@
|
|||
/*
|
||||
* Copyright (c) 2014 MediaTek Inc.
|
||||
* Author: Jie Qiu <jie.qiu@mediatek.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include "mtk_dpi_regs.h"
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
|
||||
enum mtk_dpi_out_bit_num {
|
||||
MTK_DPI_OUT_BIT_NUM_8BITS,
|
||||
MTK_DPI_OUT_BIT_NUM_10BITS,
|
||||
MTK_DPI_OUT_BIT_NUM_12BITS,
|
||||
MTK_DPI_OUT_BIT_NUM_16BITS
|
||||
};
|
||||
|
||||
enum mtk_dpi_out_yc_map {
|
||||
MTK_DPI_OUT_YC_MAP_RGB,
|
||||
MTK_DPI_OUT_YC_MAP_CYCY,
|
||||
MTK_DPI_OUT_YC_MAP_YCYC,
|
||||
MTK_DPI_OUT_YC_MAP_CY,
|
||||
MTK_DPI_OUT_YC_MAP_YC
|
||||
};
|
||||
|
||||
enum mtk_dpi_out_channel_swap {
|
||||
MTK_DPI_OUT_CHANNEL_SWAP_RGB,
|
||||
MTK_DPI_OUT_CHANNEL_SWAP_GBR,
|
||||
MTK_DPI_OUT_CHANNEL_SWAP_BRG,
|
||||
MTK_DPI_OUT_CHANNEL_SWAP_RBG,
|
||||
MTK_DPI_OUT_CHANNEL_SWAP_GRB,
|
||||
MTK_DPI_OUT_CHANNEL_SWAP_BGR
|
||||
};
|
||||
|
||||
enum mtk_dpi_out_color_format {
|
||||
MTK_DPI_COLOR_FORMAT_RGB,
|
||||
MTK_DPI_COLOR_FORMAT_RGB_FULL,
|
||||
MTK_DPI_COLOR_FORMAT_YCBCR_444,
|
||||
MTK_DPI_COLOR_FORMAT_YCBCR_422,
|
||||
MTK_DPI_COLOR_FORMAT_XV_YCC,
|
||||
MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL,
|
||||
MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL
|
||||
};
|
||||
|
||||
struct mtk_dpi {
|
||||
struct mtk_ddp_comp ddp_comp;
|
||||
struct drm_encoder encoder;
|
||||
void __iomem *regs;
|
||||
struct device *dev;
|
||||
struct clk *engine_clk;
|
||||
struct clk *pixel_clk;
|
||||
struct clk *tvd_clk;
|
||||
int irq;
|
||||
struct drm_display_mode mode;
|
||||
enum mtk_dpi_out_color_format color_format;
|
||||
enum mtk_dpi_out_yc_map yc_map;
|
||||
enum mtk_dpi_out_bit_num bit_num;
|
||||
enum mtk_dpi_out_channel_swap channel_swap;
|
||||
bool power_sta;
|
||||
u8 power_ctl;
|
||||
};
|
||||
|
||||
static inline struct mtk_dpi *mtk_dpi_from_encoder(struct drm_encoder *e)
|
||||
{
|
||||
return container_of(e, struct mtk_dpi, encoder);
|
||||
}
|
||||
|
||||
enum mtk_dpi_polarity {
|
||||
MTK_DPI_POLARITY_RISING,
|
||||
MTK_DPI_POLARITY_FALLING,
|
||||
};
|
||||
|
||||
enum mtk_dpi_power_ctl {
|
||||
DPI_POWER_START = BIT(0),
|
||||
DPI_POWER_ENABLE = BIT(1),
|
||||
};
|
||||
|
||||
struct mtk_dpi_polarities {
|
||||
enum mtk_dpi_polarity de_pol;
|
||||
enum mtk_dpi_polarity ck_pol;
|
||||
enum mtk_dpi_polarity hsync_pol;
|
||||
enum mtk_dpi_polarity vsync_pol;
|
||||
};
|
||||
|
||||
struct mtk_dpi_sync_param {
|
||||
u32 sync_width;
|
||||
u32 front_porch;
|
||||
u32 back_porch;
|
||||
bool shift_half_line;
|
||||
};
|
||||
|
||||
struct mtk_dpi_yc_limit {
|
||||
u16 y_top;
|
||||
u16 y_bottom;
|
||||
u16 c_top;
|
||||
u16 c_bottom;
|
||||
};
|
||||
|
||||
static void mtk_dpi_mask(struct mtk_dpi *dpi, u32 offset, u32 val, u32 mask)
|
||||
{
|
||||
u32 tmp = readl(dpi->regs + offset) & ~mask;
|
||||
|
||||
tmp |= (val & mask);
|
||||
writel(tmp, dpi->regs + offset);
|
||||
}
|
||||
|
||||
static void mtk_dpi_sw_reset(struct mtk_dpi *dpi, bool reset)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_RET, reset ? RST : 0, RST);
|
||||
}
|
||||
|
||||
static void mtk_dpi_enable(struct mtk_dpi *dpi)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_EN, EN, EN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_disable(struct mtk_dpi *dpi)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_EN, 0, EN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_hsync(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_sync_param *sync)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_TGEN_HWIDTH,
|
||||
sync->sync_width << HPW, HPW_MASK);
|
||||
mtk_dpi_mask(dpi, DPI_TGEN_HPORCH,
|
||||
sync->back_porch << HBP, HBP_MASK);
|
||||
mtk_dpi_mask(dpi, DPI_TGEN_HPORCH, sync->front_porch << HFP,
|
||||
HFP_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_vsync(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_sync_param *sync,
|
||||
u32 width_addr, u32 porch_addr)
|
||||
{
|
||||
mtk_dpi_mask(dpi, width_addr,
|
||||
sync->sync_width << VSYNC_WIDTH_SHIFT,
|
||||
VSYNC_WIDTH_MASK);
|
||||
mtk_dpi_mask(dpi, width_addr,
|
||||
sync->shift_half_line << VSYNC_HALF_LINE_SHIFT,
|
||||
VSYNC_HALF_LINE_MASK);
|
||||
mtk_dpi_mask(dpi, porch_addr,
|
||||
sync->back_porch << VSYNC_BACK_PORCH_SHIFT,
|
||||
VSYNC_BACK_PORCH_MASK);
|
||||
mtk_dpi_mask(dpi, porch_addr,
|
||||
sync->front_porch << VSYNC_FRONT_PORCH_SHIFT,
|
||||
VSYNC_FRONT_PORCH_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_vsync_lodd(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_sync_param *sync)
|
||||
{
|
||||
mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH, DPI_TGEN_VPORCH);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_vsync_leven(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_sync_param *sync)
|
||||
{
|
||||
mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_LEVEN,
|
||||
DPI_TGEN_VPORCH_LEVEN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_vsync_rodd(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_sync_param *sync)
|
||||
{
|
||||
mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_RODD,
|
||||
DPI_TGEN_VPORCH_RODD);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_vsync_reven(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_sync_param *sync)
|
||||
{
|
||||
mtk_dpi_config_vsync(dpi, sync, DPI_TGEN_VWIDTH_REVEN,
|
||||
DPI_TGEN_VPORCH_REVEN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_pol(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_polarities *dpi_pol)
|
||||
{
|
||||
unsigned int pol;
|
||||
|
||||
pol = (dpi_pol->ck_pol == MTK_DPI_POLARITY_RISING ? 0 : CK_POL) |
|
||||
(dpi_pol->de_pol == MTK_DPI_POLARITY_RISING ? 0 : DE_POL) |
|
||||
(dpi_pol->hsync_pol == MTK_DPI_POLARITY_RISING ? 0 : HSYNC_POL) |
|
||||
(dpi_pol->vsync_pol == MTK_DPI_POLARITY_RISING ? 0 : VSYNC_POL);
|
||||
mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, pol,
|
||||
CK_POL | DE_POL | HSYNC_POL | VSYNC_POL);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_3d(struct mtk_dpi *dpi, bool en_3d)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_CON, en_3d ? TDFP_EN : 0, TDFP_EN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_interface(struct mtk_dpi *dpi, bool inter)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_CON, inter ? INTL_EN : 0, INTL_EN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_fb_size(struct mtk_dpi *dpi, u32 width, u32 height)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_SIZE, width << HSIZE, HSIZE_MASK);
|
||||
mtk_dpi_mask(dpi, DPI_SIZE, height << VSIZE, VSIZE_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_channel_limit(struct mtk_dpi *dpi,
|
||||
struct mtk_dpi_yc_limit *limit)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_bottom << Y_LIMINT_BOT,
|
||||
Y_LIMINT_BOT_MASK);
|
||||
mtk_dpi_mask(dpi, DPI_Y_LIMIT, limit->y_top << Y_LIMINT_TOP,
|
||||
Y_LIMINT_TOP_MASK);
|
||||
mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_bottom << C_LIMIT_BOT,
|
||||
C_LIMIT_BOT_MASK);
|
||||
mtk_dpi_mask(dpi, DPI_C_LIMIT, limit->c_top << C_LIMIT_TOP,
|
||||
C_LIMIT_TOP_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_bit_num(struct mtk_dpi *dpi,
|
||||
enum mtk_dpi_out_bit_num num)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
switch (num) {
|
||||
case MTK_DPI_OUT_BIT_NUM_8BITS:
|
||||
val = OUT_BIT_8;
|
||||
break;
|
||||
case MTK_DPI_OUT_BIT_NUM_10BITS:
|
||||
val = OUT_BIT_10;
|
||||
break;
|
||||
case MTK_DPI_OUT_BIT_NUM_12BITS:
|
||||
val = OUT_BIT_12;
|
||||
break;
|
||||
case MTK_DPI_OUT_BIT_NUM_16BITS:
|
||||
val = OUT_BIT_16;
|
||||
break;
|
||||
default:
|
||||
val = OUT_BIT_8;
|
||||
break;
|
||||
}
|
||||
mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << OUT_BIT,
|
||||
OUT_BIT_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_yc_map(struct mtk_dpi *dpi,
|
||||
enum mtk_dpi_out_yc_map map)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
switch (map) {
|
||||
case MTK_DPI_OUT_YC_MAP_RGB:
|
||||
val = YC_MAP_RGB;
|
||||
break;
|
||||
case MTK_DPI_OUT_YC_MAP_CYCY:
|
||||
val = YC_MAP_CYCY;
|
||||
break;
|
||||
case MTK_DPI_OUT_YC_MAP_YCYC:
|
||||
val = YC_MAP_YCYC;
|
||||
break;
|
||||
case MTK_DPI_OUT_YC_MAP_CY:
|
||||
val = YC_MAP_CY;
|
||||
break;
|
||||
case MTK_DPI_OUT_YC_MAP_YC:
|
||||
val = YC_MAP_YC;
|
||||
break;
|
||||
default:
|
||||
val = YC_MAP_RGB;
|
||||
break;
|
||||
}
|
||||
|
||||
mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << YC_MAP, YC_MAP_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_channel_swap(struct mtk_dpi *dpi,
|
||||
enum mtk_dpi_out_channel_swap swap)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
switch (swap) {
|
||||
case MTK_DPI_OUT_CHANNEL_SWAP_RGB:
|
||||
val = SWAP_RGB;
|
||||
break;
|
||||
case MTK_DPI_OUT_CHANNEL_SWAP_GBR:
|
||||
val = SWAP_GBR;
|
||||
break;
|
||||
case MTK_DPI_OUT_CHANNEL_SWAP_BRG:
|
||||
val = SWAP_BRG;
|
||||
break;
|
||||
case MTK_DPI_OUT_CHANNEL_SWAP_RBG:
|
||||
val = SWAP_RBG;
|
||||
break;
|
||||
case MTK_DPI_OUT_CHANNEL_SWAP_GRB:
|
||||
val = SWAP_GRB;
|
||||
break;
|
||||
case MTK_DPI_OUT_CHANNEL_SWAP_BGR:
|
||||
val = SWAP_BGR;
|
||||
break;
|
||||
default:
|
||||
val = SWAP_RGB;
|
||||
break;
|
||||
}
|
||||
|
||||
mtk_dpi_mask(dpi, DPI_OUTPUT_SETTING, val << CH_SWAP, CH_SWAP_MASK);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_yuv422_enable(struct mtk_dpi *dpi, bool enable)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_CON, enable ? YUV422_EN : 0, YUV422_EN);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_csc_enable(struct mtk_dpi *dpi, bool enable)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_CON, enable ? CSC_ENABLE : 0, CSC_ENABLE);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_swap_input(struct mtk_dpi *dpi, bool enable)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_CON, enable ? IN_RB_SWAP : 0, IN_RB_SWAP);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_2n_h_fre(struct mtk_dpi *dpi)
|
||||
{
|
||||
mtk_dpi_mask(dpi, DPI_H_FRE_CON, H_FRE_2N, H_FRE_2N);
|
||||
}
|
||||
|
||||
static void mtk_dpi_config_color_format(struct mtk_dpi *dpi,
|
||||
enum mtk_dpi_out_color_format format)
|
||||
{
|
||||
if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_444) ||
|
||||
(format == MTK_DPI_COLOR_FORMAT_YCBCR_444_FULL)) {
|
||||
mtk_dpi_config_yuv422_enable(dpi, false);
|
||||
mtk_dpi_config_csc_enable(dpi, true);
|
||||
mtk_dpi_config_swap_input(dpi, false);
|
||||
mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_BGR);
|
||||
} else if ((format == MTK_DPI_COLOR_FORMAT_YCBCR_422) ||
|
||||
(format == MTK_DPI_COLOR_FORMAT_YCBCR_422_FULL)) {
|
||||
mtk_dpi_config_yuv422_enable(dpi, true);
|
||||
mtk_dpi_config_csc_enable(dpi, true);
|
||||
mtk_dpi_config_swap_input(dpi, true);
|
||||
mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB);
|
||||
} else {
|
||||
mtk_dpi_config_yuv422_enable(dpi, false);
|
||||
mtk_dpi_config_csc_enable(dpi, false);
|
||||
mtk_dpi_config_swap_input(dpi, false);
|
||||
mtk_dpi_config_channel_swap(dpi, MTK_DPI_OUT_CHANNEL_SWAP_RGB);
|
||||
}
|
||||
}
|
||||
|
||||
static void mtk_dpi_power_off(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
|
||||
{
|
||||
dpi->power_ctl &= ~pctl;
|
||||
|
||||
if ((dpi->power_ctl & DPI_POWER_START) ||
|
||||
(dpi->power_ctl & DPI_POWER_ENABLE))
|
||||
return;
|
||||
|
||||
if (!dpi->power_sta)
|
||||
return;
|
||||
|
||||
mtk_dpi_disable(dpi);
|
||||
clk_disable_unprepare(dpi->pixel_clk);
|
||||
clk_disable_unprepare(dpi->engine_clk);
|
||||
dpi->power_sta = false;
|
||||
}
|
||||
|
||||
static int mtk_dpi_power_on(struct mtk_dpi *dpi, enum mtk_dpi_power_ctl pctl)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dpi->power_ctl |= pctl;
|
||||
|
||||
if (!(dpi->power_ctl & DPI_POWER_START) &&
|
||||
!(dpi->power_ctl & DPI_POWER_ENABLE))
|
||||
return 0;
|
||||
|
||||
if (dpi->power_sta)
|
||||
return 0;
|
||||
|
||||
ret = clk_prepare_enable(dpi->engine_clk);
|
||||
if (ret) {
|
||||
dev_err(dpi->dev, "Failed to enable engine clock: %d\n", ret);
|
||||
goto err_eng;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(dpi->pixel_clk);
|
||||
if (ret) {
|
||||
dev_err(dpi->dev, "Failed to enable pixel clock: %d\n", ret);
|
||||
goto err_pixel;
|
||||
}
|
||||
|
||||
mtk_dpi_enable(dpi);
|
||||
dpi->power_sta = true;
|
||||
return 0;
|
||||
|
||||
err_pixel:
|
||||
clk_disable_unprepare(dpi->engine_clk);
|
||||
err_eng:
|
||||
dpi->power_ctl &= ~pctl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_dpi_set_display_mode(struct mtk_dpi *dpi,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct mtk_dpi_yc_limit limit;
|
||||
struct mtk_dpi_polarities dpi_pol;
|
||||
struct mtk_dpi_sync_param hsync;
|
||||
struct mtk_dpi_sync_param vsync_lodd = { 0 };
|
||||
struct mtk_dpi_sync_param vsync_leven = { 0 };
|
||||
struct mtk_dpi_sync_param vsync_rodd = { 0 };
|
||||
struct mtk_dpi_sync_param vsync_reven = { 0 };
|
||||
unsigned long pix_rate;
|
||||
unsigned long pll_rate;
|
||||
unsigned int factor;
|
||||
|
||||
if (!dpi) {
|
||||
dev_err(dpi->dev, "invalid argument\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pix_rate = 1000UL * mode->clock;
|
||||
if (mode->clock <= 74000)
|
||||
factor = 8 * 3;
|
||||
else
|
||||
factor = 4 * 3;
|
||||
pll_rate = pix_rate * factor;
|
||||
|
||||
dev_dbg(dpi->dev, "Want PLL %lu Hz, pixel clock %lu Hz\n",
|
||||
pll_rate, pix_rate);
|
||||
|
||||
clk_set_rate(dpi->tvd_clk, pll_rate);
|
||||
pll_rate = clk_get_rate(dpi->tvd_clk);
|
||||
|
||||
pix_rate = pll_rate / factor;
|
||||
clk_set_rate(dpi->pixel_clk, pix_rate);
|
||||
pix_rate = clk_get_rate(dpi->pixel_clk);
|
||||
|
||||
dev_dbg(dpi->dev, "Got PLL %lu Hz, pixel clock %lu Hz\n",
|
||||
pll_rate, pix_rate);
|
||||
|
||||
limit.c_bottom = 0x0010;
|
||||
limit.c_top = 0x0FE0;
|
||||
limit.y_bottom = 0x0010;
|
||||
limit.y_top = 0x0FE0;
|
||||
|
||||
dpi_pol.ck_pol = MTK_DPI_POLARITY_FALLING;
|
||||
dpi_pol.de_pol = MTK_DPI_POLARITY_RISING;
|
||||
dpi_pol.hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ?
|
||||
MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING;
|
||||
dpi_pol.vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ?
|
||||
MTK_DPI_POLARITY_FALLING : MTK_DPI_POLARITY_RISING;
|
||||
|
||||
hsync.sync_width = mode->hsync_end - mode->hsync_start;
|
||||
hsync.back_porch = mode->htotal - mode->hsync_end;
|
||||
hsync.front_porch = mode->hsync_start - mode->hdisplay;
|
||||
hsync.shift_half_line = false;
|
||||
|
||||
vsync_lodd.sync_width = mode->vsync_end - mode->vsync_start;
|
||||
vsync_lodd.back_porch = mode->vtotal - mode->vsync_end;
|
||||
vsync_lodd.front_porch = mode->vsync_start - mode->vdisplay;
|
||||
vsync_lodd.shift_half_line = false;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
|
||||
mode->flags & DRM_MODE_FLAG_3D_MASK) {
|
||||
vsync_leven = vsync_lodd;
|
||||
vsync_rodd = vsync_lodd;
|
||||
vsync_reven = vsync_lodd;
|
||||
vsync_leven.shift_half_line = true;
|
||||
vsync_reven.shift_half_line = true;
|
||||
} else if (mode->flags & DRM_MODE_FLAG_INTERLACE &&
|
||||
!(mode->flags & DRM_MODE_FLAG_3D_MASK)) {
|
||||
vsync_leven = vsync_lodd;
|
||||
vsync_leven.shift_half_line = true;
|
||||
} else if (!(mode->flags & DRM_MODE_FLAG_INTERLACE) &&
|
||||
mode->flags & DRM_MODE_FLAG_3D_MASK) {
|
||||
vsync_rodd = vsync_lodd;
|
||||
}
|
||||
mtk_dpi_sw_reset(dpi, true);
|
||||
mtk_dpi_config_pol(dpi, &dpi_pol);
|
||||
|
||||
mtk_dpi_config_hsync(dpi, &hsync);
|
||||
mtk_dpi_config_vsync_lodd(dpi, &vsync_lodd);
|
||||
mtk_dpi_config_vsync_rodd(dpi, &vsync_rodd);
|
||||
mtk_dpi_config_vsync_leven(dpi, &vsync_leven);
|
||||
mtk_dpi_config_vsync_reven(dpi, &vsync_reven);
|
||||
|
||||
mtk_dpi_config_3d(dpi, !!(mode->flags & DRM_MODE_FLAG_3D_MASK));
|
||||
mtk_dpi_config_interface(dpi, !!(mode->flags &
|
||||
DRM_MODE_FLAG_INTERLACE));
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
||||
mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay / 2);
|
||||
else
|
||||
mtk_dpi_config_fb_size(dpi, mode->hdisplay, mode->vdisplay);
|
||||
|
||||
mtk_dpi_config_channel_limit(dpi, &limit);
|
||||
mtk_dpi_config_bit_num(dpi, dpi->bit_num);
|
||||
mtk_dpi_config_channel_swap(dpi, dpi->channel_swap);
|
||||
mtk_dpi_config_yc_map(dpi, dpi->yc_map);
|
||||
mtk_dpi_config_color_format(dpi, dpi->color_format);
|
||||
mtk_dpi_config_2n_h_fre(dpi);
|
||||
mtk_dpi_sw_reset(dpi, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_dpi_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
drm_encoder_cleanup(encoder);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs mtk_dpi_encoder_funcs = {
|
||||
.destroy = mtk_dpi_encoder_destroy,
|
||||
};
|
||||
|
||||
static bool mtk_dpi_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mtk_dpi_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
|
||||
|
||||
drm_mode_copy(&dpi->mode, adjusted_mode);
|
||||
}
|
||||
|
||||
static void mtk_dpi_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
|
||||
|
||||
mtk_dpi_power_off(dpi, DPI_POWER_ENABLE);
|
||||
}
|
||||
|
||||
static void mtk_dpi_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mtk_dpi *dpi = mtk_dpi_from_encoder(encoder);
|
||||
|
||||
mtk_dpi_power_on(dpi, DPI_POWER_ENABLE);
|
||||
mtk_dpi_set_display_mode(dpi, &dpi->mode);
|
||||
}
|
||||
|
||||
static int mtk_dpi_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs mtk_dpi_encoder_helper_funcs = {
|
||||
.mode_fixup = mtk_dpi_encoder_mode_fixup,
|
||||
.mode_set = mtk_dpi_encoder_mode_set,
|
||||
.disable = mtk_dpi_encoder_disable,
|
||||
.enable = mtk_dpi_encoder_enable,
|
||||
.atomic_check = mtk_dpi_atomic_check,
|
||||
};
|
||||
|
||||
static void mtk_dpi_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp);
|
||||
|
||||
mtk_dpi_power_on(dpi, DPI_POWER_START);
|
||||
}
|
||||
|
||||
static void mtk_dpi_stop(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_dpi *dpi = container_of(comp, struct mtk_dpi, ddp_comp);
|
||||
|
||||
mtk_dpi_power_off(dpi, DPI_POWER_START);
|
||||
}
|
||||
|
||||
static const struct mtk_ddp_comp_funcs mtk_dpi_funcs = {
|
||||
.start = mtk_dpi_start,
|
||||
.stop = mtk_dpi_stop,
|
||||
};
|
||||
|
||||
static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
struct mtk_dpi *dpi = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
int ret;
|
||||
|
||||
ret = mtk_ddp_comp_register(drm_dev, &dpi->ddp_comp);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register component %s: %d\n",
|
||||
dev->of_node->full_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = drm_encoder_init(drm_dev, &dpi->encoder, &mtk_dpi_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize decoder: %d\n", ret);
|
||||
goto err_unregister;
|
||||
}
|
||||
drm_encoder_helper_add(&dpi->encoder, &mtk_dpi_encoder_helper_funcs);
|
||||
|
||||
/* Currently DPI0 is fixed to be driven by OVL1 */
|
||||
dpi->encoder.possible_crtcs = BIT(1);
|
||||
|
||||
dpi->encoder.bridge->encoder = &dpi->encoder;
|
||||
ret = drm_bridge_attach(dpi->encoder.dev, dpi->encoder.bridge);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to attach bridge: %d\n", ret);
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
dpi->bit_num = MTK_DPI_OUT_BIT_NUM_8BITS;
|
||||
dpi->channel_swap = MTK_DPI_OUT_CHANNEL_SWAP_RGB;
|
||||
dpi->yc_map = MTK_DPI_OUT_YC_MAP_RGB;
|
||||
dpi->color_format = MTK_DPI_COLOR_FORMAT_RGB;
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup:
|
||||
drm_encoder_cleanup(&dpi->encoder);
|
||||
err_unregister:
|
||||
mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_dpi_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct mtk_dpi *dpi = dev_get_drvdata(dev);
|
||||
struct drm_device *drm_dev = data;
|
||||
|
||||
drm_encoder_cleanup(&dpi->encoder);
|
||||
mtk_ddp_comp_unregister(drm_dev, &dpi->ddp_comp);
|
||||
}
|
||||
|
||||
static const struct component_ops mtk_dpi_component_ops = {
|
||||
.bind = mtk_dpi_bind,
|
||||
.unbind = mtk_dpi_unbind,
|
||||
};
|
||||
|
||||
static int mtk_dpi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_dpi *dpi;
|
||||
struct resource *mem;
|
||||
struct device_node *ep, *bridge_node = NULL;
|
||||
int comp_id;
|
||||
int ret;
|
||||
|
||||
dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL);
|
||||
if (!dpi)
|
||||
return -ENOMEM;
|
||||
|
||||
dpi->dev = dev;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dpi->regs = devm_ioremap_resource(dev, mem);
|
||||
if (IS_ERR(dpi->regs)) {
|
||||
ret = PTR_ERR(dpi->regs);
|
||||
dev_err(dev, "Failed to ioremap mem resource: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dpi->engine_clk = devm_clk_get(dev, "engine");
|
||||
if (IS_ERR(dpi->engine_clk)) {
|
||||
ret = PTR_ERR(dpi->engine_clk);
|
||||
dev_err(dev, "Failed to get engine clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dpi->pixel_clk = devm_clk_get(dev, "pixel");
|
||||
if (IS_ERR(dpi->pixel_clk)) {
|
||||
ret = PTR_ERR(dpi->pixel_clk);
|
||||
dev_err(dev, "Failed to get pixel clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dpi->tvd_clk = devm_clk_get(dev, "pll");
|
||||
if (IS_ERR(dpi->tvd_clk)) {
|
||||
ret = PTR_ERR(dpi->tvd_clk);
|
||||
dev_err(dev, "Failed to get tvdpll clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dpi->irq = platform_get_irq(pdev, 0);
|
||||
if (dpi->irq <= 0) {
|
||||
dev_err(dev, "Failed to get irq: %d\n", dpi->irq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ep = of_graph_get_next_endpoint(dev->of_node, NULL);
|
||||
if (ep) {
|
||||
bridge_node = of_graph_get_remote_port_parent(ep);
|
||||
of_node_put(ep);
|
||||
}
|
||||
if (!bridge_node) {
|
||||
dev_err(dev, "Failed to find bridge node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_info(dev, "Found bridge node: %s\n", bridge_node->full_name);
|
||||
|
||||
dpi->encoder.bridge = of_drm_find_bridge(bridge_node);
|
||||
of_node_put(bridge_node);
|
||||
if (!dpi->encoder.bridge)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DPI);
|
||||
if (comp_id < 0) {
|
||||
dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
|
||||
return comp_id;
|
||||
}
|
||||
|
||||
ret = mtk_ddp_comp_init(dev, dev->of_node, &dpi->ddp_comp, comp_id,
|
||||
&mtk_dpi_funcs);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize component: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dpi);
|
||||
|
||||
ret = component_add(dev, &mtk_dpi_component_ops);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to add component: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_dpi_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &mtk_dpi_component_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mtk_dpi_of_ids[] = {
|
||||
{ .compatible = "mediatek,mt8173-dpi", },
|
||||
{}
|
||||
};
|
||||
|
||||
struct platform_driver mtk_dpi_driver = {
|
||||
.probe = mtk_dpi_probe,
|
||||
.remove = mtk_dpi_remove,
|
||||
.driver = {
|
||||
.name = "mediatek-dpi",
|
||||
.of_match_table = mtk_dpi_of_ids,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright (c) 2014 MediaTek Inc.
|
||||
* Author: Jie Qiu <jie.qiu@mediatek.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#ifndef __MTK_DPI_REGS_H
|
||||
#define __MTK_DPI_REGS_H
|
||||
|
||||
#define DPI_EN 0x00
|
||||
#define EN BIT(0)
|
||||
|
||||
#define DPI_RET 0x04
|
||||
#define RST BIT(0)
|
||||
|
||||
#define DPI_INTEN 0x08
|
||||
#define INT_VSYNC_EN BIT(0)
|
||||
#define INT_VDE_EN BIT(1)
|
||||
#define INT_UNDERFLOW_EN BIT(2)
|
||||
|
||||
#define DPI_INTSTA 0x0C
|
||||
#define INT_VSYNC_STA BIT(0)
|
||||
#define INT_VDE_STA BIT(1)
|
||||
#define INT_UNDERFLOW_STA BIT(2)
|
||||
|
||||
#define DPI_CON 0x10
|
||||
#define BG_ENABLE BIT(0)
|
||||
#define IN_RB_SWAP BIT(1)
|
||||
#define INTL_EN BIT(2)
|
||||
#define TDFP_EN BIT(3)
|
||||
#define CLPF_EN BIT(4)
|
||||
#define YUV422_EN BIT(5)
|
||||
#define CSC_ENABLE BIT(6)
|
||||
#define R601_SEL BIT(7)
|
||||
#define EMBSYNC_EN BIT(8)
|
||||
#define VS_LODD_EN BIT(16)
|
||||
#define VS_LEVEN_EN BIT(17)
|
||||
#define VS_RODD_EN BIT(18)
|
||||
#define VS_REVEN BIT(19)
|
||||
#define FAKE_DE_LODD BIT(20)
|
||||
#define FAKE_DE_LEVEN BIT(21)
|
||||
#define FAKE_DE_RODD BIT(22)
|
||||
#define FAKE_DE_REVEN BIT(23)
|
||||
|
||||
#define DPI_OUTPUT_SETTING 0x14
|
||||
#define CH_SWAP 0
|
||||
#define CH_SWAP_MASK (0x7 << 0)
|
||||
#define SWAP_RGB 0x00
|
||||
#define SWAP_GBR 0x01
|
||||
#define SWAP_BRG 0x02
|
||||
#define SWAP_RBG 0x03
|
||||
#define SWAP_GRB 0x04
|
||||
#define SWAP_BGR 0x05
|
||||
#define BIT_SWAP BIT(3)
|
||||
#define B_MASK BIT(4)
|
||||
#define G_MASK BIT(5)
|
||||
#define R_MASK BIT(6)
|
||||
#define DE_MASK BIT(8)
|
||||
#define HS_MASK BIT(9)
|
||||
#define VS_MASK BIT(10)
|
||||
#define DE_POL BIT(12)
|
||||
#define HSYNC_POL BIT(13)
|
||||
#define VSYNC_POL BIT(14)
|
||||
#define CK_POL BIT(15)
|
||||
#define OEN_OFF BIT(16)
|
||||
#define EDGE_SEL BIT(17)
|
||||
#define OUT_BIT 18
|
||||
#define OUT_BIT_MASK (0x3 << 18)
|
||||
#define OUT_BIT_8 0x00
|
||||
#define OUT_BIT_10 0x01
|
||||
#define OUT_BIT_12 0x02
|
||||
#define OUT_BIT_16 0x03
|
||||
#define YC_MAP 20
|
||||
#define YC_MAP_MASK (0x7 << 20)
|
||||
#define YC_MAP_RGB 0x00
|
||||
#define YC_MAP_CYCY 0x04
|
||||
#define YC_MAP_YCYC 0x05
|
||||
#define YC_MAP_CY 0x06
|
||||
#define YC_MAP_YC 0x07
|
||||
|
||||
#define DPI_SIZE 0x18
|
||||
#define HSIZE 0
|
||||
#define HSIZE_MASK (0x1FFF << 0)
|
||||
#define VSIZE 16
|
||||
#define VSIZE_MASK (0x1FFF << 16)
|
||||
|
||||
#define DPI_DDR_SETTING 0x1C
|
||||
#define DDR_EN BIT(0)
|
||||
#define DDDR_SEL BIT(1)
|
||||
#define DDR_4PHASE BIT(2)
|
||||
#define DDR_WIDTH (0x3 << 4)
|
||||
#define DDR_PAD_MODE (0x1 << 8)
|
||||
|
||||
#define DPI_TGEN_HWIDTH 0x20
|
||||
#define HPW 0
|
||||
#define HPW_MASK (0xFFF << 0)
|
||||
|
||||
#define DPI_TGEN_HPORCH 0x24
|
||||
#define HBP 0
|
||||
#define HBP_MASK (0xFFF << 0)
|
||||
#define HFP 16
|
||||
#define HFP_MASK (0xFFF << 16)
|
||||
|
||||
#define DPI_TGEN_VWIDTH 0x28
|
||||
#define DPI_TGEN_VPORCH 0x2C
|
||||
|
||||
#define VSYNC_WIDTH_SHIFT 0
|
||||
#define VSYNC_WIDTH_MASK (0xFFF << 0)
|
||||
#define VSYNC_HALF_LINE_SHIFT 16
|
||||
#define VSYNC_HALF_LINE_MASK BIT(16)
|
||||
#define VSYNC_BACK_PORCH_SHIFT 0
|
||||
#define VSYNC_BACK_PORCH_MASK (0xFFF << 0)
|
||||
#define VSYNC_FRONT_PORCH_SHIFT 16
|
||||
#define VSYNC_FRONT_PORCH_MASK (0xFFF << 16)
|
||||
|
||||
#define DPI_BG_HCNTL 0x30
|
||||
#define BG_RIGHT (0x1FFF << 0)
|
||||
#define BG_LEFT (0x1FFF << 16)
|
||||
|
||||
#define DPI_BG_VCNTL 0x34
|
||||
#define BG_BOT (0x1FFF << 0)
|
||||
#define BG_TOP (0x1FFF << 16)
|
||||
|
||||
#define DPI_BG_COLOR 0x38
|
||||
#define BG_B (0xF << 0)
|
||||
#define BG_G (0xF << 8)
|
||||
#define BG_R (0xF << 16)
|
||||
|
||||
#define DPI_FIFO_CTL 0x3C
|
||||
#define FIFO_VALID_SET (0x1F << 0)
|
||||
#define FIFO_RST_SEL (0x1 << 8)
|
||||
|
||||
#define DPI_STATUS 0x40
|
||||
#define VCOUNTER (0x1FFF << 0)
|
||||
#define DPI_BUSY BIT(16)
|
||||
#define OUTEN BIT(17)
|
||||
#define FIELD BIT(20)
|
||||
#define TDLR BIT(21)
|
||||
|
||||
#define DPI_TMODE 0x44
|
||||
#define DPI_OEN_ON BIT(0)
|
||||
|
||||
#define DPI_CHECKSUM 0x48
|
||||
#define DPI_CHECKSUM_MASK (0xFFFFFF << 0)
|
||||
#define DPI_CHECKSUM_READY BIT(30)
|
||||
#define DPI_CHECKSUM_EN BIT(31)
|
||||
|
||||
#define DPI_DUMMY 0x50
|
||||
#define DPI_DUMMY_MASK (0xFFFFFFFF << 0)
|
||||
|
||||
#define DPI_TGEN_VWIDTH_LEVEN 0x68
|
||||
#define DPI_TGEN_VPORCH_LEVEN 0x6C
|
||||
#define DPI_TGEN_VWIDTH_RODD 0x70
|
||||
#define DPI_TGEN_VPORCH_RODD 0x74
|
||||
#define DPI_TGEN_VWIDTH_REVEN 0x78
|
||||
#define DPI_TGEN_VPORCH_REVEN 0x7C
|
||||
|
||||
#define DPI_ESAV_VTIMING_LODD 0x80
|
||||
#define ESAV_VOFST_LODD (0xFFF << 0)
|
||||
#define ESAV_VWID_LODD (0xFFF << 16)
|
||||
|
||||
#define DPI_ESAV_VTIMING_LEVEN 0x84
|
||||
#define ESAV_VOFST_LEVEN (0xFFF << 0)
|
||||
#define ESAV_VWID_LEVEN (0xFFF << 16)
|
||||
|
||||
#define DPI_ESAV_VTIMING_RODD 0x88
|
||||
#define ESAV_VOFST_RODD (0xFFF << 0)
|
||||
#define ESAV_VWID_RODD (0xFFF << 16)
|
||||
|
||||
#define DPI_ESAV_VTIMING_REVEN 0x8C
|
||||
#define ESAV_VOFST_REVEN (0xFFF << 0)
|
||||
#define ESAV_VWID_REVEN (0xFFF << 16)
|
||||
|
||||
#define DPI_ESAV_FTIMING 0x90
|
||||
#define ESAV_FOFST_ODD (0xFFF << 0)
|
||||
#define ESAV_FOFST_EVEN (0xFFF << 16)
|
||||
|
||||
#define DPI_CLPF_SETTING 0x94
|
||||
#define CLPF_TYPE (0x3 << 0)
|
||||
#define ROUND_EN BIT(4)
|
||||
|
||||
#define DPI_Y_LIMIT 0x98
|
||||
#define Y_LIMINT_BOT 0
|
||||
#define Y_LIMINT_BOT_MASK (0xFFF << 0)
|
||||
#define Y_LIMINT_TOP 16
|
||||
#define Y_LIMINT_TOP_MASK (0xFFF << 16)
|
||||
|
||||
#define DPI_C_LIMIT 0x9C
|
||||
#define C_LIMIT_BOT 0
|
||||
#define C_LIMIT_BOT_MASK (0xFFF << 0)
|
||||
#define C_LIMIT_TOP 16
|
||||
#define C_LIMIT_TOP_MASK (0xFFF << 16)
|
||||
|
||||
#define DPI_YUV422_SETTING 0xA0
|
||||
#define UV_SWAP BIT(0)
|
||||
#define CR_DELSEL BIT(4)
|
||||
#define CB_DELSEL BIT(5)
|
||||
#define Y_DELSEL BIT(6)
|
||||
#define DE_DELSEL BIT(7)
|
||||
|
||||
#define DPI_EMBSYNC_SETTING 0xA4
|
||||
#define EMBSYNC_R_CR_EN BIT(0)
|
||||
#define EMPSYNC_G_Y_EN BIT(1)
|
||||
#define EMPSYNC_B_CB_EN BIT(2)
|
||||
#define ESAV_F_INV BIT(4)
|
||||
#define ESAV_V_INV BIT(5)
|
||||
#define ESAV_H_INV BIT(6)
|
||||
#define ESAV_CODE_MAN BIT(8)
|
||||
#define VS_OUT_SEL (0x7 << 12)
|
||||
|
||||
#define DPI_ESAV_CODE_SET0 0xA8
|
||||
#define ESAV_CODE0 (0xFFF << 0)
|
||||
#define ESAV_CODE1 (0xFFF << 16)
|
||||
|
||||
#define DPI_ESAV_CODE_SET1 0xAC
|
||||
#define ESAV_CODE2 (0xFFF << 0)
|
||||
#define ESAV_CODE3_MSB BIT(16)
|
||||
|
||||
#define DPI_H_FRE_CON 0xE0
|
||||
#define H_FRE_2N BIT(25)
|
||||
#endif /* __MTK_DPI_REGS_H */
|
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
* 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 <asm/barrier.h>
|
||||
#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/pm_runtime.h>
|
||||
#include <soc/mediatek/smi.h>
|
||||
|
||||
#include "mtk_drm_drv.h"
|
||||
#include "mtk_drm_crtc.h"
|
||||
#include "mtk_drm_ddp.h"
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
#include "mtk_drm_gem.h"
|
||||
#include "mtk_drm_plane.h"
|
||||
|
||||
/**
|
||||
* struct mtk_drm_crtc - MediaTek specific crtc structure.
|
||||
* @base: crtc object.
|
||||
* @enabled: records whether crtc_enable succeeded
|
||||
* @planes: array of 4 mtk_drm_plane structures, one for each overlay plane
|
||||
* @pending_planes: whether any plane has pending changes to be applied
|
||||
* @config_regs: memory mapped mmsys configuration register space
|
||||
* @mutex: handle to one of the ten disp_mutex streams
|
||||
* @ddp_comp_nr: number of components in ddp_comp
|
||||
* @ddp_comp: array of pointers the mtk_ddp_comp structures used by this crtc
|
||||
*/
|
||||
struct mtk_drm_crtc {
|
||||
struct drm_crtc base;
|
||||
bool enabled;
|
||||
|
||||
bool pending_needs_vblank;
|
||||
struct drm_pending_vblank_event *event;
|
||||
|
||||
struct mtk_drm_plane planes[OVL_LAYER_NR];
|
||||
bool pending_planes;
|
||||
|
||||
void __iomem *config_regs;
|
||||
struct mtk_disp_mutex *mutex;
|
||||
unsigned int ddp_comp_nr;
|
||||
struct mtk_ddp_comp **ddp_comp;
|
||||
};
|
||||
|
||||
struct mtk_crtc_state {
|
||||
struct drm_crtc_state base;
|
||||
|
||||
bool pending_config;
|
||||
unsigned int pending_width;
|
||||
unsigned int pending_height;
|
||||
unsigned int pending_vrefresh;
|
||||
};
|
||||
|
||||
static inline struct mtk_drm_crtc *to_mtk_crtc(struct drm_crtc *c)
|
||||
{
|
||||
return container_of(c, struct mtk_drm_crtc, base);
|
||||
}
|
||||
|
||||
static inline struct mtk_crtc_state *to_mtk_crtc_state(struct drm_crtc_state *s)
|
||||
{
|
||||
return container_of(s, struct mtk_crtc_state, base);
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_finish_page_flip(struct mtk_drm_crtc *mtk_crtc)
|
||||
{
|
||||
struct drm_crtc *crtc = &mtk_crtc->base;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&crtc->dev->event_lock, flags);
|
||||
drm_crtc_send_vblank_event(crtc, mtk_crtc->event);
|
||||
drm_crtc_vblank_put(crtc);
|
||||
mtk_crtc->event = NULL;
|
||||
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
|
||||
}
|
||||
|
||||
static void mtk_drm_finish_page_flip(struct mtk_drm_crtc *mtk_crtc)
|
||||
{
|
||||
drm_crtc_handle_vblank(&mtk_crtc->base);
|
||||
if (mtk_crtc->pending_needs_vblank) {
|
||||
mtk_drm_crtc_finish_page_flip(mtk_crtc);
|
||||
mtk_crtc->pending_needs_vblank = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_destroy(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
|
||||
clk_unprepare(mtk_crtc->ddp_comp[i]->clk);
|
||||
|
||||
mtk_disp_mutex_put(mtk_crtc->mutex);
|
||||
|
||||
drm_crtc_cleanup(crtc);
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_reset(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_crtc_state *state;
|
||||
|
||||
if (crtc->state) {
|
||||
if (crtc->state->mode_blob)
|
||||
drm_property_unreference_blob(crtc->state->mode_blob);
|
||||
|
||||
state = to_mtk_crtc_state(crtc->state);
|
||||
memset(state, 0, sizeof(*state));
|
||||
} else {
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return;
|
||||
crtc->state = &state->base;
|
||||
}
|
||||
|
||||
state->base.crtc = crtc;
|
||||
}
|
||||
|
||||
static struct drm_crtc_state *mtk_drm_crtc_duplicate_state(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_crtc_state *state;
|
||||
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return NULL;
|
||||
|
||||
__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
|
||||
|
||||
WARN_ON(state->base.crtc != crtc);
|
||||
state->base.crtc = crtc;
|
||||
|
||||
return &state->base;
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_destroy_state(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *state)
|
||||
{
|
||||
__drm_atomic_helper_crtc_destroy_state(crtc, state);
|
||||
kfree(to_mtk_crtc_state(state));
|
||||
}
|
||||
|
||||
static bool mtk_drm_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* Nothing to do here, but this callback is mandatory. */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
|
||||
|
||||
state->pending_width = crtc->mode.hdisplay;
|
||||
state->pending_height = crtc->mode.vdisplay;
|
||||
state->pending_vrefresh = crtc->mode.vrefresh;
|
||||
wmb(); /* Make sure the above parameters are set before update */
|
||||
state->pending_config = true;
|
||||
}
|
||||
|
||||
int mtk_drm_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe)
|
||||
{
|
||||
struct mtk_drm_private *priv = drm->dev_private;
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]);
|
||||
struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
|
||||
|
||||
mtk_ddp_comp_enable_vblank(ovl, &mtk_crtc->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mtk_drm_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe)
|
||||
{
|
||||
struct mtk_drm_private *priv = drm->dev_private;
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(priv->crtc[pipe]);
|
||||
struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
|
||||
|
||||
mtk_ddp_comp_disable_vblank(ovl);
|
||||
}
|
||||
|
||||
static int mtk_crtc_ddp_clk_enable(struct mtk_drm_crtc *mtk_crtc)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s\n", __func__);
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) {
|
||||
ret = clk_enable(mtk_crtc->ddp_comp[i]->clk);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to enable clock %d: %d\n", i, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
while (--i >= 0)
|
||||
clk_disable(mtk_crtc->ddp_comp[i]->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_crtc_ddp_clk_disable(struct mtk_drm_crtc *mtk_crtc)
|
||||
{
|
||||
int i;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s\n", __func__);
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
|
||||
clk_disable(mtk_crtc->ddp_comp[i]->clk);
|
||||
}
|
||||
|
||||
static int mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *mtk_crtc)
|
||||
{
|
||||
struct drm_crtc *crtc = &mtk_crtc->base;
|
||||
unsigned int width, height, vrefresh;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s\n", __func__);
|
||||
if (WARN_ON(!crtc->state))
|
||||
return -EINVAL;
|
||||
|
||||
width = crtc->state->adjusted_mode.hdisplay;
|
||||
height = crtc->state->adjusted_mode.vdisplay;
|
||||
vrefresh = crtc->state->adjusted_mode.vrefresh;
|
||||
|
||||
ret = pm_runtime_get_sync(crtc->dev->dev);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Failed to enable power domain: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mtk_disp_mutex_prepare(mtk_crtc->mutex);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Failed to enable mutex clock: %d\n", ret);
|
||||
goto err_pm_runtime_put;
|
||||
}
|
||||
|
||||
ret = mtk_crtc_ddp_clk_enable(mtk_crtc);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Failed to enable component clocks: %d\n", ret);
|
||||
goto err_mutex_unprepare;
|
||||
}
|
||||
|
||||
DRM_DEBUG_DRIVER("mediatek_ddp_ddp_path_setup\n");
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) {
|
||||
mtk_ddp_add_comp_to_path(mtk_crtc->config_regs,
|
||||
mtk_crtc->ddp_comp[i]->id,
|
||||
mtk_crtc->ddp_comp[i + 1]->id);
|
||||
mtk_disp_mutex_add_comp(mtk_crtc->mutex,
|
||||
mtk_crtc->ddp_comp[i]->id);
|
||||
}
|
||||
mtk_disp_mutex_add_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id);
|
||||
mtk_disp_mutex_enable(mtk_crtc->mutex);
|
||||
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) {
|
||||
struct mtk_ddp_comp *comp = mtk_crtc->ddp_comp[i];
|
||||
|
||||
mtk_ddp_comp_config(comp, width, height, vrefresh);
|
||||
mtk_ddp_comp_start(comp);
|
||||
}
|
||||
|
||||
/* Initially configure all planes */
|
||||
for (i = 0; i < OVL_LAYER_NR; i++) {
|
||||
struct drm_plane *plane = &mtk_crtc->planes[i].base;
|
||||
struct mtk_plane_state *plane_state;
|
||||
|
||||
plane_state = to_mtk_plane_state(plane->state);
|
||||
mtk_ddp_comp_layer_config(mtk_crtc->ddp_comp[0], i,
|
||||
plane_state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_mutex_unprepare:
|
||||
mtk_disp_mutex_unprepare(mtk_crtc->mutex);
|
||||
err_pm_runtime_put:
|
||||
pm_runtime_put(crtc->dev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_crtc_ddp_hw_fini(struct mtk_drm_crtc *mtk_crtc)
|
||||
{
|
||||
struct drm_device *drm = mtk_crtc->base.dev;
|
||||
int i;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s\n", __func__);
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
|
||||
mtk_ddp_comp_stop(mtk_crtc->ddp_comp[i]);
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++)
|
||||
mtk_disp_mutex_remove_comp(mtk_crtc->mutex,
|
||||
mtk_crtc->ddp_comp[i]->id);
|
||||
mtk_disp_mutex_disable(mtk_crtc->mutex);
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr - 1; i++) {
|
||||
mtk_ddp_remove_comp_from_path(mtk_crtc->config_regs,
|
||||
mtk_crtc->ddp_comp[i]->id,
|
||||
mtk_crtc->ddp_comp[i + 1]->id);
|
||||
mtk_disp_mutex_remove_comp(mtk_crtc->mutex,
|
||||
mtk_crtc->ddp_comp[i]->id);
|
||||
}
|
||||
mtk_disp_mutex_remove_comp(mtk_crtc->mutex, mtk_crtc->ddp_comp[i]->id);
|
||||
mtk_crtc_ddp_clk_disable(mtk_crtc);
|
||||
mtk_disp_mutex_unprepare(mtk_crtc->mutex);
|
||||
|
||||
pm_runtime_put(drm->dev);
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_enable(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
|
||||
struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id);
|
||||
|
||||
ret = mtk_smi_larb_get(ovl->larb_dev);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to get larb: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mtk_crtc_ddp_hw_init(mtk_crtc);
|
||||
if (ret) {
|
||||
mtk_smi_larb_put(ovl->larb_dev);
|
||||
return;
|
||||
}
|
||||
|
||||
drm_crtc_vblank_on(crtc);
|
||||
mtk_crtc->enabled = true;
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_disable(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
|
||||
struct mtk_ddp_comp *ovl = mtk_crtc->ddp_comp[0];
|
||||
int i;
|
||||
|
||||
DRM_DEBUG_DRIVER("%s %d\n", __func__, crtc->base.id);
|
||||
if (!mtk_crtc->enabled)
|
||||
return;
|
||||
|
||||
/* Set all pending plane state to disabled */
|
||||
for (i = 0; i < OVL_LAYER_NR; i++) {
|
||||
struct drm_plane *plane = &mtk_crtc->planes[i].base;
|
||||
struct mtk_plane_state *plane_state;
|
||||
|
||||
plane_state = to_mtk_plane_state(plane->state);
|
||||
plane_state->pending.enable = false;
|
||||
plane_state->pending.config = true;
|
||||
}
|
||||
mtk_crtc->pending_planes = true;
|
||||
|
||||
/* Wait for planes to be disabled */
|
||||
drm_crtc_wait_one_vblank(crtc);
|
||||
|
||||
drm_crtc_vblank_off(crtc);
|
||||
mtk_crtc_ddp_hw_fini(mtk_crtc);
|
||||
mtk_smi_larb_put(ovl->larb_dev);
|
||||
|
||||
mtk_crtc->enabled = false;
|
||||
}
|
||||
|
||||
static void mtk_drm_crtc_atomic_begin(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *old_crtc_state)
|
||||
{
|
||||
struct mtk_crtc_state *state = to_mtk_crtc_state(crtc->state);
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
|
||||
|
||||
if (mtk_crtc->event && state->base.event)
|
||||
DRM_ERROR("new event while there is still a pending event\n");
|
||||
|
||||
if (state->base.event) {
|
||||
state->base.event->pipe = drm_crtc_index(crtc);
|
||||
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
|
||||
mtk_crtc->event = state->base.event;
|
||||
state->base.event = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
unsigned int pending_planes = 0;
|
||||
int i;
|
||||
|
||||
if (mtk_crtc->event)
|
||||
mtk_crtc->pending_needs_vblank = true;
|
||||
for (i = 0; i < OVL_LAYER_NR; i++) {
|
||||
struct drm_plane *plane = &mtk_crtc->planes[i].base;
|
||||
struct mtk_plane_state *plane_state;
|
||||
|
||||
plane_state = to_mtk_plane_state(plane->state);
|
||||
if (plane_state->pending.dirty) {
|
||||
plane_state->pending.config = true;
|
||||
plane_state->pending.dirty = false;
|
||||
pending_planes |= BIT(i);
|
||||
}
|
||||
}
|
||||
if (pending_planes)
|
||||
mtk_crtc->pending_planes = true;
|
||||
}
|
||||
|
||||
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 = mtk_drm_crtc_reset,
|
||||
.atomic_duplicate_state = mtk_drm_crtc_duplicate_state,
|
||||
.atomic_destroy_state = mtk_drm_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,
|
||||
};
|
||||
|
||||
static int mtk_drm_crtc_init(struct drm_device *drm,
|
||||
struct mtk_drm_crtc *mtk_crtc,
|
||||
struct drm_plane *primary,
|
||||
struct drm_plane *cursor, unsigned int pipe)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = drm_crtc_init_with_planes(drm, &mtk_crtc->base, primary, cursor,
|
||||
&mtk_crtc_funcs, NULL);
|
||||
if (ret)
|
||||
goto err_cleanup_crtc;
|
||||
|
||||
drm_crtc_helper_add(&mtk_crtc->base, &mtk_crtc_helper_funcs);
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_crtc:
|
||||
drm_crtc_cleanup(&mtk_crtc->base);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *ovl)
|
||||
{
|
||||
struct mtk_drm_crtc *mtk_crtc = to_mtk_crtc(crtc);
|
||||
struct mtk_crtc_state *state = to_mtk_crtc_state(mtk_crtc->base.state);
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
* TODO: instead of updating the registers here, we should prepare
|
||||
* working registers in atomic_commit and let the hardware command
|
||||
* queue update module registers on vblank.
|
||||
*/
|
||||
if (state->pending_config) {
|
||||
mtk_ddp_comp_config(ovl, state->pending_width,
|
||||
state->pending_height,
|
||||
state->pending_vrefresh);
|
||||
|
||||
state->pending_config = false;
|
||||
}
|
||||
|
||||
if (mtk_crtc->pending_planes) {
|
||||
for (i = 0; i < OVL_LAYER_NR; i++) {
|
||||
struct drm_plane *plane = &mtk_crtc->planes[i].base;
|
||||
struct mtk_plane_state *plane_state;
|
||||
|
||||
plane_state = to_mtk_plane_state(plane->state);
|
||||
|
||||
if (plane_state->pending.config) {
|
||||
mtk_ddp_comp_layer_config(ovl, i, plane_state);
|
||||
plane_state->pending.config = false;
|
||||
}
|
||||
}
|
||||
mtk_crtc->pending_planes = false;
|
||||
}
|
||||
|
||||
mtk_drm_finish_page_flip(mtk_crtc);
|
||||
}
|
||||
|
||||
int mtk_drm_crtc_create(struct drm_device *drm_dev,
|
||||
const enum mtk_ddp_comp_id *path, unsigned int path_len)
|
||||
{
|
||||
struct mtk_drm_private *priv = drm_dev->dev_private;
|
||||
struct device *dev = drm_dev->dev;
|
||||
struct mtk_drm_crtc *mtk_crtc;
|
||||
enum drm_plane_type type;
|
||||
unsigned int zpos;
|
||||
int pipe = priv->num_pipes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < path_len; i++) {
|
||||
enum mtk_ddp_comp_id comp_id = path[i];
|
||||
struct device_node *node;
|
||||
|
||||
node = priv->comp_node[comp_id];
|
||||
if (!node) {
|
||||
dev_info(dev,
|
||||
"Not creating crtc %d because component %d is disabled or missing\n",
|
||||
pipe, comp_id);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
mtk_crtc = devm_kzalloc(dev, sizeof(*mtk_crtc), GFP_KERNEL);
|
||||
if (!mtk_crtc)
|
||||
return -ENOMEM;
|
||||
|
||||
mtk_crtc->config_regs = priv->config_regs;
|
||||
mtk_crtc->ddp_comp_nr = path_len;
|
||||
mtk_crtc->ddp_comp = devm_kmalloc_array(dev, mtk_crtc->ddp_comp_nr,
|
||||
sizeof(*mtk_crtc->ddp_comp),
|
||||
GFP_KERNEL);
|
||||
|
||||
mtk_crtc->mutex = mtk_disp_mutex_get(priv->mutex_dev, pipe);
|
||||
if (IS_ERR(mtk_crtc->mutex)) {
|
||||
ret = PTR_ERR(mtk_crtc->mutex);
|
||||
dev_err(dev, "Failed to get mutex: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < mtk_crtc->ddp_comp_nr; i++) {
|
||||
enum mtk_ddp_comp_id comp_id = path[i];
|
||||
struct mtk_ddp_comp *comp;
|
||||
struct device_node *node;
|
||||
|
||||
node = priv->comp_node[comp_id];
|
||||
comp = priv->ddp_comp[comp_id];
|
||||
if (!comp) {
|
||||
dev_err(dev, "Component %s not initialized\n",
|
||||
node->full_name);
|
||||
ret = -ENODEV;
|
||||
goto unprepare;
|
||||
}
|
||||
|
||||
ret = clk_prepare(comp->clk);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to prepare clock for component %s: %d\n",
|
||||
node->full_name, ret);
|
||||
goto unprepare;
|
||||
}
|
||||
|
||||
mtk_crtc->ddp_comp[i] = comp;
|
||||
}
|
||||
|
||||
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, &mtk_crtc->planes[zpos],
|
||||
BIT(pipe), type, zpos);
|
||||
if (ret)
|
||||
goto unprepare;
|
||||
}
|
||||
|
||||
ret = mtk_drm_crtc_init(drm_dev, mtk_crtc, &mtk_crtc->planes[0].base,
|
||||
&mtk_crtc->planes[1].base, pipe);
|
||||
if (ret < 0)
|
||||
goto unprepare;
|
||||
|
||||
priv->crtc[pipe] = &mtk_crtc->base;
|
||||
priv->num_pipes++;
|
||||
|
||||
return 0;
|
||||
|
||||
unprepare:
|
||||
while (--i >= 0)
|
||||
clk_unprepare(mtk_crtc->ddp_comp[i]->clk);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef MTK_DRM_CRTC_H
|
||||
#define MTK_DRM_CRTC_H
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
#include "mtk_drm_plane.h"
|
||||
|
||||
#define OVL_LAYER_NR 4
|
||||
|
||||
int mtk_drm_crtc_enable_vblank(struct drm_device *drm, unsigned int pipe);
|
||||
void mtk_drm_crtc_disable_vblank(struct drm_device *drm, unsigned int pipe);
|
||||
void mtk_drm_crtc_check_flush(struct drm_crtc *crtc);
|
||||
void mtk_drm_crtc_commit(struct drm_crtc *crtc);
|
||||
void mtk_crtc_ddp_irq(struct drm_crtc *crtc, struct mtk_ddp_comp *ovl);
|
||||
int mtk_drm_crtc_create(struct drm_device *drm_dev,
|
||||
const enum mtk_ddp_comp_id *path,
|
||||
unsigned int path_len);
|
||||
|
||||
#endif /* MTK_DRM_CRTC_H */
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.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_DISP_OVL0 BIT(11)
|
||||
#define MUTEX_MOD_DISP_OVL1 BIT(12)
|
||||
#define MUTEX_MOD_DISP_RDMA0 BIT(13)
|
||||
#define MUTEX_MOD_DISP_RDMA1 BIT(14)
|
||||
#define MUTEX_MOD_DISP_RDMA2 BIT(15)
|
||||
#define MUTEX_MOD_DISP_WDMA0 BIT(16)
|
||||
#define MUTEX_MOD_DISP_WDMA1 BIT(17)
|
||||
#define MUTEX_MOD_DISP_COLOR0 BIT(18)
|
||||
#define MUTEX_MOD_DISP_COLOR1 BIT(19)
|
||||
#define MUTEX_MOD_DISP_AAL BIT(20)
|
||||
#define MUTEX_MOD_DISP_GAMMA BIT(21)
|
||||
#define MUTEX_MOD_DISP_UFOE BIT(22)
|
||||
#define MUTEX_MOD_DISP_PWM0 BIT(23)
|
||||
#define MUTEX_MOD_DISP_PWM1 BIT(24)
|
||||
#define MUTEX_MOD_DISP_OD BIT(25)
|
||||
|
||||
#define MUTEX_SOF_SINGLE_MODE 0
|
||||
#define MUTEX_SOF_DSI0 1
|
||||
#define MUTEX_SOF_DSI1 2
|
||||
#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
|
||||
|
||||
struct mtk_disp_mutex {
|
||||
int id;
|
||||
bool claimed;
|
||||
};
|
||||
|
||||
struct mtk_ddp {
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
void __iomem *regs;
|
||||
struct mtk_disp_mutex mutex[10];
|
||||
};
|
||||
|
||||
static const unsigned int mutex_mod[DDP_COMPONENT_ID_MAX] = {
|
||||
[DDP_COMPONENT_AAL] = MUTEX_MOD_DISP_AAL,
|
||||
[DDP_COMPONENT_COLOR0] = MUTEX_MOD_DISP_COLOR0,
|
||||
[DDP_COMPONENT_COLOR1] = MUTEX_MOD_DISP_COLOR1,
|
||||
[DDP_COMPONENT_GAMMA] = MUTEX_MOD_DISP_GAMMA,
|
||||
[DDP_COMPONENT_OD] = MUTEX_MOD_DISP_OD,
|
||||
[DDP_COMPONENT_OVL0] = MUTEX_MOD_DISP_OVL0,
|
||||
[DDP_COMPONENT_OVL1] = MUTEX_MOD_DISP_OVL1,
|
||||
[DDP_COMPONENT_PWM0] = MUTEX_MOD_DISP_PWM0,
|
||||
[DDP_COMPONENT_PWM1] = MUTEX_MOD_DISP_PWM1,
|
||||
[DDP_COMPONENT_RDMA0] = MUTEX_MOD_DISP_RDMA0,
|
||||
[DDP_COMPONENT_RDMA1] = MUTEX_MOD_DISP_RDMA1,
|
||||
[DDP_COMPONENT_RDMA2] = MUTEX_MOD_DISP_RDMA2,
|
||||
[DDP_COMPONENT_UFOE] = MUTEX_MOD_DISP_UFOE,
|
||||
[DDP_COMPONENT_WDMA0] = MUTEX_MOD_DISP_WDMA0,
|
||||
[DDP_COMPONENT_WDMA1] = MUTEX_MOD_DISP_WDMA1,
|
||||
};
|
||||
|
||||
static unsigned int mtk_ddp_mout_en(enum mtk_ddp_comp_id cur,
|
||||
enum mtk_ddp_comp_id next,
|
||||
unsigned int *addr)
|
||||
{
|
||||
unsigned int 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;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static unsigned int mtk_ddp_sel_in(enum mtk_ddp_comp_id cur,
|
||||
enum mtk_ddp_comp_id next,
|
||||
unsigned int *addr)
|
||||
{
|
||||
unsigned int value;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void mtk_ddp_add_comp_to_path(void __iomem *config_regs,
|
||||
enum mtk_ddp_comp_id cur,
|
||||
enum mtk_ddp_comp_id next)
|
||||
{
|
||||
unsigned int addr, value, reg;
|
||||
|
||||
value = mtk_ddp_mout_en(cur, next, &addr);
|
||||
if (value) {
|
||||
reg = readl_relaxed(config_regs + addr) | value;
|
||||
writel_relaxed(reg, config_regs + addr);
|
||||
}
|
||||
|
||||
value = mtk_ddp_sel_in(cur, next, &addr);
|
||||
if (value) {
|
||||
reg = readl_relaxed(config_regs + addr) | value;
|
||||
writel_relaxed(reg, config_regs + addr);
|
||||
}
|
||||
}
|
||||
|
||||
void mtk_ddp_remove_comp_from_path(void __iomem *config_regs,
|
||||
enum mtk_ddp_comp_id cur,
|
||||
enum mtk_ddp_comp_id next)
|
||||
{
|
||||
unsigned int addr, value, reg;
|
||||
|
||||
value = mtk_ddp_mout_en(cur, next, &addr);
|
||||
if (value) {
|
||||
reg = readl_relaxed(config_regs + addr) & ~value;
|
||||
writel_relaxed(reg, config_regs + addr);
|
||||
}
|
||||
|
||||
value = mtk_ddp_sel_in(cur, next, &addr);
|
||||
if (value) {
|
||||
reg = readl_relaxed(config_regs + addr) & ~value;
|
||||
writel_relaxed(reg, config_regs + addr);
|
||||
}
|
||||
}
|
||||
|
||||
struct mtk_disp_mutex *mtk_disp_mutex_get(struct device *dev, unsigned int id)
|
||||
{
|
||||
struct mtk_ddp *ddp = dev_get_drvdata(dev);
|
||||
|
||||
if (id >= 10)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (ddp->mutex[id].claimed)
|
||||
return ERR_PTR(-EBUSY);
|
||||
|
||||
ddp->mutex[id].claimed = true;
|
||||
|
||||
return &ddp->mutex[id];
|
||||
}
|
||||
|
||||
void mtk_disp_mutex_put(struct mtk_disp_mutex *mutex)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
|
||||
WARN_ON(&ddp->mutex[mutex->id] != mutex);
|
||||
|
||||
mutex->claimed = false;
|
||||
}
|
||||
|
||||
int mtk_disp_mutex_prepare(struct mtk_disp_mutex *mutex)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
return clk_prepare_enable(ddp->clk);
|
||||
}
|
||||
|
||||
void mtk_disp_mutex_unprepare(struct mtk_disp_mutex *mutex)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
clk_disable_unprepare(ddp->clk);
|
||||
}
|
||||
|
||||
void mtk_disp_mutex_add_comp(struct mtk_disp_mutex *mutex,
|
||||
enum mtk_ddp_comp_id id)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
unsigned int reg;
|
||||
|
||||
WARN_ON(&ddp->mutex[mutex->id] != mutex);
|
||||
|
||||
switch (id) {
|
||||
case DDP_COMPONENT_DSI0:
|
||||
reg = MUTEX_SOF_DSI0;
|
||||
break;
|
||||
case DDP_COMPONENT_DSI1:
|
||||
reg = MUTEX_SOF_DSI0;
|
||||
break;
|
||||
case DDP_COMPONENT_DPI0:
|
||||
reg = MUTEX_SOF_DPI0;
|
||||
break;
|
||||
default:
|
||||
reg = readl_relaxed(ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
|
||||
reg |= mutex_mod[id];
|
||||
writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
|
||||
return;
|
||||
}
|
||||
|
||||
writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_SOF(mutex->id));
|
||||
}
|
||||
|
||||
void mtk_disp_mutex_remove_comp(struct mtk_disp_mutex *mutex,
|
||||
enum mtk_ddp_comp_id id)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
unsigned int reg;
|
||||
|
||||
WARN_ON(&ddp->mutex[mutex->id] != mutex);
|
||||
|
||||
switch (id) {
|
||||
case DDP_COMPONENT_DSI0:
|
||||
case DDP_COMPONENT_DSI1:
|
||||
case DDP_COMPONENT_DPI0:
|
||||
writel_relaxed(MUTEX_SOF_SINGLE_MODE,
|
||||
ddp->regs + DISP_REG_MUTEX_SOF(mutex->id));
|
||||
break;
|
||||
default:
|
||||
reg = readl_relaxed(ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
|
||||
reg &= ~mutex_mod[id];
|
||||
writel_relaxed(reg, ddp->regs + DISP_REG_MUTEX_MOD(mutex->id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void mtk_disp_mutex_enable(struct mtk_disp_mutex *mutex)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
|
||||
WARN_ON(&ddp->mutex[mutex->id] != mutex);
|
||||
|
||||
writel(1, ddp->regs + DISP_REG_MUTEX_EN(mutex->id));
|
||||
}
|
||||
|
||||
void mtk_disp_mutex_disable(struct mtk_disp_mutex *mutex)
|
||||
{
|
||||
struct mtk_ddp *ddp = container_of(mutex, struct mtk_ddp,
|
||||
mutex[mutex->id]);
|
||||
|
||||
WARN_ON(&ddp->mutex[mutex->id] != mutex);
|
||||
|
||||
writel(0, ddp->regs + DISP_REG_MUTEX_EN(mutex->id));
|
||||
}
|
||||
|
||||
static int mtk_ddp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_ddp *ddp;
|
||||
struct resource *regs;
|
||||
int i;
|
||||
|
||||
ddp = devm_kzalloc(dev, sizeof(*ddp), GFP_KERNEL);
|
||||
if (!ddp)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
ddp->mutex[i].id = i;
|
||||
|
||||
ddp->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(ddp->clk)) {
|
||||
dev_err(dev, "Failed to get clock\n");
|
||||
return PTR_ERR(ddp->clk);
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ddp->regs = devm_ioremap_resource(dev, regs);
|
||||
if (IS_ERR(ddp->regs)) {
|
||||
dev_err(dev, "Failed to map mutex registers\n");
|
||||
return PTR_ERR(ddp->regs);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ddp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_ddp_remove(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ddp_driver_dt_match[] = {
|
||||
{ .compatible = "mediatek,mt8173-disp-mutex" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ddp_driver_dt_match);
|
||||
|
||||
struct platform_driver mtk_ddp_driver = {
|
||||
.probe = mtk_ddp_probe,
|
||||
.remove = mtk_ddp_remove,
|
||||
.driver = {
|
||||
.name = "mediatek-ddp",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ddp_driver_dt_match,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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_disp_mutex;
|
||||
|
||||
void mtk_ddp_add_comp_to_path(void __iomem *config_regs,
|
||||
enum mtk_ddp_comp_id cur,
|
||||
enum mtk_ddp_comp_id next);
|
||||
void mtk_ddp_remove_comp_from_path(void __iomem *config_regs,
|
||||
enum mtk_ddp_comp_id cur,
|
||||
enum mtk_ddp_comp_id next);
|
||||
|
||||
struct mtk_disp_mutex *mtk_disp_mutex_get(struct device *dev, unsigned int id);
|
||||
int mtk_disp_mutex_prepare(struct mtk_disp_mutex *mutex);
|
||||
void mtk_disp_mutex_add_comp(struct mtk_disp_mutex *mutex,
|
||||
enum mtk_ddp_comp_id id);
|
||||
void mtk_disp_mutex_enable(struct mtk_disp_mutex *mutex);
|
||||
void mtk_disp_mutex_disable(struct mtk_disp_mutex *mutex);
|
||||
void mtk_disp_mutex_remove_comp(struct mtk_disp_mutex *mutex,
|
||||
enum mtk_ddp_comp_id id);
|
||||
void mtk_disp_mutex_unprepare(struct mtk_disp_mutex *mutex);
|
||||
void mtk_disp_mutex_put(struct mtk_disp_mutex *mutex);
|
||||
|
||||
#endif /* MTK_DRM_DDP_H */
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <drm/drmP.h>
|
||||
#include "mtk_drm_drv.h"
|
||||
#include "mtk_drm_plane.h"
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
|
||||
#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
|
||||
#define DISP_COLOR_WIDTH 0x0c50
|
||||
#define DISP_COLOR_HEIGHT 0x0c54
|
||||
|
||||
#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_config(struct mtk_ddp_comp *comp, unsigned int w,
|
||||
unsigned int h, unsigned int vrefresh)
|
||||
{
|
||||
writel(w, comp->regs + DISP_COLOR_WIDTH);
|
||||
writel(h, comp->regs + DISP_COLOR_HEIGHT);
|
||||
}
|
||||
|
||||
static void mtk_color_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL,
|
||||
comp->regs + DISP_COLOR_CFG_MAIN);
|
||||
writel(0x1, comp->regs + DISP_COLOR_START);
|
||||
}
|
||||
|
||||
static void mtk_od_config(struct mtk_ddp_comp *comp, unsigned int w,
|
||||
unsigned int h, unsigned int vrefresh)
|
||||
{
|
||||
writel(w << 16 | h, comp->regs + DISP_OD_SIZE);
|
||||
}
|
||||
|
||||
static void mtk_od_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
writel(OD_RELAY_MODE, comp->regs + DISP_OD_CFG);
|
||||
writel(1, comp->regs + DISP_OD_EN);
|
||||
}
|
||||
|
||||
static void mtk_ufoe_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
writel(UFO_BYPASS, comp->regs + DISP_REG_UFO_START);
|
||||
}
|
||||
|
||||
static const struct mtk_ddp_comp_funcs ddp_color = {
|
||||
.config = mtk_color_config,
|
||||
.start = mtk_color_start,
|
||||
};
|
||||
|
||||
static const struct mtk_ddp_comp_funcs ddp_od = {
|
||||
.config = mtk_od_config,
|
||||
.start = mtk_od_start,
|
||||
};
|
||||
|
||||
static const struct mtk_ddp_comp_funcs ddp_ufoe = {
|
||||
.start = mtk_ufoe_start,
|
||||
};
|
||||
|
||||
static const char * const mtk_ddp_comp_stem[MTK_DDP_COMP_TYPE_MAX] = {
|
||||
[MTK_DISP_OVL] = "ovl",
|
||||
[MTK_DISP_RDMA] = "rdma",
|
||||
[MTK_DISP_WDMA] = "wdma",
|
||||
[MTK_DISP_COLOR] = "color",
|
||||
[MTK_DISP_AAL] = "aal",
|
||||
[MTK_DISP_GAMMA] = "gamma",
|
||||
[MTK_DISP_UFOE] = "ufoe",
|
||||
[MTK_DSI] = "dsi",
|
||||
[MTK_DPI] = "dpi",
|
||||
[MTK_DISP_PWM] = "pwm",
|
||||
[MTK_DISP_MUTEX] = "mutex",
|
||||
[MTK_DISP_OD] = "od",
|
||||
};
|
||||
|
||||
struct mtk_ddp_comp_match {
|
||||
enum mtk_ddp_comp_type type;
|
||||
int alias_id;
|
||||
const struct mtk_ddp_comp_funcs *funcs;
|
||||
};
|
||||
|
||||
static const struct mtk_ddp_comp_match mtk_ddp_matches[DDP_COMPONENT_ID_MAX] = {
|
||||
[DDP_COMPONENT_AAL] = { MTK_DISP_AAL, 0, NULL },
|
||||
[DDP_COMPONENT_COLOR0] = { MTK_DISP_COLOR, 0, &ddp_color },
|
||||
[DDP_COMPONENT_COLOR1] = { MTK_DISP_COLOR, 1, &ddp_color },
|
||||
[DDP_COMPONENT_DPI0] = { MTK_DPI, 0, NULL },
|
||||
[DDP_COMPONENT_DSI0] = { MTK_DSI, 0, NULL },
|
||||
[DDP_COMPONENT_DSI1] = { MTK_DSI, 1, NULL },
|
||||
[DDP_COMPONENT_GAMMA] = { MTK_DISP_GAMMA, 0, NULL },
|
||||
[DDP_COMPONENT_OD] = { MTK_DISP_OD, 0, &ddp_od },
|
||||
[DDP_COMPONENT_OVL0] = { MTK_DISP_OVL, 0, NULL },
|
||||
[DDP_COMPONENT_OVL1] = { MTK_DISP_OVL, 1, NULL },
|
||||
[DDP_COMPONENT_PWM0] = { MTK_DISP_PWM, 0, NULL },
|
||||
[DDP_COMPONENT_RDMA0] = { MTK_DISP_RDMA, 0, NULL },
|
||||
[DDP_COMPONENT_RDMA1] = { MTK_DISP_RDMA, 1, NULL },
|
||||
[DDP_COMPONENT_RDMA2] = { MTK_DISP_RDMA, 2, NULL },
|
||||
[DDP_COMPONENT_UFOE] = { MTK_DISP_UFOE, 0, &ddp_ufoe },
|
||||
[DDP_COMPONENT_WDMA0] = { MTK_DISP_WDMA, 0, NULL },
|
||||
[DDP_COMPONENT_WDMA1] = { MTK_DISP_WDMA, 1, NULL },
|
||||
};
|
||||
|
||||
int mtk_ddp_comp_get_id(struct device_node *node,
|
||||
enum mtk_ddp_comp_type comp_type)
|
||||
{
|
||||
int id = of_alias_get_id(node, mtk_ddp_comp_stem[comp_type]);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mtk_ddp_matches); i++) {
|
||||
if (comp_type == mtk_ddp_matches[i].type &&
|
||||
(id < 0 || id == mtk_ddp_matches[i].alias_id))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int mtk_ddp_comp_init(struct device *dev, struct device_node *node,
|
||||
struct mtk_ddp_comp *comp, enum mtk_ddp_comp_id comp_id,
|
||||
const struct mtk_ddp_comp_funcs *funcs)
|
||||
{
|
||||
enum mtk_ddp_comp_type type;
|
||||
struct device_node *larb_node;
|
||||
struct platform_device *larb_pdev;
|
||||
|
||||
if (comp_id < 0 || comp_id >= DDP_COMPONENT_ID_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
comp->id = comp_id;
|
||||
comp->funcs = funcs ?: mtk_ddp_matches[comp_id].funcs;
|
||||
|
||||
if (comp_id == DDP_COMPONENT_DPI0 ||
|
||||
comp_id == DDP_COMPONENT_DSI0 ||
|
||||
comp_id == DDP_COMPONENT_PWM0) {
|
||||
comp->regs = NULL;
|
||||
comp->clk = NULL;
|
||||
comp->irq = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
comp->regs = of_iomap(node, 0);
|
||||
comp->irq = of_irq_get(node, 0);
|
||||
comp->clk = of_clk_get(node, 0);
|
||||
if (IS_ERR(comp->clk))
|
||||
comp->clk = NULL;
|
||||
|
||||
type = mtk_ddp_matches[comp_id].type;
|
||||
|
||||
/* Only DMA capable components need the LARB property */
|
||||
comp->larb_dev = NULL;
|
||||
if (type != MTK_DISP_OVL &&
|
||||
type != MTK_DISP_RDMA &&
|
||||
type != MTK_DISP_WDMA)
|
||||
return 0;
|
||||
|
||||
larb_node = of_parse_phandle(node, "mediatek,larb", 0);
|
||||
if (!larb_node) {
|
||||
dev_err(dev,
|
||||
"Missing mediadek,larb phandle in %s node\n",
|
||||
node->full_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
larb_pdev = of_find_device_by_node(larb_node);
|
||||
if (!larb_pdev) {
|
||||
dev_warn(dev, "Waiting for larb device %s\n",
|
||||
larb_node->full_name);
|
||||
of_node_put(larb_node);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
of_node_put(larb_node);
|
||||
|
||||
comp->larb_dev = &larb_pdev->dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mtk_ddp_comp_register(struct drm_device *drm, struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_drm_private *private = drm->dev_private;
|
||||
|
||||
if (private->ddp_comp[comp->id])
|
||||
return -EBUSY;
|
||||
|
||||
private->ddp_comp[comp->id] = comp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mtk_ddp_comp_unregister(struct drm_device *drm, struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_drm_private *private = drm->dev_private;
|
||||
|
||||
private->ddp_comp[comp->id] = NULL;
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef MTK_DRM_DDP_COMP_H
|
||||
#define MTK_DRM_DDP_COMP_H
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
struct device;
|
||||
struct device_node;
|
||||
struct drm_crtc;
|
||||
struct drm_device;
|
||||
struct mtk_plane_state;
|
||||
|
||||
enum mtk_ddp_comp_type {
|
||||
MTK_DISP_OVL,
|
||||
MTK_DISP_RDMA,
|
||||
MTK_DISP_WDMA,
|
||||
MTK_DISP_COLOR,
|
||||
MTK_DISP_AAL,
|
||||
MTK_DISP_GAMMA,
|
||||
MTK_DISP_UFOE,
|
||||
MTK_DSI,
|
||||
MTK_DPI,
|
||||
MTK_DISP_PWM,
|
||||
MTK_DISP_MUTEX,
|
||||
MTK_DISP_OD,
|
||||
MTK_DDP_COMP_TYPE_MAX,
|
||||
};
|
||||
|
||||
enum mtk_ddp_comp_id {
|
||||
DDP_COMPONENT_AAL,
|
||||
DDP_COMPONENT_COLOR0,
|
||||
DDP_COMPONENT_COLOR1,
|
||||
DDP_COMPONENT_DPI0,
|
||||
DDP_COMPONENT_DSI0,
|
||||
DDP_COMPONENT_DSI1,
|
||||
DDP_COMPONENT_GAMMA,
|
||||
DDP_COMPONENT_OD,
|
||||
DDP_COMPONENT_OVL0,
|
||||
DDP_COMPONENT_OVL1,
|
||||
DDP_COMPONENT_PWM0,
|
||||
DDP_COMPONENT_PWM1,
|
||||
DDP_COMPONENT_RDMA0,
|
||||
DDP_COMPONENT_RDMA1,
|
||||
DDP_COMPONENT_RDMA2,
|
||||
DDP_COMPONENT_UFOE,
|
||||
DDP_COMPONENT_WDMA0,
|
||||
DDP_COMPONENT_WDMA1,
|
||||
DDP_COMPONENT_ID_MAX,
|
||||
};
|
||||
|
||||
struct mtk_ddp_comp;
|
||||
|
||||
struct mtk_ddp_comp_funcs {
|
||||
void (*config)(struct mtk_ddp_comp *comp, unsigned int w,
|
||||
unsigned int h, unsigned int vrefresh);
|
||||
void (*start)(struct mtk_ddp_comp *comp);
|
||||
void (*stop)(struct mtk_ddp_comp *comp);
|
||||
void (*enable_vblank)(struct mtk_ddp_comp *comp, struct drm_crtc *crtc);
|
||||
void (*disable_vblank)(struct mtk_ddp_comp *comp);
|
||||
void (*layer_on)(struct mtk_ddp_comp *comp, unsigned int idx);
|
||||
void (*layer_off)(struct mtk_ddp_comp *comp, unsigned int idx);
|
||||
void (*layer_config)(struct mtk_ddp_comp *comp, unsigned int idx,
|
||||
struct mtk_plane_state *state);
|
||||
};
|
||||
|
||||
struct mtk_ddp_comp {
|
||||
struct clk *clk;
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
struct device *larb_dev;
|
||||
enum mtk_ddp_comp_id id;
|
||||
const struct mtk_ddp_comp_funcs *funcs;
|
||||
};
|
||||
|
||||
static inline void mtk_ddp_comp_config(struct mtk_ddp_comp *comp,
|
||||
unsigned int w, unsigned int h,
|
||||
unsigned int vrefresh)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->config)
|
||||
comp->funcs->config(comp, w, h, vrefresh);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->start)
|
||||
comp->funcs->start(comp);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_stop(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->stop)
|
||||
comp->funcs->stop(comp);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_enable_vblank(struct mtk_ddp_comp *comp,
|
||||
struct drm_crtc *crtc)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->enable_vblank)
|
||||
comp->funcs->enable_vblank(comp, crtc);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_disable_vblank(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->disable_vblank)
|
||||
comp->funcs->disable_vblank(comp);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_layer_on(struct mtk_ddp_comp *comp,
|
||||
unsigned int idx)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->layer_on)
|
||||
comp->funcs->layer_on(comp, idx);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_layer_off(struct mtk_ddp_comp *comp,
|
||||
unsigned int idx)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->layer_off)
|
||||
comp->funcs->layer_off(comp, idx);
|
||||
}
|
||||
|
||||
static inline void mtk_ddp_comp_layer_config(struct mtk_ddp_comp *comp,
|
||||
unsigned int idx,
|
||||
struct mtk_plane_state *state)
|
||||
{
|
||||
if (comp->funcs && comp->funcs->layer_config)
|
||||
comp->funcs->layer_config(comp, idx, state);
|
||||
}
|
||||
|
||||
int mtk_ddp_comp_get_id(struct device_node *node,
|
||||
enum mtk_ddp_comp_type comp_type);
|
||||
int mtk_ddp_comp_init(struct device *dev, struct device_node *comp_node,
|
||||
struct mtk_ddp_comp *comp, enum mtk_ddp_comp_id comp_id,
|
||||
const struct mtk_ddp_comp_funcs *funcs);
|
||||
int mtk_ddp_comp_register(struct drm_device *drm, struct mtk_ddp_comp *comp);
|
||||
void mtk_ddp_comp_unregister(struct drm_device *drm, struct mtk_ddp_comp *comp);
|
||||
|
||||
#endif /* MTK_DRM_DDP_COMP_H */
|
|
@ -0,0 +1,567 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
* Author: YT SHEN <yt.shen@mediatek.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_gem.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "mtk_drm_crtc.h"
|
||||
#include "mtk_drm_ddp.h"
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
#include "mtk_drm_drv.h"
|
||||
#include "mtk_drm_fb.h"
|
||||
#include "mtk_drm_gem.h"
|
||||
|
||||
#define DRIVER_NAME "mediatek"
|
||||
#define DRIVER_DESC "Mediatek SoC DRM"
|
||||
#define DRIVER_DATE "20150513"
|
||||
#define DRIVER_MAJOR 1
|
||||
#define DRIVER_MINOR 0
|
||||
|
||||
static void mtk_atomic_schedule(struct mtk_drm_private *private,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
private->commit.state = state;
|
||||
schedule_work(&private->commit.work);
|
||||
}
|
||||
|
||||
static void mtk_atomic_wait_for_fences(struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_plane *plane;
|
||||
struct drm_plane_state *plane_state;
|
||||
int i;
|
||||
|
||||
for_each_plane_in_state(state, plane, plane_state, i)
|
||||
mtk_fb_wait(plane->state->fb);
|
||||
}
|
||||
|
||||
static void mtk_atomic_complete(struct mtk_drm_private *private,
|
||||
struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_device *drm = private->drm;
|
||||
|
||||
mtk_atomic_wait_for_fences(state);
|
||||
|
||||
drm_atomic_helper_commit_modeset_disables(drm, state);
|
||||
drm_atomic_helper_commit_planes(drm, state, false);
|
||||
drm_atomic_helper_commit_modeset_enables(drm, state);
|
||||
drm_atomic_helper_wait_for_vblanks(drm, state);
|
||||
drm_atomic_helper_cleanup_planes(drm, state);
|
||||
drm_atomic_state_free(state);
|
||||
}
|
||||
|
||||
static void mtk_atomic_work(struct work_struct *work)
|
||||
{
|
||||
struct mtk_drm_private *private = container_of(work,
|
||||
struct mtk_drm_private, commit.work);
|
||||
|
||||
mtk_atomic_complete(private, private->commit.state);
|
||||
}
|
||||
|
||||
static int mtk_atomic_commit(struct drm_device *drm,
|
||||
struct drm_atomic_state *state,
|
||||
bool async)
|
||||
{
|
||||
struct mtk_drm_private *private = drm->dev_private;
|
||||
int ret;
|
||||
|
||||
ret = drm_atomic_helper_prepare_planes(drm, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&private->commit.lock);
|
||||
flush_work(&private->commit.work);
|
||||
|
||||
drm_atomic_helper_swap_state(drm, state);
|
||||
|
||||
if (async)
|
||||
mtk_atomic_schedule(private, state);
|
||||
else
|
||||
mtk_atomic_complete(private, state);
|
||||
|
||||
mutex_unlock(&private->commit.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_funcs mtk_drm_mode_config_funcs = {
|
||||
.fb_create = mtk_drm_mode_fb_create,
|
||||
.atomic_check = drm_atomic_helper_check,
|
||||
.atomic_commit = mtk_atomic_commit,
|
||||
};
|
||||
|
||||
static const enum mtk_ddp_comp_id mtk_ddp_main[] = {
|
||||
DDP_COMPONENT_OVL0,
|
||||
DDP_COMPONENT_COLOR0,
|
||||
DDP_COMPONENT_AAL,
|
||||
DDP_COMPONENT_OD,
|
||||
DDP_COMPONENT_RDMA0,
|
||||
DDP_COMPONENT_UFOE,
|
||||
DDP_COMPONENT_DSI0,
|
||||
DDP_COMPONENT_PWM0,
|
||||
};
|
||||
|
||||
static const enum mtk_ddp_comp_id mtk_ddp_ext[] = {
|
||||
DDP_COMPONENT_OVL1,
|
||||
DDP_COMPONENT_COLOR1,
|
||||
DDP_COMPONENT_GAMMA,
|
||||
DDP_COMPONENT_RDMA1,
|
||||
DDP_COMPONENT_DPI0,
|
||||
};
|
||||
|
||||
static int mtk_drm_kms_init(struct drm_device *drm)
|
||||
{
|
||||
struct mtk_drm_private *private = drm->dev_private;
|
||||
struct platform_device *pdev;
|
||||
struct device_node *np;
|
||||
int ret;
|
||||
|
||||
if (!iommu_present(&platform_bus_type))
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
pdev = of_find_device_by_node(private->mutex_node);
|
||||
if (!pdev) {
|
||||
dev_err(drm->dev, "Waiting for disp-mutex device %s\n",
|
||||
private->mutex_node->full_name);
|
||||
of_node_put(private->mutex_node);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
private->mutex_dev = &pdev->dev;
|
||||
|
||||
drm_mode_config_init(drm);
|
||||
|
||||
drm->mode_config.min_width = 64;
|
||||
drm->mode_config.min_height = 64;
|
||||
|
||||
/*
|
||||
* set max width and height as default value(4096x4096).
|
||||
* this value would be used to check framebuffer size limitation
|
||||
* at drm_mode_addfb().
|
||||
*/
|
||||
drm->mode_config.max_width = 4096;
|
||||
drm->mode_config.max_height = 4096;
|
||||
drm->mode_config.funcs = &mtk_drm_mode_config_funcs;
|
||||
|
||||
ret = component_bind_all(drm->dev, drm);
|
||||
if (ret)
|
||||
goto err_config_cleanup;
|
||||
|
||||
/*
|
||||
* We currently support two fixed data streams, each optional,
|
||||
* and each statically assigned to a crtc:
|
||||
* OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0 ...
|
||||
*/
|
||||
ret = mtk_drm_crtc_create(drm, mtk_ddp_main, ARRAY_SIZE(mtk_ddp_main));
|
||||
if (ret < 0)
|
||||
goto err_component_unbind;
|
||||
/* ... and OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0. */
|
||||
ret = mtk_drm_crtc_create(drm, mtk_ddp_ext, ARRAY_SIZE(mtk_ddp_ext));
|
||||
if (ret < 0)
|
||||
goto err_component_unbind;
|
||||
|
||||
/* Use OVL device for all DMA memory allocations */
|
||||
np = private->comp_node[mtk_ddp_main[0]] ?:
|
||||
private->comp_node[mtk_ddp_ext[0]];
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev) {
|
||||
ret = -ENODEV;
|
||||
dev_err(drm->dev, "Need at least one OVL device\n");
|
||||
goto err_component_unbind;
|
||||
}
|
||||
|
||||
private->dma_dev = &pdev->dev;
|
||||
|
||||
/*
|
||||
* We don't use the drm_irq_install() helpers provided by the DRM
|
||||
* core, so we need to set this manually in order to allow the
|
||||
* DRM_IOCTL_WAIT_VBLANK to operate correctly.
|
||||
*/
|
||||
drm->irq_enabled = true;
|
||||
ret = drm_vblank_init(drm, MAX_CRTC);
|
||||
if (ret < 0)
|
||||
goto err_component_unbind;
|
||||
|
||||
drm_kms_helper_poll_init(drm);
|
||||
drm_mode_config_reset(drm);
|
||||
|
||||
return 0;
|
||||
|
||||
err_component_unbind:
|
||||
component_unbind_all(drm->dev, drm);
|
||||
err_config_cleanup:
|
||||
drm_mode_config_cleanup(drm);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_drm_kms_deinit(struct drm_device *drm)
|
||||
{
|
||||
drm_kms_helper_poll_fini(drm);
|
||||
|
||||
drm_vblank_cleanup(drm);
|
||||
component_unbind_all(drm->dev, drm);
|
||||
drm_mode_config_cleanup(drm);
|
||||
}
|
||||
|
||||
static const struct file_operations mtk_drm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = drm_open,
|
||||
.release = drm_release,
|
||||
.unlocked_ioctl = drm_ioctl,
|
||||
.mmap = mtk_drm_gem_mmap,
|
||||
.poll = drm_poll,
|
||||
.read = drm_read,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = drm_compat_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct drm_driver mtk_drm_driver = {
|
||||
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME |
|
||||
DRIVER_ATOMIC,
|
||||
|
||||
.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 = &drm_gem_cma_vm_ops,
|
||||
.dumb_create = mtk_drm_gem_dumb_create,
|
||||
.dumb_map_offset = mtk_drm_gem_dumb_map_offset,
|
||||
.dumb_destroy = drm_gem_dumb_destroy,
|
||||
|
||||
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
|
||||
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
|
||||
.gem_prime_export = drm_gem_prime_export,
|
||||
.gem_prime_import = drm_gem_prime_import,
|
||||
.gem_prime_get_sg_table = mtk_gem_prime_get_sg_table,
|
||||
.gem_prime_import_sg_table = mtk_gem_prime_import_sg_table,
|
||||
.gem_prime_mmap = mtk_drm_gem_mmap_buf,
|
||||
.fops = &mtk_drm_fops,
|
||||
|
||||
.name = DRIVER_NAME,
|
||||
.desc = DRIVER_DESC,
|
||||
.date = DRIVER_DATE,
|
||||
.major = DRIVER_MAJOR,
|
||||
.minor = DRIVER_MINOR,
|
||||
};
|
||||
|
||||
static int compare_of(struct device *dev, void *data)
|
||||
{
|
||||
return dev->of_node == data;
|
||||
}
|
||||
|
||||
static int mtk_drm_bind(struct device *dev)
|
||||
{
|
||||
struct mtk_drm_private *private = dev_get_drvdata(dev);
|
||||
struct drm_device *drm;
|
||||
int ret;
|
||||
|
||||
drm = drm_dev_alloc(&mtk_drm_driver, dev);
|
||||
if (!drm)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_dev_set_unique(drm, dev_name(dev));
|
||||
|
||||
drm->dev_private = private;
|
||||
private->drm = drm;
|
||||
|
||||
ret = mtk_drm_kms_init(drm);
|
||||
if (ret < 0)
|
||||
goto err_free;
|
||||
|
||||
ret = drm_dev_register(drm, 0);
|
||||
if (ret < 0)
|
||||
goto err_deinit;
|
||||
|
||||
ret = drm_connector_register_all(drm);
|
||||
if (ret < 0)
|
||||
goto err_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
drm_dev_unregister(drm);
|
||||
err_deinit:
|
||||
mtk_drm_kms_deinit(drm);
|
||||
err_free:
|
||||
drm_dev_unref(drm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_drm_unbind(struct device *dev)
|
||||
{
|
||||
struct mtk_drm_private *private = dev_get_drvdata(dev);
|
||||
|
||||
drm_put_dev(private->drm);
|
||||
private->drm = NULL;
|
||||
}
|
||||
|
||||
static const struct component_master_ops mtk_drm_ops = {
|
||||
.bind = mtk_drm_bind,
|
||||
.unbind = mtk_drm_unbind,
|
||||
};
|
||||
|
||||
static const struct of_device_id mtk_ddp_comp_dt_ids[] = {
|
||||
{ .compatible = "mediatek,mt8173-disp-ovl", .data = (void *)MTK_DISP_OVL },
|
||||
{ .compatible = "mediatek,mt8173-disp-rdma", .data = (void *)MTK_DISP_RDMA },
|
||||
{ .compatible = "mediatek,mt8173-disp-wdma", .data = (void *)MTK_DISP_WDMA },
|
||||
{ .compatible = "mediatek,mt8173-disp-color", .data = (void *)MTK_DISP_COLOR },
|
||||
{ .compatible = "mediatek,mt8173-disp-aal", .data = (void *)MTK_DISP_AAL},
|
||||
{ .compatible = "mediatek,mt8173-disp-gamma", .data = (void *)MTK_DISP_GAMMA, },
|
||||
{ .compatible = "mediatek,mt8173-disp-ufoe", .data = (void *)MTK_DISP_UFOE },
|
||||
{ .compatible = "mediatek,mt8173-dsi", .data = (void *)MTK_DSI },
|
||||
{ .compatible = "mediatek,mt8173-dpi", .data = (void *)MTK_DPI },
|
||||
{ .compatible = "mediatek,mt8173-disp-mutex", .data = (void *)MTK_DISP_MUTEX },
|
||||
{ .compatible = "mediatek,mt8173-disp-pwm", .data = (void *)MTK_DISP_PWM },
|
||||
{ .compatible = "mediatek,mt8173-disp-od", .data = (void *)MTK_DISP_OD },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int mtk_drm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_drm_private *private;
|
||||
struct resource *mem;
|
||||
struct device_node *node;
|
||||
struct component_match *match = NULL;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
private = devm_kzalloc(dev, sizeof(*private), GFP_KERNEL);
|
||||
if (!private)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&private->commit.lock);
|
||||
INIT_WORK(&private->commit.work, mtk_atomic_work);
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
private->config_regs = devm_ioremap_resource(dev, mem);
|
||||
if (IS_ERR(private->config_regs)) {
|
||||
ret = PTR_ERR(private->config_regs);
|
||||
dev_err(dev, "Failed to ioremap mmsys-config resource: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Iterate over sibling DISP function blocks */
|
||||
for_each_child_of_node(dev->of_node->parent, node) {
|
||||
const struct of_device_id *of_id;
|
||||
enum mtk_ddp_comp_type comp_type;
|
||||
int comp_id;
|
||||
|
||||
of_id = of_match_node(mtk_ddp_comp_dt_ids, node);
|
||||
if (!of_id)
|
||||
continue;
|
||||
|
||||
if (!of_device_is_available(node)) {
|
||||
dev_dbg(dev, "Skipping disabled component %s\n",
|
||||
node->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
comp_type = (enum mtk_ddp_comp_type)of_id->data;
|
||||
|
||||
if (comp_type == MTK_DISP_MUTEX) {
|
||||
private->mutex_node = of_node_get(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
comp_id = mtk_ddp_comp_get_id(node, comp_type);
|
||||
if (comp_id < 0) {
|
||||
dev_warn(dev, "Skipping unknown component %s\n",
|
||||
node->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
private->comp_node[comp_id] = of_node_get(node);
|
||||
|
||||
/*
|
||||
* Currently only the OVL, RDMA, DSI, and DPI blocks have
|
||||
* separate component platform drivers and initialize their own
|
||||
* DDP component structure. The others are initialized here.
|
||||
*/
|
||||
if (comp_type == MTK_DISP_OVL ||
|
||||
comp_type == MTK_DISP_RDMA ||
|
||||
comp_type == MTK_DSI ||
|
||||
comp_type == MTK_DPI) {
|
||||
dev_info(dev, "Adding component match for %s\n",
|
||||
node->full_name);
|
||||
component_match_add(dev, &match, compare_of, node);
|
||||
} else {
|
||||
struct mtk_ddp_comp *comp;
|
||||
|
||||
comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
|
||||
if (!comp) {
|
||||
ret = -ENOMEM;
|
||||
goto err_node;
|
||||
}
|
||||
|
||||
ret = mtk_ddp_comp_init(dev, node, comp, comp_id, NULL);
|
||||
if (ret)
|
||||
goto err_node;
|
||||
|
||||
private->ddp_comp[comp_id] = comp;
|
||||
}
|
||||
}
|
||||
|
||||
if (!private->mutex_node) {
|
||||
dev_err(dev, "Failed to find disp-mutex node\n");
|
||||
ret = -ENODEV;
|
||||
goto err_node;
|
||||
}
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
platform_set_drvdata(pdev, private);
|
||||
|
||||
ret = component_master_add_with_match(dev, &mtk_drm_ops, match);
|
||||
if (ret)
|
||||
goto err_pm;
|
||||
|
||||
return 0;
|
||||
|
||||
err_pm:
|
||||
pm_runtime_disable(dev);
|
||||
err_node:
|
||||
of_node_put(private->mutex_node);
|
||||
for (i = 0; i < DDP_COMPONENT_ID_MAX; i++)
|
||||
of_node_put(private->comp_node[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_drm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_drm_private *private = platform_get_drvdata(pdev);
|
||||
struct drm_device *drm = private->drm;
|
||||
int i;
|
||||
|
||||
drm_connector_unregister_all(drm);
|
||||
drm_dev_unregister(drm);
|
||||
mtk_drm_kms_deinit(drm);
|
||||
drm_dev_unref(drm);
|
||||
|
||||
component_master_del(&pdev->dev, &mtk_drm_ops);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
of_node_put(private->mutex_node);
|
||||
for (i = 0; i < DDP_COMPONENT_ID_MAX; i++)
|
||||
of_node_put(private->comp_node[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mtk_drm_sys_suspend(struct device *dev)
|
||||
{
|
||||
struct mtk_drm_private *private = dev_get_drvdata(dev);
|
||||
struct drm_device *drm = private->drm;
|
||||
|
||||
drm_kms_helper_poll_disable(drm);
|
||||
|
||||
private->suspend_state = drm_atomic_helper_suspend(drm);
|
||||
if (IS_ERR(private->suspend_state)) {
|
||||
drm_kms_helper_poll_enable(drm);
|
||||
return PTR_ERR(private->suspend_state);
|
||||
}
|
||||
|
||||
DRM_DEBUG_DRIVER("mtk_drm_sys_suspend\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_drm_sys_resume(struct device *dev)
|
||||
{
|
||||
struct mtk_drm_private *private = dev_get_drvdata(dev);
|
||||
struct drm_device *drm = private->drm;
|
||||
|
||||
drm_atomic_helper_resume(drm, private->suspend_state);
|
||||
drm_kms_helper_poll_enable(drm);
|
||||
|
||||
DRM_DEBUG_DRIVER("mtk_drm_sys_resume\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(mtk_drm_pm_ops, mtk_drm_sys_suspend,
|
||||
mtk_drm_sys_resume);
|
||||
|
||||
static const struct of_device_id mtk_drm_of_ids[] = {
|
||||
{ .compatible = "mediatek,mt8173-mmsys", },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver mtk_drm_platform_driver = {
|
||||
.probe = mtk_drm_probe,
|
||||
.remove = mtk_drm_remove,
|
||||
.driver = {
|
||||
.name = "mediatek-drm",
|
||||
.of_match_table = mtk_drm_of_ids,
|
||||
.pm = &mtk_drm_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_driver * const mtk_drm_drivers[] = {
|
||||
&mtk_ddp_driver,
|
||||
&mtk_disp_ovl_driver,
|
||||
&mtk_disp_rdma_driver,
|
||||
&mtk_dpi_driver,
|
||||
&mtk_drm_platform_driver,
|
||||
&mtk_dsi_driver,
|
||||
&mtk_mipi_tx_driver,
|
||||
};
|
||||
|
||||
static int __init mtk_drm_init(void)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mtk_drm_drivers); i++) {
|
||||
ret = platform_driver_register(mtk_drm_drivers[i]);
|
||||
if (ret < 0) {
|
||||
pr_err("Failed to register %s driver: %d\n",
|
||||
mtk_drm_drivers[i]->driver.name, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
while (--i >= 0)
|
||||
platform_driver_unregister(mtk_drm_drivers[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mtk_drm_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = ARRAY_SIZE(mtk_drm_drivers) - 1; i >= 0; i--)
|
||||
platform_driver_unregister(mtk_drm_drivers[i]);
|
||||
}
|
||||
|
||||
module_init(mtk_drm_init);
|
||||
module_exit(mtk_drm_exit);
|
||||
|
||||
MODULE_AUTHOR("YT SHEN <yt.shen@mediatek.com>");
|
||||
MODULE_DESCRIPTION("Mediatek SoC DRM driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef MTK_DRM_DRV_H
|
||||
#define MTK_DRM_DRV_H
|
||||
|
||||
#include <linux/io.h>
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
|
||||
#define MAX_CRTC 2
|
||||
#define MAX_CONNECTOR 2
|
||||
|
||||
struct device;
|
||||
struct device_node;
|
||||
struct drm_crtc;
|
||||
struct drm_device;
|
||||
struct drm_fb_helper;
|
||||
struct drm_property;
|
||||
struct regmap;
|
||||
|
||||
struct mtk_drm_private {
|
||||
struct drm_device *drm;
|
||||
struct device *dma_dev;
|
||||
|
||||
struct drm_crtc *crtc[MAX_CRTC];
|
||||
unsigned int num_pipes;
|
||||
|
||||
struct device_node *mutex_node;
|
||||
struct device *mutex_dev;
|
||||
void __iomem *config_regs;
|
||||
struct device_node *comp_node[DDP_COMPONENT_ID_MAX];
|
||||
struct mtk_ddp_comp *ddp_comp[DDP_COMPONENT_ID_MAX];
|
||||
|
||||
struct {
|
||||
struct drm_atomic_state *state;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
} commit;
|
||||
|
||||
struct drm_atomic_state *suspend_state;
|
||||
};
|
||||
|
||||
extern struct platform_driver mtk_ddp_driver;
|
||||
extern struct platform_driver mtk_disp_ovl_driver;
|
||||
extern struct platform_driver mtk_disp_rdma_driver;
|
||||
extern struct platform_driver mtk_dpi_driver;
|
||||
extern struct platform_driver mtk_dsi_driver;
|
||||
extern struct platform_driver mtk_mipi_tx_driver;
|
||||
|
||||
#endif /* MTK_DRM_DRV_H */
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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 <linux/dma-buf.h>
|
||||
#include <linux/reservation.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;
|
||||
/* For now we only support a single plane */
|
||||
struct drm_gem_object *gem_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)
|
||||
{
|
||||
struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
|
||||
|
||||
return mtk_fb->gem_obj;
|
||||
}
|
||||
|
||||
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, handle);
|
||||
}
|
||||
|
||||
static void mtk_drm_fb_destroy(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct mtk_drm_fb *mtk_fb = to_mtk_fb(fb);
|
||||
|
||||
drm_framebuffer_cleanup(fb);
|
||||
|
||||
drm_gem_object_unreference_unlocked(mtk_fb->gem_obj);
|
||||
|
||||
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,
|
||||
const struct drm_mode_fb_cmd2 *mode,
|
||||
struct drm_gem_object *obj)
|
||||
{
|
||||
struct mtk_drm_fb *mtk_fb;
|
||||
int ret;
|
||||
|
||||
if (drm_format_num_planes(mode->pixel_format) != 1)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
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);
|
||||
|
||||
mtk_fb->gem_obj = obj;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for any exclusive fence in fb's gem object's reservation object.
|
||||
*
|
||||
* Returns -ERESTARTSYS if interrupted, else 0.
|
||||
*/
|
||||
int mtk_fb_wait(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct drm_gem_object *gem;
|
||||
struct reservation_object *resv;
|
||||
long ret;
|
||||
|
||||
if (!fb)
|
||||
return 0;
|
||||
|
||||
gem = mtk_fb_get_gem_obj(fb);
|
||||
if (!gem || !gem->dma_buf || !gem->dma_buf->resv)
|
||||
return 0;
|
||||
|
||||
resv = gem->dma_buf->resv;
|
||||
ret = reservation_object_wait_timeout_rcu(resv, false, true,
|
||||
MAX_SCHEDULE_TIMEOUT);
|
||||
/* MAX_SCHEDULE_TIMEOUT on success, -ERESTARTSYS if interrupted */
|
||||
if (WARN_ON(ret < 0))
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
|
||||
struct drm_file *file,
|
||||
const struct drm_mode_fb_cmd2 *cmd)
|
||||
{
|
||||
struct mtk_drm_fb *mtk_fb;
|
||||
struct drm_gem_object *gem;
|
||||
unsigned int width = cmd->width;
|
||||
unsigned int height = cmd->height;
|
||||
unsigned int size, bpp;
|
||||
int ret;
|
||||
|
||||
if (drm_format_num_planes(cmd->pixel_format) != 1)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
gem = drm_gem_object_lookup(dev, file, cmd->handles[0]);
|
||||
if (!gem)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
bpp = drm_format_plane_cpp(cmd->pixel_format, 0);
|
||||
size = (height - 1) * cmd->pitches[0] + width * bpp;
|
||||
size += cmd->offsets[0];
|
||||
|
||||
if (gem->size < size) {
|
||||
ret = -EINVAL;
|
||||
goto unreference;
|
||||
}
|
||||
|
||||
mtk_fb = mtk_drm_framebuffer_init(dev, cmd, gem);
|
||||
if (IS_ERR(mtk_fb)) {
|
||||
ret = PTR_ERR(mtk_fb);
|
||||
goto unreference;
|
||||
}
|
||||
|
||||
return &mtk_fb->base;
|
||||
|
||||
unreference:
|
||||
drm_gem_object_unreference_unlocked(gem);
|
||||
return ERR_PTR(ret);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
struct drm_gem_object *mtk_fb_get_gem_obj(struct drm_framebuffer *fb);
|
||||
int mtk_fb_wait(struct drm_framebuffer *fb);
|
||||
struct drm_framebuffer *mtk_drm_mode_fb_create(struct drm_device *dev,
|
||||
struct drm_file *file,
|
||||
const struct drm_mode_fb_cmd2 *cmd);
|
||||
|
||||
#endif /* MTK_DRM_FB_H */
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* 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 <linux/dma-buf.h>
|
||||
|
||||
#include "mtk_drm_drv.h"
|
||||
#include "mtk_drm_gem.h"
|
||||
|
||||
static 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,
|
||||
size_t size, bool alloc_kmap)
|
||||
{
|
||||
struct mtk_drm_private *priv = dev->dev_private;
|
||||
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(priv->dma_dev, obj->size,
|
||||
&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_DEBUG_DRIVER("cookie = %p dma_addr = %pad size = %zu\n",
|
||||
mtk_gem->cookie, &mtk_gem->dma_addr,
|
||||
size);
|
||||
|
||||
return mtk_gem;
|
||||
|
||||
err_gem_free:
|
||||
drm_gem_object_release(obj);
|
||||
kfree(mtk_gem);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
void mtk_drm_gem_free_object(struct drm_gem_object *obj)
|
||||
{
|
||||
struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
|
||||
struct mtk_drm_private *priv = obj->dev->dev_private;
|
||||
|
||||
if (mtk_gem->sg)
|
||||
drm_prime_gem_destroy(obj, mtk_gem->sg);
|
||||
else
|
||||
dma_free_attrs(priv->dma_dev, obj->size, mtk_gem->cookie,
|
||||
mtk_gem->dma_addr, &mtk_gem->dma_attrs);
|
||||
|
||||
/* release file pointer to gem object. */
|
||||
drm_gem_object_release(obj);
|
||||
|
||||
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 = DIV_ROUND_UP(args->width * args->bpp, 8);
|
||||
args->size = args->pitch * args->height;
|
||||
|
||||
mtk_gem = mtk_drm_gem_create(dev, args->size, false);
|
||||
if (IS_ERR(mtk_gem))
|
||||
return PTR_ERR(mtk_gem);
|
||||
|
||||
/*
|
||||
* allocate a id of idr table where the obj is registered
|
||||
* and handle has the id what user can see.
|
||||
*/
|
||||
ret = drm_gem_handle_create(file_priv, &mtk_gem->base, &args->handle);
|
||||
if (ret)
|
||||
goto err_handle_create;
|
||||
|
||||
/* drop reference from allocate - handle holds it now. */
|
||||
drm_gem_object_unreference_unlocked(&mtk_gem->base);
|
||||
|
||||
return 0;
|
||||
|
||||
err_handle_create:
|
||||
mtk_drm_gem_free_object(&mtk_gem->base);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mtk_drm_gem_dumb_map_offset(struct drm_file *file_priv,
|
||||
struct drm_device *dev, uint32_t handle,
|
||||
uint64_t *offset)
|
||||
{
|
||||
struct drm_gem_object *obj;
|
||||
int ret;
|
||||
|
||||
obj = drm_gem_object_lookup(dev, file_priv, handle);
|
||||
if (!obj) {
|
||||
DRM_ERROR("failed to lookup gem object.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = drm_gem_create_mmap_offset(obj);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
*offset = drm_vma_node_offset_addr(&obj->vma_node);
|
||||
DRM_DEBUG_KMS("offset = 0x%llx\n", *offset);
|
||||
|
||||
out:
|
||||
drm_gem_object_unreference_unlocked(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_drm_gem_object_mmap(struct drm_gem_object *obj,
|
||||
struct vm_area_struct *vma)
|
||||
|
||||
{
|
||||
int ret;
|
||||
struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
|
||||
struct mtk_drm_private *priv = obj->dev->dev_private;
|
||||
|
||||
/*
|
||||
* dma_alloc_attrs() allocated a struct page table for mtk_gem, 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(priv->dma_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)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = drm_gem_mmap_obj(obj, obj->size, vma);
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a sg_table for this GEM object.
|
||||
* Note: Both the table's contents, and the sg_table itself must be freed by
|
||||
* the caller.
|
||||
* Returns a pointer to the newly allocated sg_table, or an ERR_PTR() error.
|
||||
*/
|
||||
struct sg_table *mtk_gem_prime_get_sg_table(struct drm_gem_object *obj)
|
||||
{
|
||||
struct mtk_drm_gem_obj *mtk_gem = to_mtk_gem_obj(obj);
|
||||
struct mtk_drm_private *priv = obj->dev->dev_private;
|
||||
struct sg_table *sgt;
|
||||
int ret;
|
||||
|
||||
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
|
||||
if (!sgt)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = dma_get_sgtable_attrs(priv->dma_dev, sgt, mtk_gem->cookie,
|
||||
mtk_gem->dma_addr, obj->size,
|
||||
&mtk_gem->dma_attrs);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to allocate sgt, %d\n", ret);
|
||||
kfree(sgt);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return sgt;
|
||||
}
|
||||
|
||||
struct drm_gem_object *mtk_gem_prime_import_sg_table(struct drm_device *dev,
|
||||
struct dma_buf_attachment *attach, struct sg_table *sg)
|
||||
{
|
||||
struct mtk_drm_gem_obj *mtk_gem;
|
||||
int ret;
|
||||
struct scatterlist *s;
|
||||
unsigned int i;
|
||||
dma_addr_t expected;
|
||||
|
||||
mtk_gem = mtk_drm_gem_init(dev, attach->dmabuf->size);
|
||||
|
||||
if (IS_ERR(mtk_gem))
|
||||
return ERR_PTR(PTR_ERR(mtk_gem));
|
||||
|
||||
expected = sg_dma_address(sg->sgl);
|
||||
for_each_sg(sg->sgl, s, sg->nents, i) {
|
||||
if (sg_dma_address(s) != expected) {
|
||||
DRM_ERROR("sg_table is not contiguous");
|
||||
ret = -EINVAL;
|
||||
goto err_gem_free;
|
||||
}
|
||||
expected = sg_dma_address(s) + sg_dma_len(s);
|
||||
}
|
||||
|
||||
mtk_gem->dma_addr = sg_dma_address(sg->sgl);
|
||||
mtk_gem->sg = sg;
|
||||
|
||||
return &mtk_gem->base;
|
||||
|
||||
err_gem_free:
|
||||
kfree(mtk_gem);
|
||||
return ERR_PTR(ret);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 *cookie;
|
||||
void *kvaddr;
|
||||
dma_addr_t dma_addr;
|
||||
struct dma_attrs dma_attrs;
|
||||
struct sg_table *sg;
|
||||
};
|
||||
|
||||
#define to_mtk_gem_obj(x) container_of(x, struct mtk_drm_gem_obj, base)
|
||||
|
||||
void mtk_drm_gem_free_object(struct drm_gem_object *gem);
|
||||
struct mtk_drm_gem_obj *mtk_drm_gem_create(struct drm_device *dev, size_t 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);
|
||||
struct sg_table *mtk_gem_prime_get_sg_table(struct drm_gem_object *obj);
|
||||
struct drm_gem_object *mtk_gem_prime_import_sg_table(struct drm_device *dev,
|
||||
struct dma_buf_attachment *attach, struct sg_table *sg);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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 "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 u32 formats[] = {
|
||||
DRM_FORMAT_XRGB8888,
|
||||
DRM_FORMAT_ARGB8888,
|
||||
DRM_FORMAT_RGB565,
|
||||
};
|
||||
|
||||
static void mtk_plane_enable(struct mtk_drm_plane *mtk_plane, bool enable,
|
||||
dma_addr_t addr, struct drm_rect *dest)
|
||||
{
|
||||
struct drm_plane *plane = &mtk_plane->base;
|
||||
struct mtk_plane_state *state = to_mtk_plane_state(plane->state);
|
||||
unsigned int pitch, format;
|
||||
int x, y;
|
||||
|
||||
if (WARN_ON(!plane->state || (enable && !plane->state->fb)))
|
||||
return;
|
||||
|
||||
if (plane->state->fb) {
|
||||
pitch = plane->state->fb->pitches[0];
|
||||
format = plane->state->fb->pixel_format;
|
||||
} else {
|
||||
pitch = 0;
|
||||
format = DRM_FORMAT_RGBA8888;
|
||||
}
|
||||
|
||||
x = plane->state->crtc_x;
|
||||
y = plane->state->crtc_y;
|
||||
|
||||
if (x < 0) {
|
||||
addr -= x * 4;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
addr -= y * pitch;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
state->pending.enable = enable;
|
||||
state->pending.pitch = pitch;
|
||||
state->pending.format = format;
|
||||
state->pending.addr = addr;
|
||||
state->pending.x = x;
|
||||
state->pending.y = y;
|
||||
state->pending.width = dest->x2 - dest->x1;
|
||||
state->pending.height = dest->y2 - dest->y1;
|
||||
wmb(); /* Make sure the above parameters are set before update */
|
||||
state->pending.dirty = true;
|
||||
}
|
||||
|
||||
static void mtk_plane_reset(struct drm_plane *plane)
|
||||
{
|
||||
struct mtk_plane_state *state;
|
||||
|
||||
if (plane->state) {
|
||||
if (plane->state->fb)
|
||||
drm_framebuffer_unreference(plane->state->fb);
|
||||
|
||||
state = to_mtk_plane_state(plane->state);
|
||||
memset(state, 0, sizeof(*state));
|
||||
} else {
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return;
|
||||
plane->state = &state->base;
|
||||
}
|
||||
|
||||
state->base.plane = plane;
|
||||
state->pending.format = DRM_FORMAT_RGB565;
|
||||
}
|
||||
|
||||
static struct drm_plane_state *mtk_plane_duplicate_state(struct drm_plane *plane)
|
||||
{
|
||||
struct mtk_plane_state *old_state = to_mtk_plane_state(plane->state);
|
||||
struct mtk_plane_state *state;
|
||||
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return NULL;
|
||||
|
||||
__drm_atomic_helper_plane_duplicate_state(plane, &state->base);
|
||||
|
||||
WARN_ON(state->base.plane != plane);
|
||||
|
||||
state->pending = old_state->pending;
|
||||
|
||||
return &state->base;
|
||||
}
|
||||
|
||||
static void mtk_drm_plane_destroy_state(struct drm_plane *plane,
|
||||
struct drm_plane_state *state)
|
||||
{
|
||||
__drm_atomic_helper_plane_destroy_state(plane, state);
|
||||
kfree(to_mtk_plane_state(state));
|
||||
}
|
||||
|
||||
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 = mtk_plane_reset,
|
||||
.atomic_duplicate_state = mtk_plane_duplicate_state,
|
||||
.atomic_destroy_state = mtk_drm_plane_destroy_state,
|
||||
};
|
||||
|
||||
static int mtk_plane_atomic_check(struct drm_plane *plane,
|
||||
struct drm_plane_state *state)
|
||||
{
|
||||
struct drm_framebuffer *fb = state->fb;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
bool visible;
|
||||
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)) {
|
||||
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;
|
||||
|
||||
return 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);
|
||||
}
|
||||
|
||||
static void mtk_plane_atomic_update(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
struct mtk_plane_state *state = to_mtk_plane_state(plane->state);
|
||||
struct drm_crtc *crtc = state->base.crtc;
|
||||
struct drm_gem_object *gem;
|
||||
struct mtk_drm_gem_obj *mtk_gem;
|
||||
struct mtk_drm_plane *mtk_plane = to_mtk_plane(plane);
|
||||
struct drm_rect dest = {
|
||||
.x1 = state->base.crtc_x,
|
||||
.y1 = state->base.crtc_y,
|
||||
.x2 = state->base.crtc_x + state->base.crtc_w,
|
||||
.y2 = state->base.crtc_y + state->base.crtc_h,
|
||||
};
|
||||
struct drm_rect clip = { 0, };
|
||||
|
||||
if (!crtc)
|
||||
return;
|
||||
|
||||
clip.x2 = state->base.crtc->state->mode.hdisplay;
|
||||
clip.y2 = state->base.crtc->state->mode.vdisplay;
|
||||
drm_rect_intersect(&dest, &clip);
|
||||
|
||||
gem = mtk_fb_get_gem_obj(state->base.fb);
|
||||
mtk_gem = to_mtk_gem_obj(gem);
|
||||
mtk_plane_enable(mtk_plane, true, mtk_gem->dma_addr, &dest);
|
||||
}
|
||||
|
||||
static void mtk_plane_atomic_disable(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
struct mtk_plane_state *state = to_mtk_plane_state(plane->state);
|
||||
|
||||
state->pending.enable = false;
|
||||
wmb(); /* Make sure the above parameter is set before update */
|
||||
state->pending.dirty = true;
|
||||
}
|
||||
|
||||
static const struct drm_plane_helper_funcs mtk_plane_helper_funcs = {
|
||||
.atomic_check = mtk_plane_atomic_check,
|
||||
.atomic_update = mtk_plane_atomic_update,
|
||||
.atomic_disable = mtk_plane_atomic_disable,
|
||||
};
|
||||
|
||||
int mtk_plane_init(struct drm_device *dev, struct mtk_drm_plane *mtk_plane,
|
||||
unsigned long possible_crtcs, enum drm_plane_type type,
|
||||
unsigned int zpos)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = drm_universal_plane_init(dev, &mtk_plane->base, possible_crtcs,
|
||||
&mtk_plane_funcs, formats,
|
||||
ARRAY_SIZE(formats), type, NULL);
|
||||
if (err) {
|
||||
DRM_ERROR("failed to initialize plane\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
drm_plane_helper_add(&mtk_plane->base, &mtk_plane_helper_funcs);
|
||||
mtk_plane->idx = zpos;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
* Author: CK Hu <ck.hu@mediatek.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _MTK_DRM_PLANE_H_
|
||||
#define _MTK_DRM_PLANE_H_
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct mtk_drm_plane {
|
||||
struct drm_plane base;
|
||||
unsigned int idx;
|
||||
};
|
||||
|
||||
struct mtk_plane_pending_state {
|
||||
bool config;
|
||||
bool enable;
|
||||
dma_addr_t addr;
|
||||
unsigned int pitch;
|
||||
unsigned int format;
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
bool dirty;
|
||||
};
|
||||
|
||||
struct mtk_plane_state {
|
||||
struct drm_plane_state base;
|
||||
struct mtk_plane_pending_state pending;
|
||||
};
|
||||
|
||||
static inline struct mtk_drm_plane *to_mtk_plane(struct drm_plane *plane)
|
||||
{
|
||||
return container_of(plane, struct mtk_drm_plane, base);
|
||||
}
|
||||
|
||||
static inline struct mtk_plane_state *
|
||||
to_mtk_plane_state(struct drm_plane_state *state)
|
||||
{
|
||||
return container_of(state, struct mtk_plane_state, base);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,913 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "mtk_drm_ddp_comp.h"
|
||||
|
||||
#define DSI_VIDEO_FIFO_DEPTH (1920 / 4)
|
||||
#define DSI_HOST_FIFO_DEPTH 64
|
||||
|
||||
#define DSI_START 0x00
|
||||
|
||||
#define DSI_CON_CTRL 0x10
|
||||
#define DSI_RESET BIT(0)
|
||||
#define DSI_EN BIT(1)
|
||||
|
||||
#define DSI_MODE_CTRL 0x14
|
||||
#define MODE (3)
|
||||
#define CMD_MODE 0
|
||||
#define SYNC_PULSE_MODE 1
|
||||
#define SYNC_EVENT_MODE 2
|
||||
#define BURST_MODE 3
|
||||
#define FRM_MODE BIT(16)
|
||||
#define MIX_MODE BIT(17)
|
||||
|
||||
#define DSI_TXRX_CTRL 0x18
|
||||
#define VC_NUM (2 << 0)
|
||||
#define LANE_NUM (0xf << 2)
|
||||
#define DIS_EOT BIT(6)
|
||||
#define NULL_EN BIT(7)
|
||||
#define TE_FREERUN BIT(8)
|
||||
#define EXT_TE_EN BIT(9)
|
||||
#define EXT_TE_EDGE BIT(10)
|
||||
#define MAX_RTN_SIZE (0xf << 12)
|
||||
#define HSTX_CKLP_EN BIT(16)
|
||||
|
||||
#define DSI_PSCTRL 0x1c
|
||||
#define DSI_PS_WC 0x3fff
|
||||
#define DSI_PS_SEL (3 << 16)
|
||||
#define PACKED_PS_16BIT_RGB565 (0 << 16)
|
||||
#define LOOSELY_PS_18BIT_RGB666 (1 << 16)
|
||||
#define PACKED_PS_18BIT_RGB666 (2 << 16)
|
||||
#define PACKED_PS_24BIT_RGB888 (3 << 16)
|
||||
|
||||
#define DSI_VSA_NL 0x20
|
||||
#define DSI_VBP_NL 0x24
|
||||
#define DSI_VFP_NL 0x28
|
||||
#define DSI_VACT_NL 0x2C
|
||||
#define DSI_HSA_WC 0x50
|
||||
#define DSI_HBP_WC 0x54
|
||||
#define DSI_HFP_WC 0x58
|
||||
|
||||
#define DSI_HSTX_CKL_WC 0x64
|
||||
|
||||
#define DSI_PHY_LCCON 0x104
|
||||
#define LC_HS_TX_EN BIT(0)
|
||||
#define LC_ULPM_EN BIT(1)
|
||||
#define LC_WAKEUP_EN BIT(2)
|
||||
|
||||
#define DSI_PHY_LD0CON 0x108
|
||||
#define LD0_HS_TX_EN BIT(0)
|
||||
#define LD0_ULPM_EN BIT(1)
|
||||
#define LD0_WAKEUP_EN BIT(2)
|
||||
|
||||
#define DSI_PHY_TIMECON0 0x110
|
||||
#define LPX (0xff << 0)
|
||||
#define HS_PRPR (0xff << 8)
|
||||
#define HS_ZERO (0xff << 16)
|
||||
#define HS_TRAIL (0xff << 24)
|
||||
|
||||
#define DSI_PHY_TIMECON1 0x114
|
||||
#define TA_GO (0xff << 0)
|
||||
#define TA_SURE (0xff << 8)
|
||||
#define TA_GET (0xff << 16)
|
||||
#define DA_HS_EXIT (0xff << 24)
|
||||
|
||||
#define DSI_PHY_TIMECON2 0x118
|
||||
#define CONT_DET (0xff << 0)
|
||||
#define CLK_ZERO (0xff << 16)
|
||||
#define CLK_TRAIL (0xff << 24)
|
||||
|
||||
#define DSI_PHY_TIMECON3 0x11c
|
||||
#define CLK_HS_PRPR (0xff << 0)
|
||||
#define CLK_HS_POST (0xff << 8)
|
||||
#define CLK_HS_EXIT (0xff << 16)
|
||||
|
||||
#define NS_TO_CYCLE(n, c) ((n) / (c) + (((n) % (c)) ? 1 : 0))
|
||||
|
||||
struct phy;
|
||||
|
||||
struct mtk_dsi {
|
||||
struct mtk_ddp_comp ddp_comp;
|
||||
struct device *dev;
|
||||
struct mipi_dsi_host host;
|
||||
struct drm_encoder encoder;
|
||||
struct drm_connector conn;
|
||||
struct drm_panel *panel;
|
||||
struct drm_bridge *bridge;
|
||||
struct phy *phy;
|
||||
|
||||
void __iomem *regs;
|
||||
|
||||
struct clk *engine_clk;
|
||||
struct clk *digital_clk;
|
||||
struct clk *hs_clk;
|
||||
|
||||
u32 data_rate;
|
||||
|
||||
unsigned long mode_flags;
|
||||
enum mipi_dsi_pixel_format format;
|
||||
unsigned int lanes;
|
||||
struct videomode vm;
|
||||
int refcount;
|
||||
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);
|
||||
}
|
||||
|
||||
static inline struct mtk_dsi *host_to_dsi(struct mipi_dsi_host *h)
|
||||
{
|
||||
return container_of(h, struct mtk_dsi, host);
|
||||
}
|
||||
|
||||
static void mtk_dsi_mask(struct mtk_dsi *dsi, u32 offset, u32 mask, u32 data)
|
||||
{
|
||||
u32 temp = readl(dsi->regs + offset);
|
||||
|
||||
writel((temp & ~mask) | (data & mask), dsi->regs + offset);
|
||||
}
|
||||
|
||||
static void dsi_phy_timconfig(struct mtk_dsi *dsi)
|
||||
{
|
||||
u32 timcon0, timcon1, timcon2, timcon3;
|
||||
unsigned int ui, cycle_time;
|
||||
unsigned int lpx;
|
||||
|
||||
ui = 1000 / dsi->data_rate + 0x01;
|
||||
cycle_time = 8000 / dsi->data_rate + 0x01;
|
||||
lpx = 5;
|
||||
|
||||
timcon0 = (8 << 24) | (0xa << 16) | (0x6 << 8) | lpx;
|
||||
timcon1 = (7 << 24) | (5 * lpx << 16) | ((3 * lpx) / 2) << 8 |
|
||||
(4 * lpx);
|
||||
timcon2 = ((NS_TO_CYCLE(0x64, cycle_time) + 0xa) << 24) |
|
||||
(NS_TO_CYCLE(0x150, cycle_time) << 16);
|
||||
timcon3 = (2 * lpx) << 16 | NS_TO_CYCLE(80 + 52 * ui, cycle_time) << 8 |
|
||||
NS_TO_CYCLE(0x40, cycle_time);
|
||||
|
||||
writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
|
||||
writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
|
||||
writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
|
||||
writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
|
||||
}
|
||||
|
||||
static void mtk_dsi_enable(struct mtk_dsi *dsi)
|
||||
{
|
||||
mtk_dsi_mask(dsi, DSI_CON_CTRL, DSI_EN, DSI_EN);
|
||||
}
|
||||
|
||||
static void mtk_dsi_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)
|
||||
{
|
||||
struct device *dev = dsi->dev;
|
||||
int ret;
|
||||
|
||||
if (++dsi->refcount != 1)
|
||||
return 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);
|
||||
|
||||
ret = clk_set_rate(dsi->hs_clk, dsi->data_rate * 1000000);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to set data rate: %d\n", ret);
|
||||
goto err_refcount;
|
||||
}
|
||||
|
||||
phy_power_on(dsi->phy);
|
||||
|
||||
ret = clk_prepare_enable(dsi->engine_clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to enable engine clock: %d\n", ret);
|
||||
goto err_phy_power_off;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(dsi->digital_clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to enable digital clock: %d\n", ret);
|
||||
goto err_disable_engine_clk;
|
||||
}
|
||||
|
||||
mtk_dsi_enable(dsi);
|
||||
mtk_dsi_reset(dsi);
|
||||
dsi_phy_timconfig(dsi);
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_engine_clk:
|
||||
clk_disable_unprepare(dsi->engine_clk);
|
||||
err_phy_power_off:
|
||||
phy_power_off(dsi->phy);
|
||||
err_refcount:
|
||||
dsi->refcount--;
|
||||
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->regs + 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->regs + 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->regs + DSI_VACT_NL);
|
||||
writel(ps_bpp_mode, dsi->regs + DSI_PSCTRL);
|
||||
writel(ps_wc, dsi->regs + 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->regs + 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->regs + 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->regs + DSI_VSA_NL);
|
||||
writel(vm->vback_porch, dsi->regs + DSI_VBP_NL);
|
||||
writel(vm->vfront_porch, dsi->regs + DSI_VFP_NL);
|
||||
writel(vm->vactive, dsi->regs + 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->regs + DSI_HSA_WC);
|
||||
writel(horizontal_backporch_byte, dsi->regs + DSI_HBP_WC);
|
||||
writel(horizontal_frontporch_byte, dsi->regs + DSI_HFP_WC);
|
||||
|
||||
dsi_ps_control(dsi);
|
||||
}
|
||||
|
||||
static void mtk_dsi_start(struct mtk_dsi *dsi)
|
||||
{
|
||||
writel(0, dsi->regs + DSI_START);
|
||||
writel(1, dsi->regs + DSI_START);
|
||||
}
|
||||
|
||||
static void mtk_dsi_poweroff(struct mtk_dsi *dsi)
|
||||
{
|
||||
if (WARN_ON(dsi->refcount == 0))
|
||||
return;
|
||||
|
||||
if (--dsi->refcount != 0)
|
||||
return;
|
||||
|
||||
dsi_lane0_ulp_mode_enter(dsi);
|
||||
dsi_clk_ulp_mode_enter(dsi);
|
||||
|
||||
mtk_dsi_disable(dsi);
|
||||
|
||||
clk_disable_unprepare(dsi->engine_clk);
|
||||
clk_disable_unprepare(dsi->digital_clk);
|
||||
|
||||
phy_power_off(dsi->phy);
|
||||
}
|
||||
|
||||
static void mtk_output_dsi_enable(struct mtk_dsi *dsi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (dsi->enabled)
|
||||
return;
|
||||
|
||||
if (dsi->panel) {
|
||||
if (drm_panel_prepare(dsi->panel)) {
|
||||
DRM_ERROR("failed to setup the panel\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mtk_dsi_poweron(dsi);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to power on dsi\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dsi_rxtx_control(dsi);
|
||||
|
||||
dsi_clk_ulp_mode_leave(dsi);
|
||||
dsi_lane0_ulp_mode_leave(dsi);
|
||||
dsi_clk_hs_mode(dsi, 0);
|
||||
dsi_set_mode(dsi);
|
||||
|
||||
dsi_ps_control_vact(dsi);
|
||||
dsi_config_vdo_timing(dsi);
|
||||
|
||||
dsi_set_mode(dsi);
|
||||
dsi_clk_hs_mode(dsi, 1);
|
||||
|
||||
mtk_dsi_start(dsi);
|
||||
|
||||
dsi->enabled = true;
|
||||
}
|
||||
|
||||
static void mtk_output_dsi_disable(struct mtk_dsi *dsi)
|
||||
{
|
||||
if (!dsi->enabled)
|
||||
return;
|
||||
|
||||
if (dsi->panel) {
|
||||
if (drm_panel_disable(dsi->panel)) {
|
||||
DRM_ERROR("failed to disable the panel\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mtk_dsi_poweroff(dsi);
|
||||
|
||||
dsi->enabled = false;
|
||||
}
|
||||
|
||||
static void mtk_dsi_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
drm_encoder_cleanup(encoder);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs mtk_dsi_encoder_funcs = {
|
||||
.destroy = mtk_dsi_encoder_destroy,
|
||||
};
|
||||
|
||||
static bool mtk_dsi_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mtk_dsi_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
struct mtk_dsi *dsi = encoder_to_dsi(encoder);
|
||||
|
||||
dsi->vm.pixelclock = adjusted->clock;
|
||||
dsi->vm.hactive = adjusted->hdisplay;
|
||||
dsi->vm.hback_porch = adjusted->htotal - adjusted->hsync_end;
|
||||
dsi->vm.hfront_porch = adjusted->hsync_start - adjusted->hdisplay;
|
||||
dsi->vm.hsync_len = adjusted->hsync_end - adjusted->hsync_start;
|
||||
|
||||
dsi->vm.vactive = adjusted->vdisplay;
|
||||
dsi->vm.vback_porch = adjusted->vtotal - adjusted->vsync_end;
|
||||
dsi->vm.vfront_porch = adjusted->vsync_start - adjusted->vdisplay;
|
||||
dsi->vm.vsync_len = adjusted->vsync_end - adjusted->vsync_start;
|
||||
}
|
||||
|
||||
static void mtk_dsi_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mtk_dsi *dsi = encoder_to_dsi(encoder);
|
||||
|
||||
mtk_output_dsi_disable(dsi);
|
||||
}
|
||||
|
||||
static void mtk_dsi_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mtk_dsi *dsi = encoder_to_dsi(encoder);
|
||||
|
||||
mtk_output_dsi_enable(dsi);
|
||||
}
|
||||
|
||||
static enum drm_connector_status mtk_dsi_connector_detect(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static int mtk_dsi_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct mtk_dsi *dsi = connector_to_dsi(connector);
|
||||
|
||||
return drm_panel_get_modes(dsi->panel);
|
||||
}
|
||||
|
||||
static struct drm_encoder *mtk_dsi_connector_best_encoder(
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct mtk_dsi *dsi = connector_to_dsi(connector);
|
||||
|
||||
return &dsi->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs mtk_dsi_encoder_helper_funcs = {
|
||||
.mode_fixup = mtk_dsi_encoder_mode_fixup,
|
||||
.mode_set = mtk_dsi_encoder_mode_set,
|
||||
.disable = mtk_dsi_encoder_disable,
|
||||
.enable = mtk_dsi_encoder_enable,
|
||||
};
|
||||
|
||||
static const struct drm_connector_funcs mtk_dsi_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.detect = mtk_dsi_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = drm_connector_cleanup,
|
||||
.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_bridge(struct drm_bridge *bridge,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!bridge)
|
||||
return -ENOENT;
|
||||
|
||||
encoder->bridge = bridge;
|
||||
bridge->encoder = encoder;
|
||||
ret = drm_bridge_attach(encoder->dev, bridge);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to attach bridge to drm\n");
|
||||
encoder->bridge = NULL;
|
||||
bridge->encoder = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_dsi_create_connector(struct drm_device *drm, struct mtk_dsi *dsi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = drm_connector_init(drm, &dsi->conn, &mtk_dsi_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_DSI);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to connector init to drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(&dsi->conn, &mtk_dsi_connector_helper_funcs);
|
||||
|
||||
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 attach panel to drm\n");
|
||||
goto err_connector_cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_connector_cleanup:
|
||||
drm_connector_cleanup(&dsi->conn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs,
|
||||
DRM_MODE_ENCODER_DSI, NULL);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to encoder init to drm\n");
|
||||
return ret;
|
||||
}
|
||||
drm_encoder_helper_add(&dsi->encoder, &mtk_dsi_encoder_helper_funcs);
|
||||
|
||||
/*
|
||||
* Currently display data paths are statically assigned to a crtc each.
|
||||
* crtc 0 is OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0
|
||||
*/
|
||||
dsi->encoder.possible_crtcs = 1;
|
||||
|
||||
/* If there's a bridge, attach to it and let it create the connector */
|
||||
ret = mtk_drm_attach_bridge(dsi->bridge, &dsi->encoder);
|
||||
if (ret) {
|
||||
/* Otherwise create our own connector and attach to a panel */
|
||||
ret = mtk_dsi_create_connector(drm, dsi);
|
||||
if (ret)
|
||||
goto err_encoder_cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_encoder_cleanup:
|
||||
drm_encoder_cleanup(&dsi->encoder);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_dsi_destroy_conn_enc(struct mtk_dsi *dsi)
|
||||
{
|
||||
drm_encoder_cleanup(&dsi->encoder);
|
||||
/* Skip connector cleanup if creation was delegated to the bridge */
|
||||
if (dsi->conn.dev) {
|
||||
drm_connector_unregister(&dsi->conn);
|
||||
drm_connector_cleanup(&dsi->conn);
|
||||
}
|
||||
}
|
||||
|
||||
static void mtk_dsi_ddp_start(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp);
|
||||
|
||||
mtk_dsi_poweron(dsi);
|
||||
}
|
||||
|
||||
static void mtk_dsi_ddp_stop(struct mtk_ddp_comp *comp)
|
||||
{
|
||||
struct mtk_dsi *dsi = container_of(comp, struct mtk_dsi, ddp_comp);
|
||||
|
||||
mtk_dsi_poweroff(dsi);
|
||||
}
|
||||
|
||||
static const struct mtk_ddp_comp_funcs mtk_dsi_funcs = {
|
||||
.start = mtk_dsi_ddp_start,
|
||||
.stop = mtk_dsi_ddp_stop,
|
||||
};
|
||||
|
||||
static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *device)
|
||||
{
|
||||
struct mtk_dsi *dsi = host_to_dsi(host);
|
||||
|
||||
dsi->lanes = device->lanes;
|
||||
dsi->format = device->format;
|
||||
dsi->mode_flags = device->mode_flags;
|
||||
|
||||
if (dsi->conn.dev)
|
||||
drm_helper_hpd_irq_event(dsi->conn.dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_dsi_host_detach(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *device)
|
||||
{
|
||||
struct mtk_dsi *dsi = host_to_dsi(host);
|
||||
|
||||
if (dsi->conn.dev)
|
||||
drm_helper_hpd_irq_event(dsi->conn.dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mipi_dsi_host_ops mtk_dsi_ops = {
|
||||
.attach = mtk_dsi_host_attach,
|
||||
.detach = mtk_dsi_host_detach,
|
||||
};
|
||||
|
||||
static int mtk_dsi_bind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct drm_device *drm = data;
|
||||
struct mtk_dsi *dsi = dev_get_drvdata(dev);
|
||||
|
||||
ret = mtk_ddp_comp_register(drm, &dsi->ddp_comp);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register component %s: %d\n",
|
||||
dev->of_node->full_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mipi_dsi_host_register(&dsi->host);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to register DSI host: %d\n", ret);
|
||||
goto err_ddp_comp_unregister;
|
||||
}
|
||||
|
||||
ret = mtk_dsi_create_conn_enc(drm, dsi);
|
||||
if (ret) {
|
||||
DRM_ERROR("Encoder create failed with %d\n", ret);
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
err_ddp_comp_unregister:
|
||||
mtk_ddp_comp_unregister(drm, &dsi->ddp_comp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_dsi_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct drm_device *drm = data;
|
||||
struct mtk_dsi *dsi = dev_get_drvdata(dev);
|
||||
|
||||
mtk_dsi_destroy_conn_enc(dsi);
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
mtk_ddp_comp_unregister(drm, &dsi->ddp_comp);
|
||||
}
|
||||
|
||||
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 comp_id;
|
||||
int ret;
|
||||
|
||||
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
|
||||
if (!dsi)
|
||||
return -ENOMEM;
|
||||
|
||||
dsi->host.ops = &mtk_dsi_ops;
|
||||
dsi->host.dev = dev;
|
||||
|
||||
endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
|
||||
if (endpoint) {
|
||||
remote_node = of_graph_get_remote_port_parent(endpoint);
|
||||
if (!remote_node) {
|
||||
dev_err(dev, "No panel connected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dsi->bridge = of_drm_find_bridge(remote_node);
|
||||
dsi->panel = of_drm_find_panel(remote_node);
|
||||
of_node_put(remote_node);
|
||||
if (!dsi->bridge && !dsi->panel) {
|
||||
dev_info(dev, "Waiting for bridge or panel driver\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
dsi->engine_clk = devm_clk_get(dev, "engine");
|
||||
if (IS_ERR(dsi->engine_clk)) {
|
||||
ret = PTR_ERR(dsi->engine_clk);
|
||||
dev_err(dev, "Failed to get engine clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsi->digital_clk = devm_clk_get(dev, "digital");
|
||||
if (IS_ERR(dsi->digital_clk)) {
|
||||
ret = PTR_ERR(dsi->digital_clk);
|
||||
dev_err(dev, "Failed to get digital clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsi->hs_clk = devm_clk_get(dev, "hs");
|
||||
if (IS_ERR(dsi->hs_clk)) {
|
||||
ret = PTR_ERR(dsi->hs_clk);
|
||||
dev_err(dev, "Failed to get hs clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dsi->regs = devm_ioremap_resource(dev, regs);
|
||||
if (IS_ERR(dsi->regs)) {
|
||||
ret = PTR_ERR(dsi->regs);
|
||||
dev_err(dev, "Failed to ioremap memory: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsi->phy = devm_phy_get(dev, "dphy");
|
||||
if (IS_ERR(dsi->phy)) {
|
||||
ret = PTR_ERR(dsi->phy);
|
||||
dev_err(dev, "Failed to get MIPI-DPHY: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DSI);
|
||||
if (comp_id < 0) {
|
||||
dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
|
||||
return comp_id;
|
||||
}
|
||||
|
||||
ret = mtk_ddp_comp_init(dev, dev->of_node, &dsi->ddp_comp, comp_id,
|
||||
&mtk_dsi_funcs);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize component: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dsi);
|
||||
|
||||
return component_add(&pdev->dev, &mtk_dsi_component_ops);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
|
||||
#define MIPITX_DSI_CON 0x00
|
||||
#define RG_DSI_LDOCORE_EN BIT(0)
|
||||
#define RG_DSI_CKG_LDOOUT_EN BIT(1)
|
||||
#define RG_DSI_BCLK_SEL (3 << 2)
|
||||
#define RG_DSI_LD_IDX_SEL (7 << 4)
|
||||
#define RG_DSI_PHYCLK_SEL (2 << 8)
|
||||
#define RG_DSI_DSICLK_FREQ_SEL BIT(10)
|
||||
#define RG_DSI_LPTX_CLMP_EN BIT(11)
|
||||
|
||||
#define MIPITX_DSI_CLOCK_LANE 0x04
|
||||
#define MIPITX_DSI_DATA_LANE0 0x08
|
||||
#define MIPITX_DSI_DATA_LANE1 0x0c
|
||||
#define MIPITX_DSI_DATA_LANE2 0x10
|
||||
#define MIPITX_DSI_DATA_LANE3 0x14
|
||||
#define RG_DSI_LNTx_LDOOUT_EN BIT(0)
|
||||
#define RG_DSI_LNTx_CKLANE_EN BIT(1)
|
||||
#define RG_DSI_LNTx_LPTX_IPLUS1 BIT(2)
|
||||
#define RG_DSI_LNTx_LPTX_IPLUS2 BIT(3)
|
||||
#define RG_DSI_LNTx_LPTX_IMINUS BIT(4)
|
||||
#define RG_DSI_LNTx_LPCD_IPLUS BIT(5)
|
||||
#define RG_DSI_LNTx_LPCD_IMINUS BIT(6)
|
||||
#define RG_DSI_LNTx_RT_CODE (0xf << 8)
|
||||
|
||||
#define MIPITX_DSI_TOP_CON 0x40
|
||||
#define RG_DSI_LNT_INTR_EN BIT(0)
|
||||
#define RG_DSI_LNT_HS_BIAS_EN BIT(1)
|
||||
#define RG_DSI_LNT_IMP_CAL_EN BIT(2)
|
||||
#define RG_DSI_LNT_TESTMODE_EN BIT(3)
|
||||
#define RG_DSI_LNT_IMP_CAL_CODE (0xf << 4)
|
||||
#define RG_DSI_LNT_AIO_SEL (7 << 8)
|
||||
#define RG_DSI_PAD_TIE_LOW_EN BIT(11)
|
||||
#define RG_DSI_DEBUG_INPUT_EN BIT(12)
|
||||
#define RG_DSI_PRESERVE (7 << 13)
|
||||
|
||||
#define MIPITX_DSI_BG_CON 0x44
|
||||
#define RG_DSI_BG_CORE_EN BIT(0)
|
||||
#define RG_DSI_BG_CKEN BIT(1)
|
||||
#define RG_DSI_BG_DIV (0x3 << 2)
|
||||
#define RG_DSI_BG_FAST_CHARGE BIT(4)
|
||||
#define RG_DSI_VOUT_MSK (0x3ffff << 5)
|
||||
#define RG_DSI_V12_SEL (7 << 5)
|
||||
#define RG_DSI_V10_SEL (7 << 8)
|
||||
#define RG_DSI_V072_SEL (7 << 11)
|
||||
#define RG_DSI_V04_SEL (7 << 14)
|
||||
#define RG_DSI_V032_SEL (7 << 17)
|
||||
#define RG_DSI_V02_SEL (7 << 20)
|
||||
#define RG_DSI_BG_R1_TRIM (0xf << 24)
|
||||
#define RG_DSI_BG_R2_TRIM (0xf << 28)
|
||||
|
||||
#define MIPITX_DSI_PLL_CON0 0x50
|
||||
#define RG_DSI_MPPLL_PLL_EN BIT(0)
|
||||
#define RG_DSI_MPPLL_DIV_MSK (0x1ff << 1)
|
||||
#define RG_DSI_MPPLL_PREDIV (3 << 1)
|
||||
#define RG_DSI_MPPLL_TXDIV0 (3 << 3)
|
||||
#define RG_DSI_MPPLL_TXDIV1 (3 << 5)
|
||||
#define RG_DSI_MPPLL_POSDIV (7 << 7)
|
||||
#define RG_DSI_MPPLL_MONVC_EN BIT(10)
|
||||
#define RG_DSI_MPPLL_MONREF_EN BIT(11)
|
||||
#define RG_DSI_MPPLL_VOD_EN BIT(12)
|
||||
|
||||
#define MIPITX_DSI_PLL_CON1 0x54
|
||||
#define RG_DSI_MPPLL_SDM_FRA_EN BIT(0)
|
||||
#define RG_DSI_MPPLL_SDM_SSC_PH_INIT BIT(1)
|
||||
#define RG_DSI_MPPLL_SDM_SSC_EN BIT(2)
|
||||
#define RG_DSI_MPPLL_SDM_SSC_PRD (0xffff << 16)
|
||||
|
||||
#define MIPITX_DSI_PLL_CON2 0x58
|
||||
|
||||
#define MIPITX_DSI_PLL_PWR 0x68
|
||||
#define RG_DSI_MPPLL_SDM_PWR_ON BIT(0)
|
||||
#define RG_DSI_MPPLL_SDM_ISO_EN BIT(1)
|
||||
#define RG_DSI_MPPLL_SDM_PWR_ACK BIT(8)
|
||||
|
||||
#define MIPITX_DSI_SW_CTRL 0x80
|
||||
#define SW_CTRL_EN BIT(0)
|
||||
|
||||
#define MIPITX_DSI_SW_CTRL_CON0 0x84
|
||||
#define SW_LNTC_LPTX_PRE_OE BIT(0)
|
||||
#define SW_LNTC_LPTX_OE BIT(1)
|
||||
#define SW_LNTC_LPTX_P BIT(2)
|
||||
#define SW_LNTC_LPTX_N BIT(3)
|
||||
#define SW_LNTC_HSTX_PRE_OE BIT(4)
|
||||
#define SW_LNTC_HSTX_OE BIT(5)
|
||||
#define SW_LNTC_HSTX_ZEROCLK BIT(6)
|
||||
#define SW_LNT0_LPTX_PRE_OE BIT(7)
|
||||
#define SW_LNT0_LPTX_OE BIT(8)
|
||||
#define SW_LNT0_LPTX_P BIT(9)
|
||||
#define SW_LNT0_LPTX_N BIT(10)
|
||||
#define SW_LNT0_HSTX_PRE_OE BIT(11)
|
||||
#define SW_LNT0_HSTX_OE BIT(12)
|
||||
#define SW_LNT0_LPRX_EN BIT(13)
|
||||
#define SW_LNT1_LPTX_PRE_OE BIT(14)
|
||||
#define SW_LNT1_LPTX_OE BIT(15)
|
||||
#define SW_LNT1_LPTX_P BIT(16)
|
||||
#define SW_LNT1_LPTX_N BIT(17)
|
||||
#define SW_LNT1_HSTX_PRE_OE BIT(18)
|
||||
#define SW_LNT1_HSTX_OE BIT(19)
|
||||
#define SW_LNT2_LPTX_PRE_OE BIT(20)
|
||||
#define SW_LNT2_LPTX_OE BIT(21)
|
||||
#define SW_LNT2_LPTX_P BIT(22)
|
||||
#define SW_LNT2_LPTX_N BIT(23)
|
||||
#define SW_LNT2_HSTX_PRE_OE BIT(24)
|
||||
#define SW_LNT2_HSTX_OE BIT(25)
|
||||
|
||||
struct mtk_mipi_tx {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
unsigned int data_rate;
|
||||
struct clk_hw pll_hw;
|
||||
struct clk *pll;
|
||||
};
|
||||
|
||||
static inline struct mtk_mipi_tx *mtk_mipi_tx_from_clk_hw(struct clk_hw *hw)
|
||||
{
|
||||
return container_of(hw, struct mtk_mipi_tx, pll_hw);
|
||||
}
|
||||
|
||||
static void mtk_mipi_tx_clear_bits(struct mtk_mipi_tx *mipi_tx, u32 offset,
|
||||
u32 bits)
|
||||
{
|
||||
u32 temp = readl(mipi_tx->regs + offset);
|
||||
|
||||
writel(temp & ~bits, mipi_tx->regs + offset);
|
||||
}
|
||||
|
||||
static void mtk_mipi_tx_set_bits(struct mtk_mipi_tx *mipi_tx, u32 offset,
|
||||
u32 bits)
|
||||
{
|
||||
u32 temp = readl(mipi_tx->regs + offset);
|
||||
|
||||
writel(temp | bits, mipi_tx->regs + offset);
|
||||
}
|
||||
|
||||
static void mtk_mipi_tx_update_bits(struct mtk_mipi_tx *mipi_tx, u32 offset,
|
||||
u32 mask, u32 data)
|
||||
{
|
||||
u32 temp = readl(mipi_tx->regs + offset);
|
||||
|
||||
writel((temp & ~mask) | (data & mask), mipi_tx->regs + offset);
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_pll_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
|
||||
unsigned int txdiv, txdiv0, txdiv1;
|
||||
u64 pcw;
|
||||
|
||||
dev_dbg(mipi_tx->dev, "prepare: %u Hz\n", mipi_tx->data_rate);
|
||||
|
||||
if (mipi_tx->data_rate >= 500000000) {
|
||||
txdiv = 1;
|
||||
txdiv0 = 0;
|
||||
txdiv1 = 0;
|
||||
} else if (mipi_tx->data_rate >= 250000000) {
|
||||
txdiv = 2;
|
||||
txdiv0 = 1;
|
||||
txdiv1 = 0;
|
||||
} else if (mipi_tx->data_rate >= 125000000) {
|
||||
txdiv = 4;
|
||||
txdiv0 = 2;
|
||||
txdiv1 = 0;
|
||||
} else if (mipi_tx->data_rate > 62000000) {
|
||||
txdiv = 8;
|
||||
txdiv0 = 2;
|
||||
txdiv1 = 1;
|
||||
} else if (mipi_tx->data_rate >= 50000000) {
|
||||
txdiv = 16;
|
||||
txdiv0 = 2;
|
||||
txdiv1 = 2;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_BG_CON,
|
||||
RG_DSI_VOUT_MSK |
|
||||
RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN,
|
||||
(4 << 20) | (4 << 17) | (4 << 14) |
|
||||
(4 << 11) | (4 << 8) | (4 << 5) |
|
||||
RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN);
|
||||
|
||||
usleep_range(30, 100);
|
||||
|
||||
mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_TOP_CON,
|
||||
RG_DSI_LNT_IMP_CAL_CODE | RG_DSI_LNT_HS_BIAS_EN,
|
||||
(8 << 4) | RG_DSI_LNT_HS_BIAS_EN);
|
||||
|
||||
mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_CON,
|
||||
RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN);
|
||||
|
||||
mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_PWR,
|
||||
RG_DSI_MPPLL_SDM_PWR_ON |
|
||||
RG_DSI_MPPLL_SDM_ISO_EN,
|
||||
RG_DSI_MPPLL_SDM_PWR_ON);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON0,
|
||||
RG_DSI_MPPLL_PLL_EN);
|
||||
|
||||
mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_CON0,
|
||||
RG_DSI_MPPLL_TXDIV0 | RG_DSI_MPPLL_TXDIV1 |
|
||||
RG_DSI_MPPLL_PREDIV,
|
||||
(txdiv0 << 3) | (txdiv1 << 5));
|
||||
|
||||
/*
|
||||
* PLL PCW config
|
||||
* PCW bit 24~30 = integer part of pcw
|
||||
* PCW bit 0~23 = fractional part of pcw
|
||||
* pcw = data_Rate*4*txdiv/(Ref_clk*2);
|
||||
* Post DIV =4, so need data_Rate*4
|
||||
* Ref_clk is 26MHz
|
||||
*/
|
||||
pcw = div_u64(((u64)mipi_tx->data_rate * 2 * txdiv) << 24,
|
||||
26000000);
|
||||
writel(pcw, mipi_tx->regs + MIPITX_DSI_PLL_CON2);
|
||||
|
||||
mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_PLL_CON1,
|
||||
RG_DSI_MPPLL_SDM_FRA_EN);
|
||||
|
||||
mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_PLL_CON0, RG_DSI_MPPLL_PLL_EN);
|
||||
|
||||
usleep_range(20, 100);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON1,
|
||||
RG_DSI_MPPLL_SDM_SSC_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_mipi_tx_pll_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
|
||||
|
||||
dev_dbg(mipi_tx->dev, "unprepare\n");
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON0,
|
||||
RG_DSI_MPPLL_PLL_EN);
|
||||
|
||||
mtk_mipi_tx_update_bits(mipi_tx, MIPITX_DSI_PLL_PWR,
|
||||
RG_DSI_MPPLL_SDM_ISO_EN |
|
||||
RG_DSI_MPPLL_SDM_PWR_ON,
|
||||
RG_DSI_MPPLL_SDM_ISO_EN);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_TOP_CON,
|
||||
RG_DSI_LNT_HS_BIAS_EN);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_CON,
|
||||
RG_DSI_CKG_LDOOUT_EN | RG_DSI_LDOCORE_EN);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_BG_CON,
|
||||
RG_DSI_BG_CKEN | RG_DSI_BG_CORE_EN);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_PLL_CON0,
|
||||
RG_DSI_MPPLL_DIV_MSK);
|
||||
}
|
||||
|
||||
static long mtk_mipi_tx_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *prate)
|
||||
{
|
||||
return clamp_val(rate, 50000000, 1250000000);
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
|
||||
|
||||
dev_dbg(mipi_tx->dev, "set rate: %lu Hz\n", rate);
|
||||
|
||||
mipi_tx->data_rate = rate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long mtk_mipi_tx_pll_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = mtk_mipi_tx_from_clk_hw(hw);
|
||||
|
||||
return mipi_tx->data_rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops mtk_mipi_tx_pll_ops = {
|
||||
.prepare = mtk_mipi_tx_pll_prepare,
|
||||
.unprepare = mtk_mipi_tx_pll_unprepare,
|
||||
.round_rate = mtk_mipi_tx_pll_round_rate,
|
||||
.set_rate = mtk_mipi_tx_pll_set_rate,
|
||||
.recalc_rate = mtk_mipi_tx_pll_recalc_rate,
|
||||
};
|
||||
|
||||
static int mtk_mipi_tx_power_on_signal(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
|
||||
unsigned int reg;
|
||||
|
||||
for (reg = MIPITX_DSI_CLOCK_LANE;
|
||||
reg <= MIPITX_DSI_DATA_LANE3; reg += 4)
|
||||
mtk_mipi_tx_set_bits(mipi_tx, reg, RG_DSI_LNTx_LDOOUT_EN);
|
||||
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, MIPITX_DSI_TOP_CON,
|
||||
RG_DSI_PAD_TIE_LOW_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_power_on(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
|
||||
int ret;
|
||||
|
||||
/* Power up core and enable PLL */
|
||||
ret = clk_prepare_enable(mipi_tx->pll);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Enable DSI Lane LDO outputs, disable pad tie low */
|
||||
mtk_mipi_tx_power_on_signal(phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_mipi_tx_power_off_signal(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
|
||||
unsigned int reg;
|
||||
|
||||
mtk_mipi_tx_set_bits(mipi_tx, MIPITX_DSI_TOP_CON,
|
||||
RG_DSI_PAD_TIE_LOW_EN);
|
||||
|
||||
for (reg = MIPITX_DSI_CLOCK_LANE;
|
||||
reg <= MIPITX_DSI_DATA_LANE3; reg += 4)
|
||||
mtk_mipi_tx_clear_bits(mipi_tx, reg, RG_DSI_LNTx_LDOOUT_EN);
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_power_off(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = phy_get_drvdata(phy);
|
||||
|
||||
/* Enable pad tie low, disable DSI Lane LDO outputs */
|
||||
mtk_mipi_tx_power_off_signal(phy);
|
||||
|
||||
/* Disable PLL and power down core */
|
||||
clk_disable_unprepare(mipi_tx->pll);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct phy_ops mtk_mipi_tx_ops = {
|
||||
.power_on = mtk_mipi_tx_power_on,
|
||||
.power_off = mtk_mipi_tx_power_off,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int mtk_mipi_tx_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mtk_mipi_tx *mipi_tx;
|
||||
struct resource *mem;
|
||||
struct clk *ref_clk;
|
||||
const char *ref_clk_name;
|
||||
struct clk_init_data clk_init = {
|
||||
.ops = &mtk_mipi_tx_pll_ops,
|
||||
.num_parents = 1,
|
||||
.parent_names = (const char * const *)&ref_clk_name,
|
||||
.flags = CLK_SET_RATE_GATE,
|
||||
};
|
||||
struct phy *phy;
|
||||
struct phy_provider *phy_provider;
|
||||
int ret;
|
||||
|
||||
mipi_tx = devm_kzalloc(dev, sizeof(*mipi_tx), GFP_KERNEL);
|
||||
if (!mipi_tx)
|
||||
return -ENOMEM;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
mipi_tx->regs = devm_ioremap_resource(dev, mem);
|
||||
if (IS_ERR(mipi_tx->regs)) {
|
||||
ret = PTR_ERR(mipi_tx->regs);
|
||||
dev_err(dev, "Failed to get memory resource: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ref_clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(ref_clk)) {
|
||||
ret = PTR_ERR(ref_clk);
|
||||
dev_err(dev, "Failed to get reference clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ref_clk_name = __clk_get_name(ref_clk);
|
||||
|
||||
ret = of_property_read_string(dev->of_node, "clock-output-names",
|
||||
&clk_init.name);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read clock-output-names: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mipi_tx->pll_hw.init = &clk_init;
|
||||
mipi_tx->pll = devm_clk_register(dev, &mipi_tx->pll_hw);
|
||||
if (IS_ERR(mipi_tx->pll)) {
|
||||
ret = PTR_ERR(mipi_tx->pll);
|
||||
dev_err(dev, "Failed to register PLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy = devm_phy_create(dev, NULL, &mtk_mipi_tx_ops);
|
||||
if (IS_ERR(phy)) {
|
||||
ret = PTR_ERR(phy);
|
||||
dev_err(dev, "Failed to create MIPI D-PHY: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
phy_set_drvdata(phy, mipi_tx);
|
||||
|
||||
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
||||
if (IS_ERR(phy)) {
|
||||
ret = PTR_ERR(phy_provider);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mipi_tx->dev = dev;
|
||||
|
||||
return of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
|
||||
mipi_tx->pll);
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_remove(struct platform_device *pdev)
|
||||
{
|
||||
of_clk_del_provider(pdev->dev.of_node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mtk_mipi_tx_match[] = {
|
||||
{ .compatible = "mediatek,mt8173-mipi-tx", },
|
||||
{},
|
||||
};
|
||||
|
||||
struct platform_driver mtk_mipi_tx_driver = {
|
||||
.probe = mtk_mipi_tx_probe,
|
||||
.remove = mtk_mipi_tx_remove,
|
||||
.driver = {
|
||||
.name = "mediatek-mipi-tx",
|
||||
.of_match_table = mtk_mipi_tx_match,
|
||||
},
|
||||
};
|
|
@ -91,6 +91,7 @@ int mtk_smi_larb_get(struct device *larbdev)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtk_smi_larb_get);
|
||||
|
||||
void mtk_smi_larb_put(struct device *larbdev)
|
||||
{
|
||||
|
@ -106,6 +107,7 @@ void mtk_smi_larb_put(struct device *larbdev)
|
|||
mtk_smi_disable(&larb->smi);
|
||||
mtk_smi_disable(common);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtk_smi_larb_put);
|
||||
|
||||
static int
|
||||
mtk_smi_larb_bind(struct device *dev, struct device *master, void *data)
|
||||
|
|
|
@ -176,7 +176,8 @@
|
|||
#define CLK_APMIXED_LVDSPLL 13
|
||||
#define CLK_APMIXED_MSDCPLL2 14
|
||||
#define CLK_APMIXED_REF2USB_TX 15
|
||||
#define CLK_APMIXED_NR_CLK 16
|
||||
#define CLK_APMIXED_HDMI_REF 16
|
||||
#define CLK_APMIXED_NR_CLK 17
|
||||
|
||||
/* INFRA_SYS */
|
||||
|
||||
|
|
Loading…
Reference in New Issue