aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Medhurst <tixy@linaro.org>2017-05-02 14:31:58 +0100
committerJon Medhurst <tixy@linaro.org>2017-05-02 14:31:58 +0100
commit2d7045c526bfb1373a3209b5bce36ada7fc3306d (patch)
tree1d335eb5caca008db3c0b820bc9caf28e92718da
parent8c24cc7f35fa9615ca2a578fcb0963947adb273b (diff)
parent8568ef1bc14639f1d50f894fc4f6ea4960f18609 (diff)
Merge branch 'latest-armlt-hdlcd' into latest-armlt
-rw-r--r--Documentation/devicetree/bindings/display/virtual-encoder.txt74
-rw-r--r--arch/arm/boot/dts/vexpress-v2m-rs1.dtsi5
-rw-r--r--arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts30
-rw-r--r--arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts30
-rw-r--r--arch/arm/boot/dts/vexpress-v2p-ca5s.dts30
-rw-r--r--arch/arm64/boot/dts/arm/juno-base.dtsi48
-rw-r--r--drivers/gpu/drm/Kconfig8
-rw-r--r--drivers/gpu/drm/Makefile3
-rw-r--r--drivers/gpu/drm/arm/Makefile2
-rw-r--r--drivers/gpu/drm/arm/hdlcd_crtc.c42
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.c93
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.h6
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb_helper.c857
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb_helper.h54
-rw-r--r--drivers/gpu/drm/drm_virtual_encoder.c299
-rw-r--r--drivers/gpu/drm/i2c/Kconfig3
-rw-r--r--drivers/gpu/drm/i2c/Makefile2
-rw-r--r--drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c264
-rw-r--r--include/drm/drm_crtc.h2
-rw-r--r--linaro/configs/vexpress.conf10
-rw-r--r--linaro/configs/vexpress64.conf23
21 files changed, 1850 insertions, 35 deletions
diff --git a/Documentation/devicetree/bindings/display/virtual-encoder.txt b/Documentation/devicetree/bindings/display/virtual-encoder.txt
new file mode 100644
index 000000000000..3a9b8221c3ec
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/virtual-encoder.txt
@@ -0,0 +1,74 @@
+DRM Virtual Encoder
+
+The DRM Virtual Encoder is a component-based basic encoder that fetches
+the display timings information from the device tree and "discovers" a
+DRM output with the given data. It is helpful in a simulated environment
+where there is no actual hardware to be probed and the configuration of
+the display happens outside the kernel world.
+
+Required properties:
+ - compatible: should be "drm,virtual-encoder"
+
+Required sub-nodes:
+ - display-timings: node describing the virtual output timings information,
+ as specified in panel/display-timing.txt file.
+ - port: the input port connection as modelled using the OF graph bindings
+ specified in Documentation/devicetree/bindings/graph.txt
+
+
+Example:
+
+/ {
+ ...
+
+ vencoder {
+ compatible = "drm,virtual-encoder";
+ display-timings {
+ native-mode = <&timing1>;
+ timing0: timing@0 {
+ /* 640x480 framebuffer */
+ clock-frequency = <23750>;
+ hactive = <640>;
+ vactive = <480>;
+ hfront-porch = <48>;
+ hback-porch = <16>;
+ hsync-len = <96>;
+ vfront-porch = <33>;
+ vback-porch = <9>;
+ vsync-len = <3>;
+ };
+ timing1: timing@1 {
+ /* 1280x720 framebuffer */
+ clock-frequency = <74440000>;
+ hactive = <1280>;
+ vactive = <720>;
+ hfront-porch = <56>;
+ hback-porch = <192>;
+ hsync-len = <136>;
+ vfront-porch = <1>;
+ vback-porch = <22>;
+ vsync-len = <3>;
+ };
+ };
+
+ port {
+ vencoder_in: endpoint {
+ remote-endpoint = <&driver_out>;
+ };
+ };
+ };
+
+ drm_driver: driver@f00bad {
+ ...
+
+ port {
+ driver_out: endpoint {
+ remote-endpoint = <&vencoder_in>;
+ };
+ };
+
+ ...
+ };
+
+ ...
+};
diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi
index 3086efacd00e..54939131af62 100644
--- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi
+++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi
@@ -222,6 +222,9 @@
dvi-transmitter@39 {
compatible = "sil,sii9022-tpi", "sil,sii9022";
reg = <0x39>;
+
+ v2m_dvi_port: port {
+ };
};
dvi-transmitter@60 {
@@ -245,7 +248,7 @@
reg-shift = <2>;
};
- clcd@1f0000 {
+ v2m_clcd: clcd@1f0000 {
compatible = "arm,pl111", "arm,primecell";
reg = <0x1f0000 0x1000>;
interrupt-names = "combined";
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts
index 15f4fd3f4695..f8f2d391d303 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts
@@ -57,6 +57,22 @@
interrupts = <0 85 4>;
clocks = <&hdlcd_clk>;
clock-names = "pxlclk";
+
+ port {
+ hdlcd0_output: endpoint@0 {
+ remote-endpoint = <&sii9022_0_input>;
+ };
+ };
+ };
+
+ hdmi0: connector@0 {
+ compatible = "hdmi-connector";
+ type = "a";
+ port {
+ hdmi0_connector_output: endpoint {
+ remote-endpoint = <&sii9022_0_output>;
+ };
+ };
};
memory-controller@2b0a0000 {
@@ -294,3 +310,17 @@
<0 3 &gic 0 39 4>;
};
};
+
+&v2m_clcd {
+ status = "disabled";
+};
+
+&v2m_dvi_port {
+ sii9022_0_input: endpoint@0 {
+ remote-endpoint = <&hdlcd0_output>;
+ };
+
+ sii9022_0_output: endpoint@1 {
+ remote-endpoint = <&hdmi0_connector_output>;
+ };
+};
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
index bd107c5a0226..7773d7953322 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
@@ -116,6 +116,22 @@
interrupts = <0 85 4>;
clocks = <&hdlcd_clk>;
clock-names = "pxlclk";
+
+ port {
+ hdlcd0_output: endpoint@0 {
+ remote-endpoint = <&sii9022_0_input>;
+ };
+ };
+ };
+
+ hdmi0: connector@0 {
+ compatible = "hdmi-connector";
+ type = "a";
+ port {
+ hdmi0_connector_output: endpoint {
+ remote-endpoint = <&sii9022_0_output>;
+ };
+ };
};
memory-controller@2b0a0000 {
@@ -657,3 +673,17 @@
<0 3 &gic 0 39 4>;
};
};
+
+&v2m_clcd {
+ status = "disabled";
+};
+
+&v2m_dvi_port {
+ sii9022_0_input: endpoint@0 {
+ remote-endpoint = <&hdlcd0_output>;
+ };
+
+ sii9022_0_output: endpoint@1 {
+ remote-endpoint = <&hdmi0_connector_output>;
+ };
+};
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts
index 1acecaf4b13d..6fad3c315c76 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts
@@ -59,6 +59,22 @@
interrupts = <0 85 4>;
clocks = <&hdlcd_clk>;
clock-names = "pxlclk";
+
+ port {
+ hdlcd0_output: endpoint@0 {
+ remote-endpoint = <&sii9022_0_input>;
+ };
+ };
+ };
+
+ hdmi0: connector@0 {
+ compatible = "hdmi-connector";
+ type = "a";
+ port {
+ hdmi0_connector_output: endpoint {
+ remote-endpoint = <&sii9022_0_output>;
+ };
+ };
};
memory-controller@2a150000 {
@@ -264,3 +280,17 @@
<0 3 &gic 0 39 4>;
};
};
+
+&v2m_clcd {
+ status = "disabled";
+};
+
+&v2m_dvi_port {
+ sii9022_0_input: endpoint@0 {
+ remote-endpoint = <&hdlcd0_output>;
+ };
+
+ sii9022_0_output: endpoint@1 {
+ remote-endpoint = <&hdmi0_connector_output>;
+ };
+};
diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi b/arch/arm64/boot/dts/arm/juno-base.dtsi
index a7b63da05400..e56b772151e4 100644
--- a/arch/arm64/boot/dts/arm/juno-base.dtsi
+++ b/arch/arm64/boot/dts/arm/juno-base.dtsi
@@ -1,4 +1,5 @@
#include "juno-clocks.dtsi"
+#include <dt-bindings/display/tda998x.h>
/ {
/*
@@ -469,8 +470,8 @@
scpi_clk: scpi-clk {
compatible = "arm,scpi-variable-clocks";
#clock-cells = <1>;
- clock-indices = <3>;
- clock-output-names = "pxlclk";
+ clock-indices = <3>, <4>, <5>;
+ clock-output-names = "pxlclk", "pxlclk1", "i2sclk";
};
};
@@ -570,7 +571,7 @@
status = "disabled";
};
- dma@7ff00000 {
+ dma0: dma@7ff00000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0x0 0x7ff00000 0 0x1000>;
#dma-cells = <1>;
@@ -597,7 +598,7 @@
clocks = <&soc_faxiclk>;
clock-names = "apb_pclk";
};
-
+/*
hdlcd@7ff50000 {
compatible = "arm,hdlcd";
reg = <0 0x7ff50000 0 0x1000>;
@@ -612,7 +613,7 @@
};
};
};
-
+*/
hdlcd@7ff60000 {
compatible = "arm,hdlcd";
reg = <0 0x7ff60000 0 0x1000>;
@@ -646,16 +647,18 @@
i2c-sda-hold-time-ns = <500>;
clocks = <&soc_smc50mhz>;
- hdmi-transmitter@70 {
+ hdmi_transmitter0: hdmi-transmitter@70 {
compatible = "nxp,tda998x";
reg = <0x70>;
+ #sound-dai-cells = <0>;
+ audio-ports = <TDA998x_I2S 0x03>;
port {
tda998x_0_input: tda998x-0-endpoint {
remote-endpoint = <&hdlcd0_output>;
};
};
};
-
+/*
hdmi-transmitter@71 {
compatible = "nxp,tda998x";
reg = <0x71>;
@@ -665,7 +668,7 @@
};
};
};
- };
+*/ };
ohci@7ffb0000 {
compatible = "generic-ohci";
@@ -699,6 +702,35 @@
<0x00000008 0x80000000 0x1 0x80000000>;
};
+ soc_i2s: i2s@7ff90000 {
+ compatible = "snps,designware-i2s";
+ reg = <0x0 0x7ff90000 0x0 0x1000>;
+ clocks = <&scpi_clk 5>, <&soc_refclk100mhz>;
+ clock-names = "i2sclk", "apb_pclk";
+ #sound-dai-cells = <0>;
+ dmas = <&dma0 5>;
+ dma-names = "tx";
+ };
+
+ hdmi_audio: hdmi_audio@0 {
+ compatible = "linux,hdmi-audio";
+ #sound-dai-cells = <0>;
+ };
+
+ sound {
+ compatible = "simple-audio-card";
+
+ simple-audio-card,format = "i2s";
+
+ simple-audio-card,cpu {
+ sound-dai = <&soc_i2s>;
+ };
+
+ simple-audio-card,codec {
+ sound-dai = <&hdmi_transmitter0>;
+ };
+ };
+
smb@08000000 {
compatible = "simple-bus";
#address-cells = <2>;
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 88e01e08e279..8364775bc527 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -194,6 +194,14 @@ config DRM_VGEM
as used by Mesa's software renderer for enhanced performance.
If M is selected the module will be called vgem.
+config DRM_VIRT_ENCODER
+ tristate "Virtual OF-based encoder"
+ depends on DRM && OF
+ help
+ Choose this option to get a virtual encoder and its associated
+ connector that will use the device tree to read the display
+ timings information. If M is selected the module will be called
+ drm_vencoder.
source "drivers/gpu/drm/exynos/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 3ee95793d122..5d64a148abcf 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -43,6 +43,9 @@ obj-$(CONFIG_DRM_DEBUG_MM_SELFTEST) += selftests/
CFLAGS_drm_trace_points.o := -I$(src)
+drm_vencoder-y := drm_virtual_encoder.o
+obj-$(CONFIG_DRM_VIRT_ENCODER) += drm_vencoder.o
+
obj-$(CONFIG_DRM) += drm.o
obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
obj-$(CONFIG_DRM_ARM) += arm/
diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile
index bb8b158ff90d..6e09ee75681e 100644
--- a/drivers/gpu/drm/arm/Makefile
+++ b/drivers/gpu/drm/arm/Makefile
@@ -1,4 +1,4 @@
-hdlcd-y := hdlcd_drv.o hdlcd_crtc.o
+hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_fb_helper.o hdlcd_fb_helper.o
obj-$(CONFIG_DRM_HDLCD) += hdlcd.o
mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o
obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o
diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c
index 20ebfb4fbdfa..2da637d56c2d 100644
--- a/drivers/gpu/drm/arm/hdlcd_crtc.c
+++ b/drivers/gpu/drm/arm/hdlcd_crtc.c
@@ -14,7 +14,7 @@
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
-#include <drm/drm_fb_cma_helper.h>
+#include "hdlcd_fb_helper.h"
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_plane_helper.h>
@@ -62,6 +62,8 @@ static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc)
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
const struct drm_framebuffer *fb = crtc->primary->state->fb;
uint32_t pixel_format;
+ bool swap_red_blue = false;
+ u32 hbi;
struct simplefb_format *format = NULL;
int i;
@@ -89,16 +91,36 @@ static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc)
* pixel is outside the visible frame area or when there is a
* buffer underrun.
*/
- hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset |
+ if (of_property_read_u32(of_root, "arm,hbi", &hbi) == 0) {
+ /*
+ * This is a hack to swap read and blue when building for some
+ * Versatile Express CoreTiles because they seem to be wired up
+ * differently.
+ */
+ if (hbi == 0x249) /* TC2 */
+ swap_red_blue = true;
+ }
+ if(!swap_red_blue) {
+ hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset |
#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN
- 0x00ff0000 | /* show underruns in red */
+ 0x00ff0000 | /* show underruns in red */
#endif
- ((format->red.length & 0xf) << 8));
- hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset |
- ((format->green.length & 0xf) << 8));
- hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset |
- ((format->blue.length & 0xf) << 8));
-
+ ((format->red.length & 0xf) << 8));
+ hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset |
+ ((format->green.length & 0xf) << 8));
+ hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset |
+ ((format->blue.length & 0xf) << 8));
+ } else {
+ hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->red.offset |
+#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN
+ 0x00ff0000 | /* show underruns in red */
+#endif
+ ((format->red.length & 0xf) << 8));
+ hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset |
+ ((format->green.length & 0xf) << 8));
+ hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->blue.offset |
+ ((format->blue.length & 0xf) << 8));
+ }
return 0;
}
@@ -234,7 +256,7 @@ static void hdlcd_plane_atomic_update(struct drm_plane *plane,
src_h = plane->state->src_h >> 16;
dest_w = plane->state->crtc_w;
dest_h = plane->state->crtc_h;
- gem = drm_fb_cma_get_gem_obj(fb, 0);
+ gem = hdlcd_fb_get_gem_obj(fb, 0);
scanout_start = gem->paddr + fb->offsets[0] +
plane->state->crtc_y * fb->pitches[0] +
plane->state->crtc_x *
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c
index 4ce4f970920b..0a2df32f7ba4 100644
--- a/drivers/gpu/drm/arm/hdlcd_drv.c
+++ b/drivers/gpu/drm/arm/hdlcd_drv.c
@@ -23,7 +23,7 @@
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
-#include <drm/drm_fb_cma_helper.h>
+#include "hdlcd_fb_helper.h"
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
@@ -67,6 +67,12 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags)
(version & HDLCD_VERSION_MAJOR_MASK) >> 8,
version & HDLCD_VERSION_MINOR_MASK);
+ /* Make sure hardware is in a safe reset state */
+ hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0);
+ hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0);
+ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR,~0);
+ hdlcd_write(hdlcd, HDLCD_REG_INT_RAWSTAT, 0);
+
/* Get the optional framebuffer memory resource */
ret = of_reserved_mem_device_init(drm->dev);
if (ret && ret != -ENODEV)
@@ -88,6 +94,9 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags)
goto irq_fail;
}
+ spin_lock_init(&hdlcd->frame_completion_lock);
+ init_completion(&hdlcd->frame_completion);
+
return 0;
irq_fail:
@@ -102,11 +111,11 @@ static void hdlcd_fb_output_poll_changed(struct drm_device *drm)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
- drm_fbdev_cma_hotplug_event(hdlcd->fbdev);
+ hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev);
}
static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
- .fb_create = drm_fb_cma_create,
+ .fb_create = hdlcd_fb_create,
.output_poll_changed = hdlcd_fb_output_poll_changed,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
@@ -126,7 +135,7 @@ static void hdlcd_lastclose(struct drm_device *drm)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
- drm_fbdev_cma_restore_mode(hdlcd->fbdev);
+ hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev);
}
static irqreturn_t hdlcd_irq(int irq, void *arg)
@@ -134,6 +143,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg)
struct drm_device *drm = arg;
struct hdlcd_drm_private *hdlcd = drm->dev_private;
unsigned long irq_status;
+ unsigned long flags;
irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS);
@@ -154,6 +164,16 @@ static irqreturn_t hdlcd_irq(int irq, void *arg)
if (irq_status & HDLCD_INTERRUPT_VSYNC)
drm_crtc_handle_vblank(&hdlcd->crtc);
+ spin_lock_irqsave(&hdlcd->frame_completion_lock, flags);
+ if (hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS) & HDLCD_INTERRUPT_DMA_END) {
+ /* Clear DMA_END interrupt here, under frame_completion_lock */
+ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END);
+ irq_status &= ~HDLCD_INTERRUPT_DMA_END;
+ /* Wake up everyone waiting for frame completion */
+ complete_all(&hdlcd->frame_completion);
+ }
+ spin_unlock_irqrestore(&hdlcd->frame_completion_lock, flags);
+
/* acknowledge interrupt(s) */
hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status);
@@ -170,15 +190,18 @@ static void hdlcd_irq_preinstall(struct drm_device *drm)
static int hdlcd_irq_postinstall(struct drm_device *drm)
{
-#ifdef CONFIG_DEBUG_FS
struct hdlcd_drm_private *hdlcd = drm->dev_private;
unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+#ifdef CONFIG_DEBUG_FS
/* enable debug interrupts */
irq_mask |= HDLCD_DEBUG_INT_MASK;
+#endif
+ /* enable DMA completion interrupts */
+ irq_mask |= HDLCD_INTERRUPT_DMA_END;
hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask);
-#endif
+
return 0;
}
@@ -193,12 +216,34 @@ static void hdlcd_irq_uninstall(struct drm_device *drm)
irq_mask &= ~HDLCD_DEBUG_INT_MASK;
#endif
- /* disable vsync interrupts */
- irq_mask &= ~HDLCD_INTERRUPT_VSYNC;
+ /* disable vsync and dma interrupts */
+ irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END);
hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask);
}
+void hdlcd_wait_for_frame_completion(struct drm_device *drm)
+{
+ struct hdlcd_drm_private *hdlcd = drm->dev_private;
+
+ if (drm_crtc_vblank_get(&hdlcd->crtc))
+ return; /* vblank interrupts not available so don't try and wait */
+
+ /*
+ * Clear pending interrupts and completions so we won't get signalled
+ * for any earlier frames,
+ */
+ spin_lock_irq(&hdlcd->frame_completion_lock);
+ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END);
+ reinit_completion(&hdlcd->frame_completion);
+ spin_unlock_irq(&hdlcd->frame_completion_lock);
+
+ /* Wait for end of current frame */
+ wait_for_completion_interruptible_timeout(&hdlcd->frame_completion, HZ / 10);
+
+ drm_crtc_vblank_put(&hdlcd->crtc);
+}
+
static int hdlcd_enable_vblank(struct drm_device *drm, unsigned int crtc)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
@@ -247,7 +292,7 @@ static int hdlcd_show_pxlclock(struct seq_file *m, void *arg)
static struct drm_info_list hdlcd_debugfs_list[] = {
{ "interrupt_count", hdlcd_show_underrun_count, 0 },
{ "clocks", hdlcd_show_pxlclock, 0 },
- { "fb", drm_fb_cma_debugfs_show, 0 },
+ { "fb", hdlcd_fb_debugfs_show, 0 },
};
static int hdlcd_debugfs_init(struct drm_minor *minor)
@@ -311,6 +356,8 @@ static int hdlcd_drm_bind(struct device *dev)
struct drm_device *drm;
struct hdlcd_drm_private *hdlcd;
int ret;
+ struct device_node *node;
+ int preferred_bpp;
hdlcd = devm_kzalloc(dev, sizeof(*hdlcd), GFP_KERNEL);
if (!hdlcd)
@@ -349,7 +396,15 @@ static int hdlcd_drm_bind(struct device *dev)
drm_mode_config_reset(drm);
drm_kms_helper_poll_init(drm);
- hdlcd->fbdev = drm_fbdev_cma_init(drm, 32,
+ /* Try to pick the colour depth that Android user-side is hard-coded for */
+ preferred_bpp = 16;
+ node = of_find_compatible_node(NULL,NULL,"arm,mali-midgard");
+ if (node) {
+ of_node_put(node);
+ preferred_bpp = 32; /* If Mali present, assume 32bpp */
+ }
+
+ hdlcd->fbdev = hdlcd_drm_fbdev_init(drm, preferred_bpp,
drm->mode_config.num_connector);
if (IS_ERR(hdlcd->fbdev)) {
@@ -366,7 +421,7 @@ static int hdlcd_drm_bind(struct device *dev)
err_register:
if (hdlcd->fbdev) {
- drm_fbdev_cma_fini(hdlcd->fbdev);
+ hdlcd_drm_fbdev_fini(hdlcd->fbdev);
hdlcd->fbdev = NULL;
}
err_fbdev:
@@ -394,7 +449,7 @@ static void hdlcd_drm_unbind(struct device *dev)
drm_dev_unregister(drm);
if (hdlcd->fbdev) {
- drm_fbdev_cma_fini(hdlcd->fbdev);
+ hdlcd_drm_fbdev_fini(hdlcd->fbdev);
hdlcd->fbdev = NULL;
}
drm_kms_helper_poll_fini(drm);
@@ -512,7 +567,19 @@ static struct platform_driver hdlcd_platform_driver = {
},
};
-module_platform_driver(hdlcd_platform_driver);
+static int __init hdlcd_init(void)
+{
+ return platform_driver_register(&hdlcd_platform_driver);
+}
+
+static void __exit hdlcd_exit(void)
+{
+ platform_driver_unregister(&hdlcd_platform_driver);
+}
+
+/* need late_initcall() so we load after i2c driver */
+late_initcall(hdlcd_init);
+module_exit(hdlcd_exit);
MODULE_AUTHOR("Liviu Dudau");
MODULE_DESCRIPTION("ARM HDLCD DRM driver");
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h
index e3950a071152..e76d99cfeada 100644
--- a/drivers/gpu/drm/arm/hdlcd_drv.h
+++ b/drivers/gpu/drm/arm/hdlcd_drv.h
@@ -8,10 +8,12 @@
struct hdlcd_drm_private {
void __iomem *mmio;
struct clk *clk;
- struct drm_fbdev_cma *fbdev;
+ struct hdlcd_drm_fbdev *fbdev;
struct drm_crtc crtc;
struct drm_plane *plane;
struct drm_atomic_state *state;
+ spinlock_t frame_completion_lock;
+ struct completion frame_completion;
#ifdef CONFIG_DEBUG_FS
atomic_t buffer_underrun_count;
atomic_t bus_error_count;
@@ -36,4 +38,6 @@ static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg)
int hdlcd_setup_crtc(struct drm_device *dev);
void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd);
+void hdlcd_wait_for_frame_completion(struct drm_device *drm);
+
#endif /* __HDLCD_DRV_H__ */
diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.c b/drivers/gpu/drm/arm/hdlcd_fb_helper.c
new file mode 100644
index 000000000000..56d12db1961d
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c
@@ -0,0 +1,857 @@
+/*
+ * drm kms/fb cma (contiguous memory allocator) helper functions
+ *
+ * Copyright (C) 2012 Analog Device Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Based on udl_fbdev.c
+ * Copyright (C) 2012 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 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_crtc.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include "hdlcd_fb_helper.h"
+#include <linux/dma-buf.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/reservation.h>
+
+#include "hdlcd_drv.h"
+#include "hdlcd_regs.h"
+
+#define DEFAULT_FBDEFIO_DELAY_MS 50
+
+#define MAX_FRAMES 2
+
+static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg);
+
+/******************************************************************************
+ * Code copied from drivers/gpu/drm/drm_fb_helper.c as of Linux 4.11-rc4
+ ******************************************************************************/
+
+/**
+ * Copy of drm_fb_helper_check_var modified to allow MAX_FRAMES * height
+ */
+static int hdlcd_fb_helper_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct drm_fb_helper *fb_helper = info->par;
+ struct drm_framebuffer *fb = fb_helper->fb;
+ int depth;
+
+ if (var->pixclock != 0 || in_dbg_master())
+ return -EINVAL;
+
+ /*
+ * Changes struct fb_var_screeninfo are currently not pushed back
+ * to KMS, hence fail if different settings are requested.
+ */
+ if (var->bits_per_pixel != fb->format->cpp[0] * 8 ||
+ var->xres > fb->width || var->yres > fb->height ||
+ var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) {
+ DRM_DEBUG("fb requested width/height/bpp can't fit in current fb "
+ "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n",
+ var->xres, var->yres, var->bits_per_pixel,
+ var->xres_virtual, var->yres_virtual,
+ fb->width, fb->height, fb->format->cpp[0] * 8);
+ return -EINVAL;
+ }
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ depth = (var->green.length == 6) ? 16 : 15;
+ break;
+ case 32:
+ depth = (var->transp.length > 0) ? 32 : 24;
+ break;
+ default:
+ depth = var->bits_per_pixel;
+ break;
+ }
+
+ switch (depth) {
+ case 8:
+ var->red.offset = 0;
+ var->green.offset = 0;
+ var->blue.offset = 0;
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ break;
+ case 15:
+ var->red.offset = 10;
+ var->green.offset = 5;
+ var->blue.offset = 0;
+ var->red.length = 5;
+ var->green.length = 5;
+ var->blue.length = 5;
+ var->transp.length = 1;
+ var->transp.offset = 15;
+ break;
+ case 16:
+ var->red.offset = 11;
+ var->green.offset = 5;
+ var->blue.offset = 0;
+ var->red.length = 5;
+ var->green.length = 6;
+ var->blue.length = 5;
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ break;
+ case 24:
+ var->red.offset = 16;
+ var->green.offset = 8;
+ var->blue.offset = 0;
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ break;
+ case 32:
+ var->red.offset = 16;
+ var->green.offset = 8;
+ var->blue.offset = 0;
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ var->transp.length = 8;
+ var->transp.offset = 24;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/******************************************************************************
+ * Code copied from drivers/gpu/drm/drm_fb_cma_helper.c as of Linux 4.4
+ ******************************************************************************/
+
+struct hdlcd_fb {
+ struct drm_framebuffer fb;
+ struct drm_gem_cma_object *obj[4];
+};
+
+struct hdlcd_drm_fbdev {
+ struct drm_fb_helper fb_helper;
+ struct hdlcd_fb *fb;
+ const struct drm_framebuffer_funcs *fb_funcs;
+};
+
+/**
+ * DOC: framebuffer cma helper functions
+ *
+ * Provides helper functions for creating a cma (contiguous memory allocator)
+ * backed framebuffer.
+ *
+ * hdlcd_fb_create() is used in the &drm_mode_config_funcs.fb_create
+ * callback function to create a cma backed framebuffer.
+ *
+ * An fbdev framebuffer backed by cma is also available by calling
+ * hdlcd_drm_fbdev_init(). hdlcd_drm_fbdev_fini() tears it down.
+ * If the &drm_framebuffer_funcs.dirty callback is set, fb_deferred_io will be
+ * set up automatically. &drm_framebuffer_funcs.dirty is called by
+ * drm_fb_helper_deferred_io() in process context (&struct delayed_work).
+ *
+ * Example fbdev deferred io code::
+ *
+ * static int driver_fb_dirty(struct drm_framebuffer *fb,
+ * struct drm_file *file_priv,
+ * unsigned flags, unsigned color,
+ * struct drm_clip_rect *clips,
+ * unsigned num_clips)
+ * {
+ * struct drm_gem_cma_object *cma = hdlcd_fb_get_gem_obj(fb, 0);
+ * ... push changes ...
+ * return 0;
+ * }
+ *
+ * static struct drm_framebuffer_funcs driver_fb_funcs = {
+ * .destroy = hdlcd_fb_destroy,
+ * .create_handle = hdlcd_fb_create_handle,
+ * .dirty = driver_fb_dirty,
+ * };
+ *
+ * Initialize::
+ *
+ * fbdev = hdlcd_drm_fbdev_init_with_funcs(dev, 16,
+ * dev->mode_config.num_crtc,
+ * dev->mode_config.num_connector,
+ * &driver_fb_funcs);
+ *
+ */
+
+static inline struct hdlcd_drm_fbdev *to_hdlcd_fbdev(struct drm_fb_helper *helper)
+{
+ return container_of(helper, struct hdlcd_drm_fbdev, fb_helper);
+}
+
+static inline struct hdlcd_fb *to_hdlcd_fb(struct drm_framebuffer *fb)
+{
+ return container_of(fb, struct hdlcd_fb, fb);
+}
+
+void hdlcd_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (hdlcd_fb->obj[i])
+ drm_gem_object_unreference_unlocked(&hdlcd_fb->obj[i]->base);
+ }
+
+ drm_framebuffer_cleanup(fb);
+ kfree(hdlcd_fb);
+}
+EXPORT_SYMBOL(hdlcd_fb_destroy);
+
+int hdlcd_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv, unsigned int *handle)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+
+ return drm_gem_handle_create(file_priv,
+ &hdlcd_fb->obj[0]->base, handle);
+}
+EXPORT_SYMBOL(hdlcd_fb_create_handle);
+
+static struct drm_framebuffer_funcs hdlcd_fb_funcs = {
+ .destroy = hdlcd_fb_destroy,
+ .create_handle = hdlcd_fb_create_handle,
+};
+
+static struct hdlcd_fb *hdlcd_fb_alloc(struct drm_device *dev,
+ const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_cma_object **obj,
+ unsigned int num_planes, const struct drm_framebuffer_funcs *funcs)
+{
+ struct hdlcd_fb *hdlcd_fb;
+ int ret;
+ int i;
+
+ hdlcd_fb = kzalloc(sizeof(*hdlcd_fb), GFP_KERNEL);
+ if (!hdlcd_fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(dev, &hdlcd_fb->fb, mode_cmd);
+
+ for (i = 0; i < num_planes; i++)
+ hdlcd_fb->obj[i] = obj[i];
+
+ ret = drm_framebuffer_init(dev, &hdlcd_fb->fb, funcs);
+ if (ret) {
+ dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret);
+ kfree(hdlcd_fb);
+ return ERR_PTR(ret);
+ }
+
+ return hdlcd_fb;
+}
+
+/**
+ * hdlcd_fb_create_with_funcs() - helper function for the
+ * &drm_mode_config_funcs.fb_create
+ * callback
+ * @dev: DRM device
+ * @file_priv: drm file for the ioctl call
+ * @mode_cmd: metadata from the userspace fb creation request
+ * @funcs: vtable to be used for the new framebuffer object
+ *
+ * This can be used to set &drm_framebuffer_funcs for drivers that need the
+ * &drm_framebuffer_funcs.dirty callback. Use hdlcd_fb_create() if you don't
+ * need to change &drm_framebuffer_funcs.
+ */
+struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd,
+ const struct drm_framebuffer_funcs *funcs)
+{
+ const struct drm_format_info *info;
+ struct hdlcd_fb *hdlcd_fb;
+ struct drm_gem_cma_object *objs[4];
+ struct drm_gem_object *obj;
+ int ret;
+ int i;
+
+ info = drm_format_info(mode_cmd->pixel_format);
+ if (!info)
+ return ERR_PTR(-EINVAL);
+
+ for (i = 0; i < info->num_planes; i++) {
+ unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
+ unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
+ unsigned int min_size;
+
+ obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]);
+ if (!obj) {
+ dev_err(dev->dev, "Failed to lookup GEM object\n");
+ ret = -ENXIO;
+ goto err_gem_object_unreference;
+ }
+
+ min_size = (height - 1) * mode_cmd->pitches[i]
+ + width * info->cpp[i]
+ + mode_cmd->offsets[i];
+
+ if (obj->size < min_size) {
+ drm_gem_object_unreference_unlocked(obj);
+ ret = -EINVAL;
+ goto err_gem_object_unreference;
+ }
+ objs[i] = to_drm_gem_cma_obj(obj);
+ }
+
+ hdlcd_fb = hdlcd_fb_alloc(dev, mode_cmd, objs, i, funcs);
+ if (IS_ERR(hdlcd_fb)) {
+ ret = PTR_ERR(hdlcd_fb);
+ goto err_gem_object_unreference;
+ }
+
+ return &hdlcd_fb->fb;
+
+err_gem_object_unreference:
+ for (i--; i >= 0; i--)
+ drm_gem_object_unreference_unlocked(&objs[i]->base);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_create_with_funcs);
+
+/**
+ * hdlcd_fb_create() - &drm_mode_config_funcs.fb_create callback function
+ * @dev: DRM device
+ * @file_priv: drm file for the ioctl call
+ * @mode_cmd: metadata from the userspace fb creation request
+ *
+ * If your hardware has special alignment or pitch requirements these should be
+ * checked before calling this function. Use hdlcd_fb_create_with_funcs() if
+ * you need to set &drm_framebuffer_funcs.dirty.
+ */
+struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ return hdlcd_fb_create_with_funcs(dev, file_priv, mode_cmd,
+ &hdlcd_fb_funcs);
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_create);
+
+/**
+ * hdlcd_fb_get_gem_obj() - Get CMA GEM object for framebuffer
+ * @fb: The framebuffer
+ * @plane: Which plane
+ *
+ * Return the CMA GEM object for given framebuffer.
+ *
+ * This function will usually be called from the CRTC callback functions.
+ */
+struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+
+ if (plane >= 4)
+ return NULL;
+
+ return hdlcd_fb->obj[plane];
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_get_gem_obj);
+
+/**
+ * hdlcd_fb_prepare_fb() - Prepare CMA framebuffer
+ * @plane: Which plane
+ * @state: Plane state attach fence to
+ *
+ * This should be set as the &struct drm_plane_helper_funcs.prepare_fb hook.
+ *
+ * This function checks if the plane FB has an dma-buf attached, extracts
+ * the exclusive fence and attaches it to plane state for the atomic helper
+ * to wait on.
+ *
+ * There is no need for cleanup_fb for CMA based framebuffer drivers.
+ */
+int hdlcd_fb_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct dma_buf *dma_buf;
+ struct dma_fence *fence;
+
+ if ((plane->state->fb == state->fb) || !state->fb)
+ return 0;
+
+ dma_buf = hdlcd_fb_get_gem_obj(state->fb, 0)->base.dma_buf;
+ if (dma_buf) {
+ fence = reservation_object_get_excl_rcu(dma_buf->resv);
+ drm_atomic_set_fence_for_plane(state, fence);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_prepare_fb);
+
+#ifdef CONFIG_DEBUG_FS
+static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+ int i;
+
+ seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height,
+ (char *)&fb->format->format);
+
+ for (i = 0; i < fb->format->num_planes; i++) {
+ seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
+ i, fb->offsets[i], fb->pitches[i]);
+ drm_gem_cma_describe(hdlcd_fb->obj[i], m);
+ }
+}
+
+/**
+ * hdlcd_fb_debugfs_show() - Helper to list CMA framebuffer objects
+ * in debugfs.
+ * @m: output file
+ * @arg: private data for the callback
+ */
+int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct drm_device *dev = node->minor->dev;
+ struct drm_framebuffer *fb;
+
+ mutex_lock(&dev->mode_config.fb_lock);
+ drm_for_each_fb(fb, dev)
+ hdlcd_fb_describe(fb, m);
+ mutex_unlock(&dev->mode_config.fb_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show);
+#endif
+
+static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ return dma_mmap_writecombine(info->device, vma, info->screen_base,
+ info->fix.smem_start, info->fix.smem_len);
+}
+
+static int hdlcd_fb_helper_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct drm_fb_helper *helper = info->par;
+ struct drm_framebuffer *fb = helper->fb;
+ struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+ struct drm_gem_cma_object *gem;
+ dma_addr_t scanout_start;
+ int ret;
+
+ ret = hdlcd_fb_helper_check_var(var, info);
+ if (ret)
+ return ret;
+
+ gem = hdlcd_fb_get_gem_obj(fb, 0);
+
+ scanout_start = gem->paddr + fb->offsets[0] +
+ (var->yoffset * fb->pitches[0]) + (var->xoffset * fb->format->cpp[0]);
+
+ hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
+
+ hdlcd_wait_for_frame_completion(helper->dev);
+
+ return 0;
+}
+
+static struct fb_ops hdlcd_drm_fbdev_ops = {
+ .owner = THIS_MODULE,
+ DRM_FB_HELPER_DEFAULT_OPS,
+ .fb_fillrect = drm_fb_helper_sys_fillrect,
+ .fb_copyarea = drm_fb_helper_sys_copyarea,
+ .fb_imageblit = drm_fb_helper_sys_imageblit,
+ .fb_mmap = hdlcd_fb_mmap,
+ .fb_ioctl = hdlcd_fb_ioctl,
+ .fb_compat_ioctl= hdlcd_fb_ioctl,
+};
+
+static int hdlcd_drm_fbdev_deferred_io_mmap(struct fb_info *info,
+ struct vm_area_struct *vma)
+{
+ fb_deferred_io_mmap(info, vma);
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ return 0;
+}
+
+static int hdlcd_drm_fbdev_defio_init(struct fb_info *fbi,
+ struct drm_gem_cma_object *cma_obj)
+{
+ struct fb_deferred_io *fbdefio;
+ struct fb_ops *fbops;
+
+ /*
+ * Per device structures are needed because:
+ * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap
+ * fbdefio: individual delays
+ */
+ fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
+ fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+ if (!fbdefio || !fbops) {
+ kfree(fbdefio);
+ kfree(fbops);
+ return -ENOMEM;
+ }
+
+ /* can't be offset from vaddr since dirty() uses cma_obj */
+ fbi->screen_buffer = cma_obj->vaddr;
+ /* fb_deferred_io_fault() needs a physical address */
+ fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer));
+
+ *fbops = *fbi->fbops;
+ fbi->fbops = fbops;
+
+ fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS);
+ fbdefio->deferred_io = drm_fb_helper_deferred_io;
+ fbi->fbdefio = fbdefio;
+ fb_deferred_io_init(fbi);
+ fbi->fbops->fb_mmap = hdlcd_drm_fbdev_deferred_io_mmap;
+
+ return 0;
+}
+
+static void hdlcd_drm_fbdev_defio_fini(struct fb_info *fbi)
+{
+ if (!fbi->fbdefio)
+ return;
+
+ fb_deferred_io_cleanup(fbi);
+ kfree(fbi->fbdefio);
+ kfree(fbi->fbops);
+}
+
+static int
+hdlcd_drm_fbdev_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct hdlcd_drm_fbdev *hdlcd_fbdev = to_hdlcd_fbdev(helper);
+ struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+ struct drm_device *dev = helper->dev;
+ struct drm_gem_cma_object *obj;
+ struct drm_framebuffer *fb;
+ unsigned int bytes_per_pixel;
+ unsigned long offset;
+ struct fb_info *fbi;
+ size_t size;
+ int ret;
+
+ DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
+ sizes->surface_width, sizes->surface_height,
+ sizes->surface_bpp);
+
+ bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ size = mode_cmd.pitches[0] * mode_cmd.height * MAX_FRAMES;
+ obj = drm_gem_cma_create(dev, size);
+ if (IS_ERR(obj))
+ return -ENOMEM;
+
+ fbi = drm_fb_helper_alloc_fbi(helper);
+ if (IS_ERR(fbi)) {
+ ret = PTR_ERR(fbi);
+ goto err_gem_free_object;
+ }
+
+ hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1,
+ hdlcd_fbdev->fb_funcs);
+ if (IS_ERR(hdlcd_fbdev->fb)) {
+ dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
+ ret = PTR_ERR(hdlcd_fbdev->fb);
+ goto err_fb_info_destroy;
+ }
+
+ fb = &hdlcd_fbdev->fb->fb;
+ helper->fb = fb;
+
+ fbi->par = helper;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ hdlcd_drm_fbdev_ops.fb_check_var = hdlcd_fb_helper_check_var;
+ hdlcd_drm_fbdev_ops.fb_pan_display = hdlcd_fb_helper_pan_display;
+ fbi->fbops = &hdlcd_drm_fbdev_ops;
+
+ drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth);
+ drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
+
+ offset = fbi->var.xoffset * bytes_per_pixel;
+ offset += fbi->var.yoffset * fb->pitches[0];
+
+ dev->mode_config.fb_base = (resource_size_t)obj->paddr;
+ fbi->screen_base = obj->vaddr + offset;
+ fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
+ fbi->screen_size = size;
+ fbi->fix.smem_len = size;
+ fbi->var.yres_virtual = fbi->var.yres * MAX_FRAMES;
+
+ if (hdlcd_fbdev->fb_funcs->dirty) {
+ ret = hdlcd_drm_fbdev_defio_init(fbi, obj);
+ if (ret)
+ goto err_cma_destroy;
+ }
+
+ return 0;
+
+err_cma_destroy:
+ drm_framebuffer_remove(&hdlcd_fbdev->fb->fb);
+err_fb_info_destroy:
+ drm_fb_helper_release_fbi(helper);
+err_gem_free_object:
+ drm_gem_object_unreference_unlocked(&obj->base);
+ return ret;
+}
+
+static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = {
+ .fb_probe = hdlcd_drm_fbdev_create,
+};
+
+/**
+ * hdlcd_drm_fbdev_init_with_funcs() - Allocate and initializes a hdlcd_drm_fbdev struct
+ * @dev: DRM device
+ * @preferred_bpp: Preferred bits per pixel for the device
+ * @max_conn_count: Maximum number of connectors
+ * @funcs: fb helper functions, in particular a custom dirty() callback
+ *
+ * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR.
+ */
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count,
+ const struct drm_framebuffer_funcs *funcs)
+{
+ struct hdlcd_drm_fbdev *hdlcd_fbdev;
+ struct drm_fb_helper *helper;
+ int ret;
+
+ hdlcd_fbdev = kzalloc(sizeof(*hdlcd_fbdev), GFP_KERNEL);
+ if (!hdlcd_fbdev) {
+ dev_err(dev->dev, "Failed to allocate drm fbdev.\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ hdlcd_fbdev->fb_funcs = funcs;
+
+ helper = &hdlcd_fbdev->fb_helper;
+
+ drm_fb_helper_prepare(dev, helper, &hdlcd_fb_helper_funcs);
+
+ ret = drm_fb_helper_init(dev, helper, max_conn_count);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
+ goto err_free;
+ }
+
+ ret = drm_fb_helper_single_add_all_connectors(helper);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to add connectors.\n");
+ goto err_drm_fb_helper_fini;
+
+ }
+
+ ret = drm_fb_helper_initial_config(helper, preferred_bpp);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to set initial hw configuration.\n");
+ goto err_drm_fb_helper_fini;
+ }
+
+ return hdlcd_fbdev;
+
+err_drm_fb_helper_fini:
+ drm_fb_helper_fini(helper);
+err_free:
+ kfree(hdlcd_fbdev);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init_with_funcs);
+
+/**
+ * hdlcd_drm_fbdev_init() - Allocate and initializes a hdlcd_drm_fbdev struct
+ * @dev: DRM device
+ * @preferred_bpp: Preferred bits per pixel for the device
+ * @num_crtc: Number of CRTCs
+ * @max_conn_count: Maximum number of connectors
+ *
+ * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR.
+ */
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count)
+{
+ return hdlcd_drm_fbdev_init_with_funcs(dev, preferred_bpp,
+ max_conn_count,
+ &hdlcd_fb_funcs);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init);
+
+/**
+ * hdlcd_drm_fbdev_fini() - Free hdlcd_drm_fbdev struct
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct
+ */
+void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev)
+{
+ drm_fb_helper_unregister_fbi(&hdlcd_fbdev->fb_helper);
+ if (hdlcd_fbdev->fb_helper.fbdev)
+ hdlcd_drm_fbdev_defio_fini(hdlcd_fbdev->fb_helper.fbdev);
+ drm_fb_helper_release_fbi(&hdlcd_fbdev->fb_helper);
+
+ if (hdlcd_fbdev->fb)
+ drm_framebuffer_remove(&hdlcd_fbdev->fb->fb);
+
+ drm_fb_helper_fini(&hdlcd_fbdev->fb_helper);
+ kfree(hdlcd_fbdev);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_fini);
+
+/**
+ * hdlcd_drm_fbdev_restore_mode() - Restores initial framebuffer mode
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ *
+ * This function is usually called from the &drm_driver.lastclose callback.
+ */
+void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_restore_fbdev_mode_unlocked(&hdlcd_fbdev->fb_helper);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_restore_mode);
+
+/**
+ * hdlcd_drm_fbdev_hotplug_event() - Poll for hotpulug events
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ *
+ * This function is usually called from the &drm_mode_config.output_poll_changed
+ * callback.
+ */
+void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_hotplug_event(&hdlcd_fbdev->fb_helper);
+}
+EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_hotplug_event);
+
+/**
+ * hdlcd_drm_fbdev_set_suspend - wrapper around drm_fb_helper_set_suspend
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ * @state: desired state, zero to resume, non-zero to suspend
+ *
+ * Calls drm_fb_helper_set_suspend, which is a wrapper around
+ * fb_set_suspend implemented by fbdev core.
+ */
+void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_set_suspend(&hdlcd_fbdev->fb_helper, state);
+}
+EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend);
+
+/**
+ * hdlcd_drm_fbdev_set_suspend_unlocked - wrapper around
+ * drm_fb_helper_set_suspend_unlocked
+ * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL
+ * @state: desired state, zero to resume, non-zero to suspend
+ *
+ * Calls drm_fb_helper_set_suspend, which is a wrapper around
+ * fb_set_suspend implemented by fbdev core.
+ */
+void hdlcd_drm_fbdev_set_suspend_unlocked(struct hdlcd_drm_fbdev *hdlcd_fbdev,
+ int state)
+{
+ if (hdlcd_fbdev)
+ drm_fb_helper_set_suspend_unlocked(&hdlcd_fbdev->fb_helper,
+ state);
+}
+EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend_unlocked);
+
+/******************************************************************************
+ * IOCTL Interface
+ ******************************************************************************/
+
+/*
+ * Used for sharing buffers with Mali userspace
+ */
+struct fb_dmabuf_export {
+ uint32_t fd;
+ uint32_t flags;
+};
+
+#define FBIOGET_DMABUF _IOR('F', 0x21, struct fb_dmabuf_export)
+
+static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct fb_dmabuf_export ebuf;
+ struct drm_fb_helper *helper = info->par;
+ struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+ struct drm_gem_cma_object *obj = hdlcd->fbdev->fb->obj[0];
+ struct dma_buf *dma_buf;
+ uint32_t fd;
+
+ if (copy_from_user(&ebuf, argp, sizeof(ebuf)))
+ return -EFAULT;
+
+ /*
+ * We need a reference on the gem object. This will be released by
+ * drm_gem_dmabuf_release when the file descriptor is closed.
+ */
+ drm_gem_object_reference(&obj->base);
+
+ dma_buf = drm_gem_prime_export(helper->dev, &obj->base, ebuf.flags | O_RDWR);
+ if (!dma_buf) {
+ dev_info(info->dev, "Failed to export DMA buffer\n");
+ goto err_export;
+ }
+
+ fd = dma_buf_fd(dma_buf, O_CLOEXEC);
+ if (fd < 0) {
+ dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n");
+ goto err_export_fd;
+ }
+ ebuf.fd = fd;
+
+ if (copy_to_user(argp, &ebuf, sizeof(ebuf)))
+ goto err_export_fd;
+
+ return 0;
+
+err_export_fd:
+ dma_buf_put(dma_buf);
+err_export:
+ drm_gem_object_unreference(&obj->base);
+ return -EFAULT;
+}
+
+static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case FBIOGET_DMABUF:
+ return hdlcd_get_dmabuf_ioctl(info, cmd, arg);
+ case FBIO_WAITFORVSYNC:
+ return 0; /* Nothing to do as we wait when page flipping anyway */
+ default:
+ printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd);
+ }
+
+ return -EFAULT;
+}
diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.h b/drivers/gpu/drm/arm/hdlcd_fb_helper.h
new file mode 100644
index 000000000000..aee0d9037561
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h
@@ -0,0 +1,54 @@
+#ifndef __DRM_FB_CMA_HELPER_H__
+#define __DRM_FB_CMA_HELPER_H__
+
+struct hdlcd_drm_fbdev;
+struct drm_gem_cma_object;
+
+struct drm_fb_helper_surface_size;
+struct drm_framebuffer_funcs;
+struct drm_fb_helper_funcs;
+struct drm_framebuffer;
+struct drm_fb_helper;
+struct drm_device;
+struct drm_file;
+struct drm_mode_fb_cmd2;
+struct drm_plane;
+struct drm_plane_state;
+
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count,
+ const struct drm_framebuffer_funcs *funcs);
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int max_conn_count);
+void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev);
+
+void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev);
+void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev);
+void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state);
+void hdlcd_drm_fbdev_set_suspend_unlocked(struct hdlcd_drm_fbdev *hdlcd_fbdev,
+ int state);
+
+void hdlcd_fb_destroy(struct drm_framebuffer *fb);
+int hdlcd_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv, unsigned int *handle);
+
+struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd,
+ const struct drm_framebuffer_funcs *funcs);
+struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd);
+
+struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane);
+
+int hdlcd_fb_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *state);
+
+#ifdef CONFIG_DEBUG_FS
+struct seq_file;
+
+int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg);
+#endif
+
+#endif
+
diff --git a/drivers/gpu/drm/drm_virtual_encoder.c b/drivers/gpu/drm/drm_virtual_encoder.c
new file mode 100644
index 000000000000..d5e24dfc27ea
--- /dev/null
+++ b/drivers/gpu/drm/drm_virtual_encoder.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2016 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * Dummy encoder and connector that use the OF to "discover" the attached
+ * display timings. Can be used in situations where the encoder and connector's
+ * functionality are emulated and no setup steps are needed, or to describe
+ * attached panels for which no driver exists but can be used without
+ * additional hardware setup.
+ *
+ * The encoder also uses the component framework so that it can be a quick
+ * replacement for existing drivers when testing in an emulated environment.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <linux/component.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+struct drm_virt_priv {
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct display_timings *timings;
+};
+
+#define connector_to_drm_virt_priv(x) \
+ container_of(x, struct drm_virt_priv, connector)
+
+#define encoder_to_drm_virt_priv(x) \
+ container_of(x, struct drm_virt_priv, encoder)
+
+static void drm_virtcon_destroy(struct drm_connector *connector)
+{
+ struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector);
+
+ drm_connector_cleanup(connector);
+ display_timings_release(conn->timings);
+}
+
+static enum drm_connector_status
+drm_virtcon_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs drm_virtcon_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .reset = drm_atomic_helper_connector_reset,
+ .detect = drm_virtcon_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_virtcon_destroy,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int drm_virtcon_get_modes(struct drm_connector *connector)
+{
+ struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector);
+ struct display_timings *timings = conn->timings;
+ int i;
+
+ for (i = 0; i < timings->num_timings; i++) {
+ struct drm_display_mode *mode = drm_mode_create(connector->dev);
+ struct videomode vm;
+
+ if (videomode_from_timings(timings, &vm, i))
+ break;
+
+ drm_display_mode_from_videomode(&vm, mode);
+ mode->type = DRM_MODE_TYPE_DRIVER;
+ if (timings->native_mode == i)
+ mode->type = DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+ }
+
+ return i;
+}
+
+static int drm_virtcon_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+struct drm_encoder *drm_virtcon_best_encoder(struct drm_connector *connector)
+{
+ struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector);
+
+ return &priv->encoder;
+}
+
+struct drm_encoder *
+drm_virtcon_atomic_best_encoder(struct drm_connector *connector,
+ struct drm_connector_state *connector_state)
+{
+ struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector);
+
+ return &priv->encoder;
+}
+
+static const struct drm_connector_helper_funcs drm_virtcon_helper_funcs = {
+ .get_modes = drm_virtcon_get_modes,
+ .mode_valid = drm_virtcon_mode_valid,
+ .best_encoder = drm_virtcon_best_encoder,
+ .atomic_best_encoder = drm_virtcon_atomic_best_encoder,
+};
+
+static void drm_vencoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs drm_vencoder_funcs = {
+ .destroy = drm_vencoder_destroy,
+};
+
+static void drm_vencoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ /* nothing needed */
+}
+
+static bool drm_vencoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+ return true;
+}
+
+static void drm_vencoder_prepare(struct drm_encoder *encoder)
+{
+ drm_vencoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void drm_vencoder_commit(struct drm_encoder *encoder)
+{
+ drm_vencoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void drm_vencoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+}
+
+static const struct drm_encoder_helper_funcs drm_vencoder_helper_funcs = {
+ .dpms = drm_vencoder_dpms,
+ .mode_fixup = drm_vencoder_mode_fixup,
+ .prepare = drm_vencoder_prepare,
+ .commit = drm_vencoder_commit,
+ .mode_set = drm_vencoder_mode_set,
+};
+
+static int drm_vencoder_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct drm_encoder *encoder;
+ struct drm_virt_priv *con;
+ struct drm_connector *connector;
+ struct drm_device *drm = data;
+ u32 crtcs = 0;
+ int ret;
+
+ con = devm_kzalloc(dev, sizeof(*con), GFP_KERNEL);
+ if (!con)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, con);
+ connector = &con->connector;
+ encoder = &con->encoder;
+
+ if (dev->of_node) {
+ struct drm_bridge *bridge;
+ crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+ bridge = of_drm_find_bridge(dev->of_node);
+ if (bridge) {
+ ret = drm_bridge_attach(encoder, bridge, NULL);
+ if (ret) {
+ DRM_ERROR("Failed to initialize bridge\n");
+ return ret;
+ }
+ encoder->bridge = bridge;
+ }
+ con->timings = of_get_display_timings(dev->of_node);
+ if (!con->timings) {
+ dev_err(dev, "failed to get display panel timings\n");
+ return ENXIO;
+ }
+ }
+
+ /* If no CRTCs were found, fall back to the old encoder's behaviour */
+ if (crtcs == 0) {
+ dev_warn(dev, "Falling back to first CRTC\n");
+ crtcs = 1 << 0;
+ }
+
+ encoder->possible_crtcs = crtcs ? crtcs : 1;
+ encoder->possible_clones = 0;
+
+ ret = drm_encoder_init(drm, encoder, &drm_vencoder_funcs,
+ DRM_MODE_ENCODER_VIRTUAL, "virtual-encoder");
+ if (ret)
+ goto encoder_init_err;
+
+ drm_encoder_helper_add(encoder, &drm_vencoder_helper_funcs);
+
+ /* bogus values, pretend we're a 24" screen for DPI calculations */
+ connector->display_info.width_mm = 519;
+ connector->display_info.height_mm = 324;
+ connector->interlace_allowed = false;
+ connector->doublescan_allowed = false;
+ connector->polled = 0;
+
+ ret = drm_connector_init(drm, connector, &drm_virtcon_funcs,
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret)
+ goto connector_init_err;
+
+ drm_connector_helper_add(connector, &drm_virtcon_helper_funcs);
+
+ drm_connector_register(connector);
+
+ ret = drm_mode_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto attach_err;
+
+ return ret;
+
+attach_err:
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+connector_init_err:
+ drm_encoder_cleanup(encoder);
+encoder_init_err:
+ display_timings_release(con->timings);
+
+ return ret;
+};
+
+static void drm_vencoder_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct drm_virt_priv *con = dev_get_drvdata(dev);
+
+ drm_connector_unregister(&con->connector);
+ drm_connector_cleanup(&con->connector);
+ drm_encoder_cleanup(&con->encoder);
+ display_timings_release(con->timings);
+}
+
+static const struct component_ops drm_vencoder_ops = {
+ .bind = drm_vencoder_bind,
+ .unbind = drm_vencoder_unbind,
+};
+
+static int drm_vencoder_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &drm_vencoder_ops);
+}
+
+static int drm_vencoder_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &drm_vencoder_ops);
+ return 0;
+}
+
+static const struct of_device_id drm_vencoder_of_match[] = {
+ { .compatible = "drm,virtual-encoder", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, drm_vencoder_of_match);
+
+static struct platform_driver drm_vencoder_driver = {
+ .probe = drm_vencoder_probe,
+ .remove = drm_vencoder_remove,
+ .driver = {
+ .name = "drm_vencoder",
+ .of_match_table = drm_vencoder_of_match,
+ },
+};
+
+module_platform_driver(drm_vencoder_driver);
+
+MODULE_AUTHOR("Liviu Dudau");
+MODULE_DESCRIPTION("Virtual DRM Encoder");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
index a6c92beb410a..23e0e8784e49 100644
--- a/drivers/gpu/drm/i2c/Kconfig
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -11,6 +11,9 @@ config DRM_I2C_CH7006
This driver is currently only useful if you're also using
the nouveau driver.
+config DRM_I2C_DUMMY
+ tristate "Dummy stub driver"
+
config DRM_I2C_SIL164
tristate "Silicon Image sil164 TMDS transmitter"
default m if DRM_NOUVEAU
diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 43aa33baebed..658751730d50 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -3,6 +3,8 @@ ccflags-y := -Iinclude/drm
ch7006-y := ch7006_drv.o ch7006_mode.o
obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
+obj-$(CONFIG_DRM_I2C_DUMMY) += dummy_drm_i2c_drv.o
+
sil164-y := sil164_drv.o
obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
diff --git a/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c
new file mode 100644
index 000000000000..2958f57940e8
--- /dev/null
+++ b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c
@@ -0,0 +1,264 @@
+/*
+ * This file was originally based on tda998x_drv.c which has the following
+ * copyright and licence...
+ *
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+
+struct dummy_priv {
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+};
+
+#define conn_to_dummy_priv(x) \
+ container_of(x, struct dummy_priv, connector);
+
+/* DRM encoder functions */
+
+static void dummy_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool
+dummy_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static int dummy_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static void
+dummy_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static enum drm_connector_status
+dummy_connector_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static const u8 edid_1024x768[] = {
+ /*
+ * These values are a copy of Documentation/EDID/1024x768.c
+ * produced by executing "make -C Documentation/EDID"
+ */
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x31, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0x16, 0x01, 0x03, 0x6d, 0x23, 0x1a, 0x78,
+ 0xea, 0x5e, 0xc0, 0xa4, 0x59, 0x4a, 0x98, 0x25,
+ 0x20, 0x50, 0x54, 0x00, 0x08, 0x00, 0x61, 0x40,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x64, 0x19,
+ 0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x08, 0x90,
+ 0x36, 0x00, 0x63, 0x0a, 0x11, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x4c, 0x69, 0x6e,
+ 0x75, 0x78, 0x20, 0x23, 0x30, 0x0a, 0x20, 0x20,
+ 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x3b,
+ 0x3d, 0x2f, 0x31, 0x07, 0x00, 0x0a, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc,
+ 0x00, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x58,
+ 0x47, 0x41, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x55
+};
+
+static int dummy_read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length)
+{
+ memcpy(buf, edid_1024x768, min(sizeof(edid_1024x768),length));
+ return 0;
+}
+
+static int dummy_connector_get_modes(struct drm_connector *connector)
+{
+ struct edid *edid;
+ int n;
+
+ edid = drm_do_get_edid(connector, dummy_read_edid_block, NULL);
+ if (!edid)
+ return 0;
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ n = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+
+ return n;
+}
+
+/* I2C driver functions */
+
+static void dummy_encoder_prepare(struct drm_encoder *encoder)
+{
+}
+
+static void dummy_encoder_commit(struct drm_encoder *encoder)
+{
+}
+
+static const struct drm_encoder_helper_funcs dummy_encoder_helper_funcs = {
+ .dpms = dummy_encoder_dpms,
+ .mode_fixup = dummy_encoder_mode_fixup,
+ .prepare = dummy_encoder_prepare,
+ .commit = dummy_encoder_commit,
+ .mode_set = dummy_encoder_mode_set,
+};
+
+static void dummy_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs dummy_encoder_funcs = {
+ .destroy = dummy_encoder_destroy,
+};
+
+static struct drm_encoder *
+dummy_connector_best_encoder(struct drm_connector *connector)
+{
+ struct dummy_priv *priv = conn_to_dummy_priv(connector);
+
+ return &priv->encoder;
+}
+
+static
+const struct drm_connector_helper_funcs dummy_connector_helper_funcs = {
+ .get_modes = dummy_connector_get_modes,
+ .mode_valid = dummy_connector_mode_valid,
+ .best_encoder = dummy_connector_best_encoder,
+};
+
+static void dummy_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs dummy_connector_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = dummy_connector_detect,
+ .destroy = dummy_connector_destroy,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int dummy_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct dummy_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+
+ priv->connector.interlace_allowed = 1;
+ priv->encoder.possible_crtcs = 1 << 0;
+
+ drm_encoder_helper_add(&priv->encoder, &dummy_encoder_helper_funcs);
+ ret = drm_encoder_init(drm, &priv->encoder, &dummy_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+ if (ret)
+ goto err_encoder;
+
+ drm_connector_helper_add(&priv->connector,
+ &dummy_connector_helper_funcs);
+ ret = drm_connector_init(drm, &priv->connector,
+ &dummy_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret)
+ goto err_connector;
+
+ drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder);
+
+ return 0;
+
+err_connector:
+ drm_encoder_cleanup(&priv->encoder);
+err_encoder:
+ return ret;
+}
+
+static void dummy_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct dummy_priv *priv = dev_get_drvdata(dev);
+
+ drm_connector_cleanup(&priv->connector);
+ drm_encoder_cleanup(&priv->encoder);
+}
+
+static const struct component_ops dummy_ops = {
+ .bind = dummy_bind,
+ .unbind = dummy_unbind,
+};
+
+static int
+dummy_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ return component_add(&client->dev, &dummy_ops);
+}
+
+static int dummy_remove(struct i2c_client *client)
+{
+ component_del(&client->dev, &dummy_ops);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dummy_of_ids[] = {
+ { .compatible = "sil,sii9022-tpi", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dummy_of_ids);
+#endif
+
+static struct i2c_device_id dummy_ids[] = {
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, dummy_ids);
+
+static struct i2c_driver dummy_driver = {
+ .probe = dummy_probe,
+ .remove = dummy_remove,
+ .driver = {
+ .name = "dummy_drm_i2c",
+ .of_match_table = dummy_of_ids
+ },
+ .id_table = dummy_ids,
+};
+
+module_i2c_driver(dummy_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 8f0b195e4a59..512f7a087aba 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -830,6 +830,8 @@ int drm_crtc_force_disable_all(struct drm_device *dev);
int drm_mode_set_config_internal(struct drm_mode_set *set);
struct drm_crtc *drm_crtc_from_index(struct drm_device *dev, int idx);
+extern int drm_create_virtual_connector(struct drm_device *dev);
+
/**
* drm_crtc_find - look up a CRTC object from its ID
* @dev: DRM device
diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf
index a5c1560aa443..71677e86b564 100644
--- a/linaro/configs/vexpress.conf
+++ b/linaro/configs/vexpress.conf
@@ -34,7 +34,15 @@ CONFIG_SERIAL_AMBA_PL011=y
CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
CONFIG_FB=y
CONFIG_FB_ARMCLCD=y
-CONFIG_FB_ARMHDLCD=y
+CONFIG_DRM=y
+CONFIG_DRM_ARM=y
+CONFIG_DRM_HDLCD=y
+CONFIG_CMA=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_SEL_MBYTES=y
+CONFIG_CMA_SIZE_MBYTES=32
+CONFIG_I2C_VERSATILE=y
+CONFIG_DRM_I2C_DUMMY=y
CONFIG_LOGO=y
# CONFIG_LOGO_LINUX_MONO is not set
# CONFIG_LOGO_LINUX_VGA16 is not set
diff --git a/linaro/configs/vexpress64.conf b/linaro/configs/vexpress64.conf
index 4d766c762acc..d5ae380d9b6f 100644
--- a/linaro/configs/vexpress64.conf
+++ b/linaro/configs/vexpress64.conf
@@ -78,3 +78,26 @@ CONFIG_SATA_SIL24=y
CONFIG_SKY2=y
CONFIG_ARM_TIMER_SP804=y
CONFIG_ARM_CPUIDLE=y
+CONFIG_DRM=y
+CONFIG_DRM_ARM=y
+CONFIG_DRM_HDLCD=y
+CONFIG_DRM_VIRT_ENCODER=y
+CONFIG_CMA=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_SEL_MBYTES=y
+CONFIG_CMA_SIZE_MBYTES=32
+CONFIG_DRM_I2C_NXP_TDA998X=y
+CONFIG_SOUND=y
+CONFIG_SND=y
+CONFIG_SND_SEQUENCER=y
+CONFIG_SND_SEQ_DUMMY=y
+CONFIG_SND_MIXER_OSS=y
+CONFIG_SND_PCM_OSS=y
+CONFIG_SND_SEQUENCER_OSS=y
+# CONFIG_SND_USB is not set
+CONFIG_SND_SOC=y
+CONFIG_SND_DESIGNWARE_I2S=y
+CONFIG_SND_SOC_SPDIF=y
+CONFIG_SND_SIMPLE_CARD=y
+CONFIG_DMADEVICES=y
+CONFIG_PL330_DMA=y