Commit 2e726dc4 authored by Dave Airlie's avatar Dave Airlie

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
parents bafb86f5 ac4b1280
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>;
};
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>;
};
};
};
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/
......
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.
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
/*
* 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,
},
};
/*
* 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,
},
};
/*
* 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,
},
};
/*
* 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 */
/*
* 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;
}
/*
* 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 */
/*
* 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,
},
};
/*
* 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 */
/*
* 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;
}
/*
* 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 */
/*
* 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");
/*
* 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 */
/*
* 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);
}
/*
* 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 */
/*
* 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);
}
/*
* 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
/*
* 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;
}
/*
* 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
/*
* 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,
},
};
/*
* 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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment