summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Medhurst <tixy@linaro.org>2015-10-06 17:39:48 +0100
committerJon Medhurst <tixy@linaro.org>2015-10-06 17:39:48 +0100
commitf49536d5c9ecbb405e5a6b3f6449f9ffcfa5ded0 (patch)
treea106fa1edd7f3c564617b1f17d86c3c161b39385
parenteb6e083b22a95139fa03f2a76c6a250033f4163d (diff)
parent8f01b241f8debc9b5ffbdd3f9edae36853b8c4ef (diff)
downloadlinux-linaro-tracking-f49536d5c9ecbb405e5a6b3f6449f9ffcfa5ded0.tar.gz
Merge branch 'tracking-armlt-hdlcd' into integration-linaro-vexpress
Conflicts: linaro/configs/vexpress64.conf
-rw-r--r--Documentation/devicetree/bindings/drm/i2c/tda998x.txt14
-rw-r--r--Documentation/devicetree/bindings/video/arm,hdlcd.txt115
-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.dtsi108
-rw-r--r--drivers/gpu/drm/Kconfig2
-rw-r--r--drivers/gpu/drm/Makefile1
-rw-r--r--drivers/gpu/drm/arm/Kconfig26
-rw-r--r--drivers/gpu/drm/arm/Makefile4
-rw-r--r--drivers/gpu/drm/arm/hdlcd_crtc.c301
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.c526
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.h69
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb_helper.c636
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb_helper.h31
-rw-r--r--drivers/gpu/drm/arm/hdlcd_regs.h87
-rw-r--r--drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c243
-rw-r--r--drivers/gpu/drm/arm/hdlcd_virt_encoder.c199
-rw-r--r--drivers/gpu/drm/i2c/Kconfig3
-rw-r--r--drivers/gpu/drm/i2c/Makefile4
-rw-r--r--drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c465
-rw-r--r--drivers/gpu/drm/i2c/tda998x_codec.c248
-rw-r--r--drivers/gpu/drm/i2c/tda998x_drv.c100
-rw-r--r--drivers/gpu/drm/i2c/tda998x_drv.h34
-rw-r--r--drivers/staging/android/ion/ion.c4
-rw-r--r--include/drm/i2c/tda998x.h1
-rw-r--r--linaro/configs/vexpress.conf8
-rw-r--r--linaro/configs/vexpress64.conf22
29 files changed, 3312 insertions, 34 deletions
diff --git a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt
index e9e4bce40760..adac4dd2de5f 100644
--- a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt
+++ b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt
@@ -17,6 +17,15 @@ Optional properties:
- video-ports: 24 bits value which defines how the video controller
output is wired to the TDA998x input - default: <0x230145>
+ - audio-ports: one or two values corresponding to entries in
+ the audio-port-names property.
+
+ - audio-port-names: must contain "i2s", "spdif" entries
+ matching entries in the audio-ports property.
+
+ - #sound-dai-cells: must be set to <1> for use with the simple-card.
+ The DAI 0 is the I2S input and the DAI 1 is the S/PDIF input.
+
Example:
tda998x: hdmi-encoder {
@@ -26,4 +35,9 @@ Example:
interrupts = <27 2>; /* falling edge */
pinctrl-0 = <&pmx_camera>;
pinctrl-names = "default";
+
+ audio-ports = <0x03>, <0x04>;
+ audio-port-names = "i2s", "spdif";
+ #sound-dai-cells = <1>;
+ };
};
diff --git a/Documentation/devicetree/bindings/video/arm,hdlcd.txt b/Documentation/devicetree/bindings/video/arm,hdlcd.txt
new file mode 100644
index 000000000000..51795ce7dff1
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/arm,hdlcd.txt
@@ -0,0 +1,115 @@
+ARM HDLCD
+
+This is a driver for a display controller found on several development
+platforms produced by ARM Ltd and in more modern of its' Fast Models.
+The HDLCD is an RGB streamer that reads the data from a framebuffer
+and sends it to a single digital encoder (DVI or HDMI).
+
+Required properties:
+ - compatible: "arm,hdlcd"
+ - reg: Physical base address and length of the controller's registers.
+ If a second pair of address and length values is present this specifies
+ the presence of a DMA coherent memory area that the HDLCD can use as
+ framebuffer instead of normal CMA memory.
+ - interrupts: One interrupt used by the display controller to notify the
+ interrupt controller when any of the interrupt sources programmed in
+ the interrupt mask register have activated.
+ - clocks: A list of phandle + clock-specifier pairs, one for each
+ entry in 'clock-names'.
+ - clock-names: A list of clock names. For HDLD it should contain:
+ - "pxlclk" for the clock feeding the output PLL of the controller.
+
+Optional nodes:
+ - port: The HDLCD connection to an encoder chip. The connection is modeled
+ using the OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
+ If no port node is specified then the driver will assume that it is
+ running an emulated device.
+ - display-timings: If the HDLCD is emulated by the Fast Model, place the
+ videomode(s) of the desired resolution(s) here. If multiple videomodes
+ are being used, a 'native-mode' property should be used to indicate the
+ preferred/default resolution. Refer to
+ Documentation/devicetree/bindings/video/display-timing.txt for details.
+
+
+Example:
+
+/ {
+ ...
+
+ /* Emulated device */
+ hdlcd@7ff60000 {
+ compatible = "arm,hdlcd";
+ reg = <0 0x7ff60000 0 0x1000>;
+ interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
+
+ display-timings {
+ native-mode = <&timing0>;
+ timing0: timing@0 {
+ /* 1024 x 768 framebufer, standard VGA timings */
+ clock-frequency = <65000>;
+ hactive = <1024>;
+ vactive = <768>;
+ hfront-porch = <24>;
+ hback-porch = <160>;
+ hsync-len = <136>;
+ vfront-porch = <3>;
+ vback-porch = <29>;
+ vsync-len = <6>;
+ };
+ };
+ };
+
+ /* Physical device */
+ hdlcd@2b000000 {
+ compatible = "arm,hdlcd";
+ reg = <0 0x2b000000 0 0x1000>;
+ interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&oscclk5>;
+ clock-names = "pxlclk";
+ port {
+ hdlcd_output: endpoint@0 {
+ remote-endpoint = <&tda998x_1_input>;
+ };
+ };
+ };
+
+ /* HDMI I2C bus */
+ i2c@7ffa0000 {
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0x7ffa0000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;
+ clock-frequency = <400000>;
+ i2c-sda-hold-time-ns = <500>;
+ clocks = <&soc_smc50mhz>;
+
+ hdmi-transmitter@70 {
+ compatible = "nxp,tda998x";
+ reg = <0x70>;
+ video-ports = <0x234501>;
+ port@0 {
+ tda998x_1_input: endpoint {
+ remote-endpoint = <&hdlcd_output>;
+ };
+
+ tda998x_1_output: endpoint {
+ remote-endpoint = <&hdmi_1_port>;
+ };
+ };
+ };
+
+ };
+
+ hdmi1: connector@1 {
+ compatible = "hdmi-connector";
+ type = "a";
+ port {
+ hdmi_1_port: endpoint {
+ remote-endpoint = <&tda998x_1_output>;
+ };
+ };
+ };
+
+ ...
+};
diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi
index 21b02874bea3..f55784037021 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 9420053acc14..d4331294741b 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 = <&oscclk5>;
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 {
@@ -281,3 +297,17 @@
/include/ "vexpress-v2m-rs1.dtsi"
};
};
+
+&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 17f63f7dfd9e..c2ba4d10e41f 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
@@ -111,6 +111,22 @@
interrupts = <0 85 4>;
clocks = <&oscclk5>;
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 {
@@ -639,3 +655,17 @@
/include/ "vexpress-v2m-rs1.dtsi"
};
};
+
+&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 d2709b73316b..83815ee3dda5 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 = <&oscclk3>;
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 {
@@ -251,3 +267,17 @@
/include/ "vexpress-v2m-rs1.dtsi"
};
};
+
+&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 aa8c66ad0d5c..ab7d6bb8f1c2 100644
--- a/arch/arm64/boot/dts/arm/juno-base.dtsi
+++ b/arch/arm64/boot/dts/arm/juno-base.dtsi
@@ -100,7 +100,7 @@
/include/ "juno-clocks.dtsi"
- dma@7ff00000 {
+ dma0: dma@7ff00000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0x0 0x7ff00000 0 0x1000>;
#dma-cells = <1>;
@@ -117,6 +117,34 @@
clocks = <&soc_faxiclk>;
clock-names = "apb_pclk";
};
+/*
+ hdlcd@7ff50000 {
+ compatible = "arm,hdlcd";
+ reg = <0 0x7ff50000 0 0x1000>;
+ interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&scpi_clk 3>;
+ clock-names = "pxlclk";
+
+ port {
+ hdlcd1_output: endpoint@0 {
+ remote-endpoint = <&tda998x_1_input>;
+ };
+ };
+ };
+*/
+ hdlcd@7ff60000 {
+ compatible = "arm,hdlcd";
+ reg = <0 0x7ff60000 0 0x1000>;
+ interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&scpi_clk 4>;
+ clock-names = "pxlclk";
+
+ port {
+ hdlcd0_output: endpoint@0 {
+ remote-endpoint = <&tda998x_0_input>;
+ };
+ };
+ };
soc_uart0: uart@7ff80000 {
compatible = "arm,pl011", "arm,primecell";
@@ -132,18 +160,39 @@
#address-cells = <1>;
#size-cells = <0>;
interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;
- clock-frequency = <400000>;
+ clock-frequency = <100000>;
i2c-sda-hold-time-ns = <500>;
clocks = <&soc_smc50mhz>;
- dvi0: dvi-transmitter@70 {
+ hdmi_transmitter0: hdmi-transmitter@70 {
compatible = "nxp,tda998x";
reg = <0x70>;
+ audio-ports = <0x03>, <0x04>;
+ audio-port-names = "i2s", "spdif";
+ #sound-dai-cells = <1>;
+ port {
+ tda998x_0_input: endpoint@0 {
+ remote-endpoint = <&hdlcd0_output>;
+ };
+
+ tda998x_0_output: endpoint@1 {
+ remote-endpoint = <&hdmi0_connector_output>;
+ };
+ };
};
- dvi1: dvi-transmitter@71 {
+ hdmi-transmitter@71 {
compatible = "nxp,tda998x";
reg = <0x71>;
+ port {
+/* tda998x_1_input: endpoint@0 {
+ remote-endpoint = <&hdlcd1_output>;
+ };
+*/
+ tda998x_1_output: endpoint@1 {
+ remote-endpoint = <&hdmi1_connector_output>;
+ };
+ };
};
};
@@ -177,6 +226,57 @@
<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>;
+ status = "okay";
+ };
+
+ 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 0>;
+ };
+
+ };
+
+ hdmi0: connector@0 {
+ compatible = "hdmi-connector";
+ type = "a";
+ port {
+ hdmi0_connector_output: endpoint {
+ remote-endpoint = <&tda998x_0_output>;
+ };
+ };
+ };
+
+ hdmi1: connector@1 {
+ compatible = "hdmi-connector";
+ type = "a";
+ port {
+ hdmi1_connector_output: endpoint {
+ remote-endpoint = <&tda998x_1_output>;
+ };
+ };
+ };
+
smb {
compatible = "simple-bus";
#address-cells = <2>;
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 1a0a8df2eed8..d133638f5baf 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -106,6 +106,8 @@ config DRM_TDFX
Choose this option if you have a 3dfx Banshee or Voodoo3 (or later),
graphics card. If M is selected, the module will be called tdfx.
+source "drivers/gpu/drm/arm/Kconfig"
+
config DRM_R128
tristate "ATI Rage 128"
depends on DRM && PCI
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 45e7719846b1..6fee5269a64b 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -32,6 +32,7 @@ CFLAGS_drm_trace_points.o := -I$(src)
obj-$(CONFIG_DRM) += drm.o
obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
+obj-$(CONFIG_DRM_ARM) += arm/
obj-$(CONFIG_DRM_TTM) += ttm/
obj-$(CONFIG_DRM_TDFX) += tdfx/
obj-$(CONFIG_DRM_R128) += r128/
diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig
new file mode 100644
index 000000000000..8de5b511a963
--- /dev/null
+++ b/drivers/gpu/drm/arm/Kconfig
@@ -0,0 +1,26 @@
+config DRM_ARM
+ bool "ARM Ltd. drivers"
+ depends on DRM && OF && (ARM || ARM64)
+ select DMA_CMA
+ select DRM_KMS_HELPER
+ select DRM_KMS_CMA_HELPER
+ select DRM_GEM_CMA_HELPER
+ select VIDEOMODE_HELPERS
+ help
+ Choose this option to select drivers for ARM's devices
+
+config DRM_HDLCD
+ tristate "ARM HDLCD"
+ depends on DRM_ARM
+ select I2C
+ help
+ Choose this option if you have an ARM High Definition Colour LCD
+ controller.
+
+ If M is selected the module will be called hdlcd.
+
+config DRM_VIRTUAL_HDLCD
+ bool "Support for virtual HDLCD"
+ depends on DRM_HDLCD
+ help
+ Enable support for virtual HDLCD as emulated by ARM's Fast Models.
diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile
new file mode 100644
index 000000000000..9a96f9b6340d
--- /dev/null
+++ b/drivers/gpu/drm/arm/Makefile
@@ -0,0 +1,4 @@
+
+hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_vexpress_encoder.o hdlcd_fb_helper.o
+hdlcd-$(CONFIG_DRM_VIRTUAL_HDLCD) += hdlcd_virt_encoder.o
+obj-$(CONFIG_DRM_HDLCD) += hdlcd.o
diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c
new file mode 100644
index 000000000000..aa435cb06a21
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_crtc.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2013-2015 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * 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.
+ *
+ * Implementation of a CRTC class for the HDLCD driver.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_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>
+#include <linux/clk.h>
+#include <linux/of_graph.h>
+#include <linux/platform_data/simplefb.h>
+
+#include "hdlcd_drv.h"
+#include "hdlcd_regs.h"
+
+/*
+ * The HDLCD controller is a dumb RGB streamer that gets connected to
+ * a single HDMI transmitter or in the case of the ARM Models it gets
+ * emulated by the software that does the actual rendering.
+ *
+ */
+static void hdlcd_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+
+ of_node_put(hdlcd->crtc.port);
+ drm_crtc_cleanup(crtc);
+}
+
+void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait)
+{
+ struct drm_framebuffer *fb = hdlcd->crtc.primary->fb;
+ struct drm_gem_cma_object *gem;
+ unsigned int depth, bpp;
+ dma_addr_t scanout_start;
+
+ drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
+ gem = hdlcd_fb_get_gem_obj(fb, 0);
+
+ scanout_start = gem->paddr + fb->offsets[0] +
+ (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8);
+
+ hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
+
+ if (wait && hdlcd->dpms == DRM_MODE_DPMS_ON) {
+ drm_vblank_get(fb->dev, 0);
+ /* Clear any interrupt that may be from before we changed scanout */
+ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END);
+ /* Wait for next interrupt so we know scanout change is live */
+ reinit_completion(&hdlcd->frame_completion);
+ wait_for_completion_interruptible(&hdlcd->frame_completion);
+
+ drm_vblank_put(fb->dev, 0);
+ }
+}
+
+static int hdlcd_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event,
+ uint32_t flags)
+{
+ struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+
+ if (hdlcd->dpms == DRM_MODE_DPMS_ON) {
+ /* don't schedule any page flipping if one is in progress */
+ if (hdlcd->event)
+ return -EBUSY;
+
+ hdlcd->event = event;
+ drm_vblank_get(crtc->dev, 0);
+ }
+
+ crtc->primary->fb = fb;
+
+ if (hdlcd->dpms == DRM_MODE_DPMS_ON) {
+ hdlcd_set_scanout(hdlcd, true);
+ } else {
+ unsigned long flags;
+
+ /* not active, update registers immediately */
+ hdlcd_set_scanout(hdlcd, false);
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ if (event)
+ drm_send_vblank_event(crtc->dev, 0, event);
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+ }
+
+ return 0;
+}
+
+static const struct drm_crtc_funcs hdlcd_crtc_funcs = {
+ .destroy = hdlcd_crtc_destroy,
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = hdlcd_crtc_page_flip,
+};
+
+static void hdlcd_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+
+ hdlcd->dpms = mode;
+ if (mode == DRM_MODE_DPMS_ON)
+ hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1);
+ else
+ hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0);
+}
+
+static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void hdlcd_crtc_prepare(struct drm_crtc *crtc)
+{
+ hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
+
+static void hdlcd_crtc_commit(struct drm_crtc *crtc)
+{
+ drm_vblank_post_modeset(crtc->dev, 0);
+ hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static struct simplefb_format supported_formats[] = SIMPLEFB_FORMATS;
+
+static int hdlcd_crtc_colour_set(struct hdlcd_drm_private *hdlcd,
+ uint32_t pixel_format)
+{
+ unsigned int depth, bpp;
+ unsigned int default_color = 0x00000000;
+ struct simplefb_format *format = NULL;
+ int i;
+
+#ifdef HDLCD_SHOW_UNDERRUN
+ default_color = 0x00ff0000; /* show underruns in red */
+#endif
+
+ /* Calculate each colour's number of bits */
+ drm_fb_get_bpp_depth(pixel_format, &depth, &bpp);
+
+ for (i = 0; i < ARRAY_SIZE(supported_formats); i++) {
+ if (supported_formats[i].fourcc == pixel_format)
+ format = &supported_formats[i];
+ }
+
+ if (!format) {
+ DRM_ERROR("Format not supported: 0x%x\n", pixel_format);
+ return -EINVAL;
+ }
+
+ /* HDLCD uses 'bytes per pixel' */
+ bpp = (bpp + 7) / 8;
+ hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (bpp - 1) << 3);
+
+ /*
+ * The format of the HDLCD_REG_<color>_SELECT register is:
+ * - bits[23:16] - default value for that color component
+ * - bits[11:8] - number of bits to extract for each color component
+ * - bits[4:0] - index of the lowest bit to extract
+ *
+ * The default color value is used when bits[11:8] read zero, when the
+ * pixel is outside the visible frame area or when there is a
+ * buffer underrun.
+ */
+ if(!config_enabled(CONFIG_ARM)) {
+ hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color |
+ format->red.offset | (format->red.length & 0xf) << 8);
+ hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color |
+ format->green.offset | (format->green.length & 0xf) << 8);
+ hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color |
+ format->blue.offset | (format->blue.length & 0xf) << 8);
+ } else {
+ /*
+ * This is a hack to swap read and blue when building for
+ * 32-bit ARM, because Versatile Express motherboard seems
+ * to be wired up differently.
+ */
+ hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color |
+ format->red.offset | (format->red.length & 0xf) << 8);
+ hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color |
+ format->green.offset | (format->green.length & 0xf) << 8);
+ hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color |
+ format->blue.offset | (format->blue.length & 0xf) << 8);
+ }
+ return 0;
+}
+
+static int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+ hdlcd_set_scanout(hdlcd, true);
+ return 0;
+}
+
+
+static int hdlcd_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y, struct drm_framebuffer *oldfb)
+{
+ struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+ unsigned int depth, bpp, polarities, line_length, err;
+
+ drm_vblank_pre_modeset(crtc->dev, 0);
+
+ polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA;
+
+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)
+ polarities |= HDLCD_POLARITY_HSYNC;
+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ polarities |= HDLCD_POLARITY_VSYNC;
+
+ drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp);
+ /* switch to the more useful 'bytes per pixel' HDLCD needs */
+ bpp = (bpp + 7) / 8;
+ line_length = crtc->primary->fb->width * bpp;
+
+ /* Allow max number of outstanding requests and largest burst size */
+ hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS,
+ HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16);
+
+ hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, line_length);
+ hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT,
+ crtc->primary->fb->height - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, line_length);
+ hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH,
+ mode->vtotal - mode->vsync_end - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH,
+ mode->vsync_start - mode->vdisplay - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_V_SYNC,
+ mode->vsync_end - mode->vsync_start - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_V_DATA, mode->vdisplay - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH,
+ mode->htotal - mode->hsync_end - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH,
+ mode->hsync_start - mode->hdisplay - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_H_SYNC,
+ mode->hsync_end - mode->hsync_start - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_H_DATA, mode->hdisplay - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities);
+
+ err = hdlcd_crtc_colour_set(hdlcd, crtc->primary->fb->pixel_format);
+ if (err)
+ return err;
+
+ clk_prepare(hdlcd->clk);
+ clk_set_rate(hdlcd->clk, mode->crtc_clock * 1000);
+ clk_enable(hdlcd->clk);
+
+ hdlcd_set_scanout(hdlcd, false);
+
+ return 0;
+}
+
+static void hdlcd_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = {
+ .dpms = hdlcd_crtc_dpms,
+ .mode_fixup = hdlcd_crtc_mode_fixup,
+ .prepare = hdlcd_crtc_prepare,
+ .commit = hdlcd_crtc_commit,
+ .mode_set = hdlcd_crtc_mode_set,
+ .mode_set_base = hdlcd_crtc_mode_set_base,
+ .load_lut = hdlcd_crtc_load_lut,
+};
+
+int hdlcd_setup_crtc(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ int ret;
+
+ hdlcd->crtc.port = of_graph_get_next_endpoint(dev->platformdev->dev.of_node, NULL);
+ if (!hdlcd->crtc.port)
+ return -ENXIO;
+
+ ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs);
+ if (ret < 0) {
+ of_node_put(hdlcd->crtc.port);
+ return ret;
+ }
+
+ drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs);
+ return 0;
+}
+
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c
new file mode 100644
index 000000000000..e9d8a63666ad
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_drv.c
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2013-2015 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * 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.
+ *
+ * ARM HDLCD Driver
+ */
+
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of_graph.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include "hdlcd_fb_helper.h"
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+
+#include "hdlcd_drv.h"
+#include "hdlcd_regs.h"
+
+static void hdlcd_setup_mode_config(struct drm_device *dev);
+
+static int compare_dev(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+struct drm_encoder *
+hdlcd_connector_best_encoder(struct drm_connector *connector)
+{
+ int enc_id = connector->encoder_ids[0];
+ struct drm_mode_object *obj;
+ struct drm_encoder *encoder;
+
+ if (connector->encoder)
+ return connector->encoder;
+
+ if (enc_id) {
+ obj = drm_mode_object_find(connector->dev, enc_id,
+ DRM_MODE_OBJECT_ENCODER);
+ if (obj) {
+ encoder = obj_to_encoder(obj);
+ return encoder;
+ }
+ }
+ return NULL;
+
+}
+
+static int hdlcd_unload(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+ drm_kms_helper_poll_fini(dev);
+ if (hdlcd->fbdev)
+ hdlcd_drm_fbdev_fini(hdlcd->fbdev);
+
+ drm_vblank_cleanup(dev);
+ drm_mode_config_cleanup(dev);
+
+ drm_irq_uninstall(dev);
+
+ if (!IS_ERR(hdlcd->clk))
+ clk_put(hdlcd->clk);
+
+ dma_release_declared_memory(dev->dev);
+ platform_set_drvdata(dev->platformdev, NULL);
+ dev->dev_private = NULL;
+
+ return 0;
+}
+
+/* Use colour depth that Android user-side is hard-coded to expect */
+static const int preferred_bpp = config_enabled(CONFIG_ARM) ? 16 : 32;
+
+static int hdlcd_load(struct drm_device *dev, unsigned long flags)
+{
+ struct platform_device *pdev = dev->platformdev;
+ struct hdlcd_drm_private *hdlcd;
+ struct resource *res;
+ u32 version;
+ int ret;
+
+ hdlcd = devm_kzalloc(dev->dev, sizeof(*hdlcd), GFP_KERNEL);
+ if (!hdlcd)
+ return -ENOMEM;
+
+#ifdef CONFIG_DEBUG_FS
+ atomic_set(&hdlcd->buffer_underrun_count, 0);
+ atomic_set(&hdlcd->bus_error_count, 0);
+ atomic_set(&hdlcd->vsync_count, 0);
+ atomic_set(&hdlcd->dma_end_count, 0);
+#endif
+ platform_set_drvdata(pdev, dev);
+ dev->dev_private = hdlcd;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdlcd->mmio = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(hdlcd->mmio)) {
+ DRM_ERROR("failed to map control registers area\n");
+ ret = PTR_ERR(hdlcd->mmio);
+ goto fail;
+ }
+
+ version = hdlcd_read(hdlcd, HDLCD_REG_VERSION);
+ if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) {
+ DRM_ERROR("unknown product id: 0x%x\n", version);
+ ret = -EINVAL;
+ goto fail;
+ }
+ DRM_INFO("found ARM HDLCD version r%dp%d\n",
+ (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 coherent memory resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ ret = dma_declare_coherent_memory(dev->dev, res->start, res->start,
+ resource_size(res), DMA_MEMORY_MAP);
+ if ((ret & DMA_MEMORY_MAP) == 0) {
+ DRM_ERROR("failed to declare coherent device memory\n");
+ ret = -ENXIO;
+ goto fail;
+ }
+ }
+
+ hdlcd_setup_mode_config(dev);
+ ret = hdlcd_setup_crtc(dev);
+ if (ret < 0) {
+ DRM_ERROR("failed to create crtc\n");
+ goto fail;
+ }
+
+ hdlcd->clk = clk_get(dev->dev, NULL);
+ if (IS_ERR(hdlcd->clk)) {
+ ret = PTR_ERR(hdlcd->clk);
+ goto fail;
+ }
+
+ ret = component_bind_all(dev->dev, dev);
+ if (ret) {
+ DRM_ERROR("Failed to bind all components\n");
+ goto fail;
+ }
+
+ drm_kms_helper_poll_init(dev);
+ drm_mode_config_reset(dev);
+
+ ret = drm_irq_install(dev, platform_get_irq(pdev, 0));
+ if (ret < 0) {
+ DRM_ERROR("failed to install IRQ handler\n");
+ goto fail;
+ }
+
+ dev->irq_enabled = true;
+ dev->vblank_disable_allowed = true;
+
+ ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
+ if (ret < 0) {
+ DRM_ERROR("failed to initialise vblank\n");
+ goto fail;
+ }
+
+ init_completion(&hdlcd->frame_completion);
+
+ hdlcd->fbdev = hdlcd_drm_fbdev_init(dev, preferred_bpp,
+ dev->mode_config.num_crtc,
+ dev->mode_config.num_connector);
+
+ if (IS_ERR(hdlcd->fbdev)) {
+ DRM_ERROR("failed to initialise fbdev buffer\n");
+ ret = PTR_ERR(hdlcd->fbdev);
+ hdlcd->fbdev = NULL;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ hdlcd_unload(dev);
+
+ return ret;
+}
+
+static void hdlcd_fb_output_poll_changed(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ if (hdlcd->fbdev) {
+ hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev);
+ }
+}
+
+static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
+ .fb_create = hdlcd_fb_create,
+ .output_poll_changed = hdlcd_fb_output_poll_changed,
+};
+
+static void hdlcd_setup_mode_config(struct drm_device *dev)
+{
+ drm_mode_config_init(dev);
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+ dev->mode_config.max_width = HDLCD_MAX_XRES;
+ dev->mode_config.max_height = HDLCD_MAX_YRES;
+ dev->mode_config.funcs = &hdlcd_mode_config_funcs;
+}
+
+static void hdlcd_preclose(struct drm_device *dev, struct drm_file *file)
+{
+}
+
+static void hdlcd_lastclose(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev);
+}
+
+static irqreturn_t hdlcd_irq(int irq, void *arg)
+{
+ struct drm_device *dev = arg;
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ unsigned long irq_status;
+
+ irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS);
+
+#ifdef CONFIG_DEBUG_FS
+ if (irq_status & HDLCD_INTERRUPT_UNDERRUN) {
+ atomic_inc(&hdlcd->buffer_underrun_count);
+ }
+ if (irq_status & HDLCD_INTERRUPT_DMA_END) {
+ atomic_inc(&hdlcd->dma_end_count);
+ }
+ if (irq_status & HDLCD_INTERRUPT_BUS_ERROR) {
+ atomic_inc(&hdlcd->bus_error_count);
+ }
+ if (irq_status & HDLCD_INTERRUPT_VSYNC) {
+ atomic_inc(&hdlcd->vsync_count);
+ }
+#endif
+ if (irq_status & HDLCD_INTERRUPT_VSYNC) {
+ struct drm_pending_vblank_event *event;
+ unsigned long flags;
+
+ drm_handle_vblank(dev, 0);
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ if (hdlcd->event) {
+ event = hdlcd->event;
+ hdlcd->event = NULL;
+ drm_send_vblank_event(dev, 0, event);
+ drm_vblank_put(dev, 0);
+ }
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ }
+ if (irq_status & HDLCD_INTERRUPT_DMA_END) {
+ // send completion when reading the frame has finished
+ complete_all(&hdlcd->frame_completion);
+ }
+
+ /* acknowledge interrupt(s) */
+ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status);
+
+ return IRQ_HANDLED;
+}
+
+static void hdlcd_irq_preinstall(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ /* Ensure interrupts are disabled */
+ hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0);
+ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, ~0);
+}
+
+static int hdlcd_irq_postinstall(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->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);
+
+ return 0;
+}
+
+static void hdlcd_irq_uninstall(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ /* disable all the interrupts that we might have enabled */
+ unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+#ifdef CONFIG_DEBUG_FS
+ /* disable debug interrupts */
+ irq_mask &= ~HDLCD_DEBUG_INT_MASK;
+#endif
+
+ /* disable vsync and dma interrupts */
+ irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END);
+
+ hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask);
+}
+
+static int hdlcd_enable_vblank(struct drm_device *dev, int crtc)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+ hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask | HDLCD_INTERRUPT_VSYNC);
+
+ return 0;
+}
+
+static void hdlcd_disable_vblank(struct drm_device *dev, int crtc)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+ hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask & ~HDLCD_INTERRUPT_VSYNC);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int hdlcd_show_underrun_count(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 hdlcd_drm_private *hdlcd = dev->dev_private;
+
+ seq_printf(m, "underrun : %d\n", atomic_read(&hdlcd->buffer_underrun_count));
+ seq_printf(m, "dma_end : %d\n", atomic_read(&hdlcd->dma_end_count));
+ seq_printf(m, "bus_error: %d\n", atomic_read(&hdlcd->bus_error_count));
+ seq_printf(m, "vsync : %d\n", atomic_read(&hdlcd->vsync_count));
+ return 0;
+}
+
+static struct drm_info_list hdlcd_debugfs_list[] = {
+ { "interrupt_count", hdlcd_show_underrun_count, 0 },
+};
+
+static int hdlcd_debugfs_init(struct drm_minor *minor)
+{
+ return drm_debugfs_create_files(hdlcd_debugfs_list,
+ ARRAY_SIZE(hdlcd_debugfs_list), minor->debugfs_root, minor);
+}
+
+static void hdlcd_debugfs_cleanup(struct drm_minor *minor)
+{
+ drm_debugfs_remove_files(hdlcd_debugfs_list,
+ ARRAY_SIZE(hdlcd_debugfs_list), minor);
+}
+#endif
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .poll = drm_poll,
+ .read = drm_read,
+ .llseek = no_llseek,
+ .mmap = drm_gem_cma_mmap,
+};
+
+static struct drm_driver hdlcd_driver = {
+ .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM |
+ DRIVER_MODESET | DRIVER_PRIME,
+ .load = hdlcd_load,
+ .unload = hdlcd_unload,
+ .preclose = hdlcd_preclose,
+ .lastclose = hdlcd_lastclose,
+ .irq_handler = hdlcd_irq,
+ .irq_preinstall = hdlcd_irq_preinstall,
+ .irq_postinstall = hdlcd_irq_postinstall,
+ .irq_uninstall = hdlcd_irq_uninstall,
+ .set_busid = drm_platform_set_busid,
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = hdlcd_enable_vblank,
+ .disable_vblank = hdlcd_disable_vblank,
+ .gem_free_object = drm_gem_cma_free_object,
+ .gem_vm_ops = &drm_gem_cma_vm_ops,
+ .dumb_create = drm_gem_cma_dumb_create,
+ .dumb_map_offset = drm_gem_cma_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 = drm_gem_cma_prime_get_sg_table,
+ .gem_prime_vmap = drm_gem_cma_prime_vmap,
+ .gem_prime_vunmap = drm_gem_cma_prime_vunmap,
+ .gem_prime_mmap = drm_gem_cma_prime_mmap,
+#ifdef CONFIG_DEBUG_FS
+ .debugfs_init = hdlcd_debugfs_init,
+ .debugfs_cleanup = hdlcd_debugfs_cleanup,
+#endif
+ .fops = &fops,
+ .name = "hdlcd",
+ .desc = "ARM HDLCD Controller DRM",
+ .date = "20130505",
+ .major = 1,
+ .minor = 0,
+};
+
+static int hdlcd_add_components(struct device *dev, struct master *master)
+{
+ struct device_node *port, *ep = NULL;
+ int ret = -ENXIO;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ do {
+ ep = of_graph_get_next_endpoint(dev->of_node, ep);
+ if (!ep)
+ break;
+
+ if (!of_device_is_available(ep)) {
+ of_node_put(ep);
+ continue;
+ }
+
+ port = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!port || !of_device_is_available(port)) {
+ of_node_put(port);
+ continue;
+ }
+
+ ret = component_master_add_child(master, compare_dev, port);
+ of_node_put(port);
+ } while (1);
+
+ return ret;
+}
+
+static int hdlcd_drm_bind(struct device *dev)
+{
+ return drm_platform_init(&hdlcd_driver, to_platform_device(dev));
+}
+
+static void hdlcd_drm_unbind(struct device *dev)
+{
+ drm_put_dev(dev_get_drvdata(dev));
+}
+
+static const struct component_master_ops hdlcd_master_ops = {
+ .add_components = hdlcd_add_components,
+ .bind = hdlcd_drm_bind,
+ .unbind = hdlcd_drm_unbind,
+};
+
+static int hdlcd_probe(struct platform_device *pdev)
+{
+ return component_master_add(&pdev->dev, &hdlcd_master_ops);
+}
+
+static int hdlcd_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &hdlcd_master_ops);
+ return 0;
+}
+
+static struct of_device_id hdlcd_of_match[] = {
+ { .compatible = "arm,hdlcd" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, hdlcd_of_match);
+
+static struct platform_driver hdlcd_platform_driver = {
+ .probe = hdlcd_probe,
+ .remove = hdlcd_remove,
+ .driver = {
+ .name = "hdlcd",
+ .owner = THIS_MODULE,
+ .of_match_table = hdlcd_of_match,
+ },
+};
+
+static int __init hdlcd_init(void)
+{
+ int err = platform_driver_register(&hdlcd_platform_driver);
+
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+ if (!err)
+ hdlcd_underrun_init();
+#endif
+
+ return err;
+}
+
+static void __exit hdlcd_exit(void)
+{
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+ hdlcd_underrun_close();
+#endif
+ 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");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h
new file mode 100644
index 000000000000..3980276d77bc
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_drv.h
@@ -0,0 +1,69 @@
+/*
+ * ARM HDLCD Controller register definition
+ */
+
+#ifndef __HDLCD_DRV_H__
+#define __HDLCD_DRV_H__
+
+struct hdlcd_drm_private {
+ void __iomem *mmio;
+ struct clk *clk;
+ struct hdlcd_drm_fbdev *fbdev;
+ struct drm_framebuffer *fb;
+ struct drm_pending_vblank_event *event;
+ struct drm_crtc crtc;
+ struct completion frame_completion;
+#ifdef CONFIG_DEBUG_FS
+ atomic_t buffer_underrun_count;
+ atomic_t bus_error_count;
+ atomic_t vsync_count;
+ atomic_t dma_end_count;
+#endif
+ int dpms;
+};
+
+#define crtc_to_hdlcd_priv(x) container_of(x, struct hdlcd_drm_private, crtc)
+
+static inline void
+hdlcd_write(struct hdlcd_drm_private *hdlcd, unsigned int reg, u32 value)
+{
+ writel(value, hdlcd->mmio + reg);
+}
+
+static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg)
+{
+ return readl(hdlcd->mmio + reg);
+}
+
+/*
+ * Developers using HDLCD may wish to enable these settings if
+ * display disruption is apparent and you suspect HDLCD
+ * access to RAM may be starved.
+ *
+ * Turn HDLCD default color to red instead of default black so
+ * that it's easier to see data underruns (compared to other
+ * visual disruptions)
+ */
+//#define HDLCD_SHOW_UNDERRUN
+
+/* setup the crtc subclass */
+int hdlcd_setup_crtc(struct drm_device *dev);
+
+/* functions for creating a suitable connector */
+extern int hdlcd_create_digital_connector(struct drm_device *dev,
+ struct hdlcd_drm_private *hdlcd);
+extern int hdlcd_create_vexpress_connector(struct drm_device *dev,
+ struct hdlcd_drm_private *hdlcd);
+#ifdef CONFIG_DRM_VIRTUAL_HDLCD
+extern int hdlcd_create_virtual_connector(struct drm_device *dev);
+#else
+static inline int hdlcd_create_virtual_connector(struct drm_device *dev)
+{
+ return -ENXIO;
+}
+#endif
+
+/* common function used by all connectors */
+extern struct drm_encoder *hdlcd_connector_best_encoder(struct drm_connector *con);
+
+#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..8958d88e6b90
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c
@@ -0,0 +1,636 @@
+/*
+ * Copied from drivers/gpu/drm/drm_fb_cma_helper.c which has the following
+ * copyright and notices...
+ *
+ * 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_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/module.h>
+#include "hdlcd_drv.h"
+
+#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 3.18
+ ******************************************************************************/
+
+/**
+ * Copy of drm_fb_helper_check_var modified to allow MAX_FRAMES * height
+ */
+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;
+
+ /* Need to resize the fb object !!! */
+ if (var->bits_per_pixel > fb->bits_per_pixel ||
+ var->xres > fb->width || var->yres > fb->height ||
+ var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) {
+ DRM_DEBUG("fb userspace requested width/height/bpp is greater than 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->bits_per_pixel);
+ 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 3.18
+ ******************************************************************************/
+
+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;
+};
+
+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);
+}
+
+static 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);
+}
+
+static 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);
+}
+
+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,
+ struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_cma_object **obj,
+ unsigned int num_planes)
+{
+ 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(&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, &hdlcd_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() - (struct drm_mode_config_funcs *)->fb_create callback function
+ *
+ * If your hardware has special alignment or pitch requirements these should be
+ * checked before calling this function.
+ */
+struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct hdlcd_fb *hdlcd_fb;
+ struct drm_gem_cma_object *objs[4];
+ struct drm_gem_object *obj;
+ unsigned int hsub;
+ unsigned int vsub;
+ int ret;
+ int i;
+
+ hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
+ vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
+
+ for (i = 0; i < drm_format_num_planes(mode_cmd->pixel_format); i++) {
+ unsigned int width = mode_cmd->width / (i ? hsub : 1);
+ unsigned int height = mode_cmd->height / (i ? vsub : 1);
+ unsigned int min_size;
+
+ obj = drm_gem_object_lookup(dev, 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 * drm_format_plane_cpp(mode_cmd->pixel_format, 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);
+ 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);
+
+/**
+ * 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);
+
+#ifdef CONFIG_DEBUG_FS
+/*
+ * hdlcd_fb_describe() - Helper to dump information about a single
+ * CMA framebuffer object
+ */
+static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m)
+{
+ struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb);
+ int i, n = drm_format_num_planes(fb->pixel_format);
+
+ seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height,
+ (char *)&fb->pixel_format);
+
+ for (i = 0; i < n; 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.
+ */
+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;
+ int ret;
+
+ ret = mutex_lock_interruptible(&dev->mode_config.mutex);
+ if (ret)
+ return ret;
+
+ ret = mutex_lock_interruptible(&dev->struct_mutex);
+ if (ret) {
+ mutex_unlock(&dev->mode_config.mutex);
+ return ret;
+ }
+
+ list_for_each_entry(fb, &dev->mode_config.fb_list, head)
+ hdlcd_fb_describe(fb, m);
+
+ mutex_unlock(&dev->struct_mutex);
+ mutex_unlock(&dev->mode_config.mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show);
+#endif
+
+static struct fb_ops hdlcd_drm_fbdev_ops = {
+ .owner = THIS_MODULE,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_check_var = hdlcd_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_setcmap = drm_fb_helper_setcmap,
+ .fb_ioctl = hdlcd_fb_ioctl,
+ .fb_compat_ioctl= hdlcd_fb_ioctl,
+};
+
+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 = framebuffer_alloc(0, dev->dev);
+ if (!fbi) {
+ dev_err(dev->dev, "Failed to allocate framebuffer info.\n");
+ ret = -ENOMEM;
+ goto err_drm_gem_cma_free_object;
+ }
+
+ hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1);
+ if (IS_ERR(hdlcd_fbdev->fb)) {
+ dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
+ ret = PTR_ERR(hdlcd_fbdev->fb);
+ goto err_framebuffer_release;
+ }
+
+ fb = &hdlcd_fbdev->fb->fb;
+ helper->fb = fb;
+ helper->fbdev = fbi;
+
+ fbi->par = helper;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->fbops = &hdlcd_drm_fbdev_ops;
+
+ ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
+ if (ret) {
+ dev_err(dev->dev, "Failed to allocate color map.\n");
+ goto err_hdlcd_fb_destroy;
+ }
+
+ drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
+ drm_fb_helper_fill_var(fbi, helper, fb->width, 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;
+
+ return 0;
+
+err_hdlcd_fb_destroy:
+ drm_framebuffer_unregister_private(fb);
+ hdlcd_fb_destroy(fb);
+err_framebuffer_release:
+ framebuffer_release(fbi);
+err_drm_gem_cma_free_object:
+ drm_gem_cma_free_object(&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() - 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 num_crtc,
+ unsigned int max_conn_count)
+{
+ 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);
+ }
+
+ helper = &hdlcd_fbdev->fb_helper;
+
+ drm_fb_helper_prepare(dev, helper, &hdlcd_fb_helper_funcs);
+
+ ret = drm_fb_helper_init(dev, helper, num_crtc, 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;
+
+ }
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ drm_helper_disable_unused_functions(dev);
+
+ 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);
+
+/**
+ * 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)
+{
+ if (hdlcd_fbdev->fb_helper.fbdev) {
+ struct fb_info *info;
+ int ret;
+
+ info = hdlcd_fbdev->fb_helper.fbdev;
+ ret = unregister_framebuffer(info);
+ if (ret < 0)
+ DRM_DEBUG_KMS("failed unregister_framebuffer()\n");
+
+ if (info->cmap.len)
+ fb_dealloc_cmap(&info->cmap);
+
+ framebuffer_release(info);
+ }
+
+ if (hdlcd_fbdev->fb) {
+ drm_framebuffer_unregister_private(&hdlcd_fbdev->fb->fb);
+ hdlcd_fb_destroy(&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 drivers 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 drivers 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);
+
+
+/******************************************************************************
+ * 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..23e268db972f
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h
@@ -0,0 +1,31 @@
+#ifndef __DRM_FB_CMA_HELPER_H__
+#define __DRM_FB_CMA_HELPER_H__
+
+struct hdlcd_drm_fbdev;
+struct drm_gem_cma_object;
+
+struct drm_framebuffer;
+struct drm_device;
+struct drm_file;
+struct drm_mode_fb_cmd2;
+
+struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev,
+ unsigned int preferred_bpp, unsigned int num_crtc,
+ 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);
+
+struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd);
+
+struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned int plane);
+
+#ifdef CONFIG_DEBUG_FS
+int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg);
+#endif
+
+#endif
+
diff --git a/drivers/gpu/drm/arm/hdlcd_regs.h b/drivers/gpu/drm/arm/hdlcd_regs.h
new file mode 100644
index 000000000000..66799ebef6d3
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_regs.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013,2014 ARM Limited
+ *
+ * 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.
+ *
+ * ARM HDLCD Controller register definition
+ */
+
+#ifndef __HDLCD_REGS_H__
+#define __HDLCD_REGS_H__
+
+/* register offsets */
+#define HDLCD_REG_VERSION 0x0000 /* ro */
+#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */
+#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */
+#define HDLCD_REG_INT_MASK 0x0018 /* rw */
+#define HDLCD_REG_INT_STATUS 0x001c /* ro */
+#define HDLCD_REG_FB_BASE 0x0100 /* rw */
+#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */
+#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */
+#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */
+#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */
+#define HDLCD_REG_V_SYNC 0x0200 /* rw */
+#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */
+#define HDLCD_REG_V_DATA 0x0208 /* rw */
+#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */
+#define HDLCD_REG_H_SYNC 0x0210 /* rw */
+#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */
+#define HDLCD_REG_H_DATA 0x0218 /* rw */
+#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */
+#define HDLCD_REG_POLARITIES 0x0220 /* rw */
+#define HDLCD_REG_COMMAND 0x0230 /* rw */
+#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */
+#define HDLCD_REG_RED_SELECT 0x0244 /* rw */
+#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */
+#define HDLCD_REG_BLUE_SELECT 0x024c /* rw */
+
+/* version */
+#define HDLCD_PRODUCT_ID 0x1CDC0000
+#define HDLCD_PRODUCT_MASK 0xFFFF0000
+#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00
+#define HDLCD_VERSION_MINOR_MASK 0x000000FF
+
+/* interrupts */
+#define HDLCD_INTERRUPT_DMA_END (1 << 0)
+#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1)
+#define HDLCD_INTERRUPT_VSYNC (1 << 2)
+#define HDLCD_INTERRUPT_UNDERRUN (1 << 3)
+#define HDLCD_DEBUG_INT_MASK (HDLCD_INTERRUPT_DMA_END | \
+ HDLCD_INTERRUPT_BUS_ERROR | \
+ HDLCD_INTERRUPT_UNDERRUN)
+
+/* polarities */
+#define HDLCD_POLARITY_VSYNC (1 << 0)
+#define HDLCD_POLARITY_HSYNC (1 << 1)
+#define HDLCD_POLARITY_DATAEN (1 << 2)
+#define HDLCD_POLARITY_DATA (1 << 3)
+#define HDLCD_POLARITY_PIXELCLK (1 << 4)
+
+/* commands */
+#define HDLCD_COMMAND_DISABLE (0 << 0)
+#define HDLCD_COMMAND_ENABLE (1 << 0)
+
+/* pixel format */
+#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31)
+#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31)
+#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3)
+
+/* bus options */
+#define HDLCD_BUS_BURST_MASK 0x01f
+#define HDLCD_BUS_MAX_OUTSTAND 0xf00
+#define HDLCD_BUS_BURST_NONE (0 << 0)
+#define HDLCD_BUS_BURST_1 (1 << 0)
+#define HDLCD_BUS_BURST_2 (1 << 1)
+#define HDLCD_BUS_BURST_4 (1 << 2)
+#define HDLCD_BUS_BURST_8 (1 << 3)
+#define HDLCD_BUS_BURST_16 (1 << 4)
+
+/* Max resolution supported is 4096x4096, 32bpp */
+#define HDLCD_MAX_XRES 4096
+#define HDLCD_MAX_YRES 4096
+
+#define NR_PALETTE 256
+
+#endif /* __HDLCD_REGS_H__ */
diff --git a/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c
new file mode 100644
index 000000000000..c03d367cdf9b
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2013,2014 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * 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 <linux/i2c.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include <linux/vexpress.h>
+
+#include "hdlcd_drv.h"
+
+/*
+ * use the functionality of vexpress-config to set the output mode
+ * for HDLCD on Versatile Express boards that lack proper control
+ * of the DDC i2c chip.
+ */
+
+//static struct vexpress_config_func *vconfig_func;
+
+/*
+ * Predefined modes that are available through the VExpress micro
+ */
+static const struct {
+ int hsize, vsize, vrefresh, dvimode;
+} vexpress_dvimodes[] = {
+ { 640, 480, 60, 0 }, /* VGA */
+ { 800, 600, 60, 1 }, /* SVGA */
+ { 1024, 768, 60, 2 }, /* XGA */
+ { 1280, 1024, 60, 3 }, /* SXGA */
+ { 1600, 1200, 60, 4 }, /* UXGA */
+ { 1920, 1080, 60, 5 }, /* HD1080 */
+};
+
+static void hdlcd_connector_destroy(struct drm_connector *connector)
+{
+}
+
+static enum drm_connector_status
+hdlcd_connector_detect(struct drm_connector *connector, bool force)
+{
+ //if (vconfig_func)
+ return connector_status_connected;
+ //return connector_status_disconnected;
+}
+
+static const struct drm_connector_funcs hdlcd_connector_funcs = {
+ .destroy = hdlcd_connector_destroy,
+ .dpms = drm_helper_connector_dpms,
+ .detect = hdlcd_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+};
+
+static int hdlcd_vexpress_con_get_modes(struct drm_connector *connector)
+{
+ int i;
+ struct drm_display_mode *mode;
+
+ /* Add the predefined modes */
+ for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) {
+ mode = drm_mode_find_dmt(connector->dev,
+ vexpress_dvimodes[i].hsize,
+ vexpress_dvimodes[i].vsize,
+ vexpress_dvimodes[i].vrefresh, false);
+ if (!mode)
+ continue;
+ /* prefer the 1280x1024 mode */
+ if (i == 3)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ }
+
+ return i;
+}
+
+/*
+ * mode valid is only called for detected modes and we know that
+ * the restricted list is correct ;)
+ */
+static int hdlcd_vexpress_con_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs hdlcd_vexpress_con_helper_funcs = {
+ .get_modes = hdlcd_vexpress_con_get_modes,
+ .mode_valid = hdlcd_vexpress_con_mode_valid,
+ .best_encoder = hdlcd_connector_best_encoder,
+};
+
+
+static void hdlcd_vexpress_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ kfree(encoder);
+ /*if (vconfig_func)
+ vexpress_config_func_put(vconfig_func);*/
+}
+
+static const struct drm_encoder_funcs hdlcd_vexpress_encoder_funcs = {
+ .destroy = hdlcd_vexpress_encoder_destroy,
+};
+
+static void hdlcd_vexpress_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ /* VExpress micro has no support for DPMS */
+}
+
+static bool hdlcd_vexpress_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needs to be done here */
+ return true;
+}
+
+static void hdlcd_vexpress_encoder_prepare(struct drm_encoder *encoder)
+{
+ hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void hdlcd_vexpress_encoder_commit(struct drm_encoder *encoder)
+{
+ hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void hdlcd_vexpress_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /*if (vconfig_func) {
+ int i, vrefresh = drm_mode_vrefresh(mode);
+
+ for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) {
+ if (vexpress_dvimodes[i].hsize != mode->hdisplay)
+ continue;
+ if (vexpress_dvimodes[i].vsize != mode->vdisplay)
+ continue;
+ if (vexpress_dvimodes[i].vrefresh != vrefresh)
+ continue;
+
+ vexpress_config_write(vconfig_func, 0,
+ vexpress_dvimodes[i].dvimode);
+ return;
+ }
+ }*/
+}
+
+static const struct drm_encoder_helper_funcs
+hdlcd_vexpress_encoder_helper_funcs = {
+ .dpms = hdlcd_vexpress_encoder_dpms,
+ .mode_fixup = hdlcd_vexpress_encoder_mode_fixup,
+ .prepare = hdlcd_vexpress_encoder_prepare,
+ .commit = hdlcd_vexpress_encoder_commit,
+ .mode_set = hdlcd_vexpress_encoder_mode_set,
+};
+
+static const struct of_device_id vexpress_dvi_match[] = {
+ { .compatible = "arm,vexpress-dvimode" },
+ {}
+};
+
+int hdlcd_create_vexpress_connector(struct drm_device *dev,
+ struct hdlcd_drm_private *hdlcd)
+{
+ int err;
+ struct drm_connector *connector;
+ struct device_node *node;
+ struct drm_encoder *encoder;
+
+ node = of_find_matching_node(NULL, vexpress_dvi_match);
+ if (!node)
+ return -ENXIO;
+
+ /*
+ vconfig_func = vexpress_config_func_get_by_node(node);
+ if (!vconfig_func) {
+ DRM_ERROR("failed to get an output connector\n");
+ return -ENXIO;
+ }*/
+
+ encoder = kzalloc(sizeof(*encoder), GFP_KERNEL);
+ if (!encoder) {
+ err = -ENOMEM;
+ goto encoder_alloc_fail;
+ }
+
+ encoder->possible_crtcs = 1;
+ encoder->possible_clones = 0;
+ err = drm_encoder_init(dev, encoder, &hdlcd_vexpress_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS);
+ if (err)
+ goto encoder_init_fail;
+
+ drm_encoder_helper_add(encoder, &hdlcd_vexpress_encoder_helper_funcs);
+
+ connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+ if (!connector) {
+ err = -ENOMEM;
+ goto connector_alloc_err;
+ }
+
+ connector->interlace_allowed = false;
+ connector->doublescan_allowed = false;
+ connector->polled = 0;
+ err = drm_connector_init(dev, connector, &hdlcd_connector_funcs,
+ DRM_MODE_CONNECTOR_DVID);
+ if (err)
+ goto connector_init_err;
+
+ drm_connector_helper_add(connector, &hdlcd_vexpress_con_helper_funcs);
+
+ connector->encoder = encoder;
+ err = drm_mode_connector_attach_encoder(connector, encoder);
+ if (err)
+ goto connector_attach_err;
+
+ return 0;
+
+connector_attach_err:
+ drm_connector_cleanup(connector);
+connector_init_err:
+ kfree(connector);
+connector_alloc_err:
+ drm_encoder_cleanup(encoder);
+encoder_init_fail:
+ kfree(encoder);
+encoder_alloc_fail:
+ //vexpress_config_func_put(vconfig_func);
+
+ return err;
+}
diff --git a/drivers/gpu/drm/arm/hdlcd_virt_encoder.c b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c
new file mode 100644
index 000000000000..2200c4a46b42
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2013,2014 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * 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 <linux/i2c.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "hdlcd_drv.h"
+
+struct hdlcd_connector {
+ struct drm_connector connector;
+ struct display_timings *timings;
+};
+
+#define conn_to_hdlcd(x) container_of(x, struct hdlcd_connector, connector)
+
+static void hdlcd_connector_destroy(struct drm_connector *connector)
+{
+ struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector);
+
+ drm_connector_cleanup(connector);
+ kfree(hdlcd);
+}
+
+static enum drm_connector_status hdlcd_connector_detect(
+ struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs hdlcd_connector_funcs = {
+ .destroy = hdlcd_connector_destroy,
+ .dpms = drm_helper_connector_dpms,
+ .detect = hdlcd_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+};
+
+static int hdlcd_connector_get_modes(struct drm_connector *connector)
+{
+ struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector);
+ struct display_timings *timings = hdlcd->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 hdlcd_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs hdlcd_virt_con_helper_funcs = {
+ .get_modes = hdlcd_connector_get_modes,
+ .mode_valid = hdlcd_connector_mode_valid,
+ .best_encoder = hdlcd_connector_best_encoder,
+};
+
+static void hdlcd_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ kfree(encoder);
+}
+
+static const struct drm_encoder_funcs hdlcd_encoder_funcs = {
+ .destroy = hdlcd_encoder_destroy,
+};
+
+static void hdlcd_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool hdlcd_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* nothing needed */
+ return true;
+}
+
+static void hdlcd_encoder_prepare(struct drm_encoder *encoder)
+{
+ hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void hdlcd_encoder_commit(struct drm_encoder *encoder)
+{
+ hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void hdlcd_encoder_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 hdlcd_encoder_helper_funcs = {
+ .dpms = hdlcd_encoder_dpms,
+ .mode_fixup = hdlcd_encoder_mode_fixup,
+ .prepare = hdlcd_encoder_prepare,
+ .commit = hdlcd_encoder_commit,
+ .mode_set = hdlcd_encoder_mode_set,
+};
+
+int hdlcd_create_virtual_connector(struct drm_device *dev)
+{
+ struct drm_encoder *encoder;
+ struct hdlcd_connector *hdlcdc;
+ struct drm_connector *connector;
+ int ret;
+
+ encoder = kzalloc(sizeof(*encoder), GFP_KERNEL);
+ if (!encoder)
+ return -ENOMEM;
+
+ encoder->possible_crtcs = 1;
+ encoder->possible_clones = 0;
+
+ ret = drm_encoder_init(dev, encoder, &hdlcd_encoder_funcs,
+ DRM_MODE_ENCODER_VIRTUAL);
+ if (ret)
+ goto encoder_init_err;
+
+ drm_encoder_helper_add(encoder, &hdlcd_encoder_helper_funcs);
+
+ hdlcdc = kzalloc(sizeof(*hdlcdc), GFP_KERNEL);
+ if (!hdlcdc) {
+ ret = -ENOMEM;
+ goto connector_alloc_err;
+ }
+
+ hdlcdc->timings = of_get_display_timings(dev->platformdev->dev.of_node);
+ if (!hdlcdc->timings) {
+ DRM_ERROR("failed to get display panel timings\n");
+ ret = -ENXIO;
+ goto connector_init_err;
+ }
+
+ connector = &hdlcdc->connector;
+
+ /* bogus values, pretend we're a 24" screen */
+ 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(dev, connector, &hdlcd_connector_funcs,
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret)
+ goto connector_init_err;
+
+ drm_connector_helper_add(connector, &hdlcd_virt_con_helper_funcs);
+
+ connector->encoder = encoder;
+ ret = drm_mode_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto attach_err;
+
+
+ return ret;
+
+attach_err:
+ drm_connector_cleanup(connector);
+connector_init_err:
+ kfree(hdlcdc);
+connector_alloc_err:
+ drm_encoder_cleanup(encoder);
+encoder_init_err:
+ kfree(encoder);
+
+ return ret;
+};
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
index 22c7ed63a001..338d224e1e62 100644
--- a/drivers/gpu/drm/i2c/Kconfig
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -17,6 +17,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 2c72eb584ab7..59aea5aa1fff 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -5,8 +5,10 @@ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
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
-tda998x-y := tda998x_drv.o
+tda998x-y := tda998x_drv.o tda998x_codec.o
obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.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..14bc710e3c2c
--- /dev/null
+++ b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c
@@ -0,0 +1,465 @@
+/*
+ * 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_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 dummy_encoder_params {
+};
+
+#define to_dummy_priv(x) ((struct dummy_priv *)to_encoder_slave(x)->slave_priv)
+
+/* DRM encoder functions */
+
+static void dummy_encoder_set_config(struct dummy_priv *priv,
+ const struct dummy_encoder_params *p)
+{
+}
+
+static void dummy_encoder_dpms(struct dummy_priv *priv, int mode)
+{
+}
+
+static void
+dummy_encoder_save(struct drm_encoder *encoder)
+{
+}
+
+static void
+dummy_encoder_restore(struct drm_encoder *encoder)
+{
+}
+
+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_encoder_mode_valid(struct dummy_priv *priv,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static void
+dummy_encoder_mode_set(struct dummy_priv *priv,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static enum drm_connector_status
+dummy_encoder_detect(struct dummy_priv *priv)
+{
+ 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_encoder_get_modes(struct dummy_priv *priv,
+ struct drm_connector *connector)
+{
+ struct edid *edid;
+ int n;
+
+ edid = drm_do_get_edid(connector, dummy_read_edid_block, priv);
+ if (!edid)
+ return 0;
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ n = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+
+ return n;
+}
+
+static int
+dummy_encoder_set_property(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ return 0;
+}
+
+static void dummy_destroy(struct dummy_priv *priv)
+{
+}
+
+/* Slave encoder support */
+
+static void
+dummy_encoder_slave_set_config(struct drm_encoder *encoder, void *params)
+{
+ dummy_encoder_set_config(to_dummy_priv(encoder), params);
+}
+
+static void dummy_encoder_slave_destroy(struct drm_encoder *encoder)
+{
+ struct dummy_priv *priv = to_dummy_priv(encoder);
+
+ dummy_destroy(priv);
+ drm_i2c_encoder_destroy(encoder);
+ kfree(priv);
+}
+
+static void dummy_encoder_slave_dpms(struct drm_encoder *encoder, int mode)
+{
+ dummy_encoder_dpms(to_dummy_priv(encoder), mode);
+}
+
+static int dummy_encoder_slave_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ return dummy_encoder_mode_valid(to_dummy_priv(encoder), mode);
+}
+
+static void
+dummy_encoder_slave_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ dummy_encoder_mode_set(to_dummy_priv(encoder), mode, adjusted_mode);
+}
+
+static enum drm_connector_status
+dummy_encoder_slave_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return dummy_encoder_detect(to_dummy_priv(encoder));
+}
+
+static int dummy_encoder_slave_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return dummy_encoder_get_modes(to_dummy_priv(encoder), connector);
+}
+
+static int
+dummy_encoder_slave_create_resources(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return 0;
+}
+
+static struct drm_encoder_slave_funcs dummy_encoder_slave_funcs = {
+ .set_config = dummy_encoder_slave_set_config,
+ .destroy = dummy_encoder_slave_destroy,
+ .dpms = dummy_encoder_slave_dpms,
+ .save = dummy_encoder_save,
+ .restore = dummy_encoder_restore,
+ .mode_fixup = dummy_encoder_mode_fixup,
+ .mode_valid = dummy_encoder_slave_mode_valid,
+ .mode_set = dummy_encoder_slave_mode_set,
+ .detect = dummy_encoder_slave_detect,
+ .get_modes = dummy_encoder_slave_get_modes,
+ .create_resources = dummy_encoder_slave_create_resources,
+ .set_property = dummy_encoder_set_property,
+};
+
+/* I2C driver functions */
+
+static int dummy_encoder_init(struct i2c_client *client,
+ struct drm_device *dev,
+ struct drm_encoder_slave *encoder_slave)
+{
+ struct dummy_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->encoder = &encoder_slave->base;
+
+ encoder_slave->slave_priv = priv;
+ encoder_slave->slave_funcs = &dummy_encoder_slave_funcs;
+
+ return 0;
+}
+
+struct dummy_priv2 {
+ struct dummy_priv base;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+};
+
+#define conn_to_dummy_priv2(x) \
+ container_of(x, struct dummy_priv2, connector);
+
+#define enc_to_dummy_priv2(x) \
+ container_of(x, struct dummy_priv2, encoder);
+
+static void dummy_encoder2_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct dummy_priv2 *priv = enc_to_dummy_priv2(encoder);
+
+ dummy_encoder_dpms(&priv->base, mode);
+}
+
+static void dummy_encoder_prepare(struct drm_encoder *encoder)
+{
+ dummy_encoder2_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void dummy_encoder_commit(struct drm_encoder *encoder)
+{
+ dummy_encoder2_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void dummy_encoder2_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct dummy_priv2 *priv = enc_to_dummy_priv2(encoder);
+
+ dummy_encoder_mode_set(&priv->base, mode, adjusted_mode);
+}
+
+static const struct drm_encoder_helper_funcs dummy_encoder_helper_funcs = {
+ .dpms = dummy_encoder2_dpms,
+ .save = dummy_encoder_save,
+ .restore = dummy_encoder_restore,
+ .mode_fixup = dummy_encoder_mode_fixup,
+ .prepare = dummy_encoder_prepare,
+ .commit = dummy_encoder_commit,
+ .mode_set = dummy_encoder2_mode_set,
+};
+
+static void dummy_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct dummy_priv2 *priv = enc_to_dummy_priv2(encoder);
+
+ dummy_destroy(&priv->base);
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs dummy_encoder_funcs = {
+ .destroy = dummy_encoder_destroy,
+};
+
+static int dummy_connector_get_modes(struct drm_connector *connector)
+{
+ struct dummy_priv2 *priv = conn_to_dummy_priv2(connector);
+
+ return dummy_encoder_get_modes(&priv->base, connector);
+}
+
+static int dummy_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct dummy_priv2 *priv = conn_to_dummy_priv2(connector);
+
+ return dummy_encoder_mode_valid(&priv->base, mode);
+}
+
+static struct drm_encoder *
+dummy_connector_best_encoder(struct drm_connector *connector)
+{
+ struct dummy_priv2 *priv = conn_to_dummy_priv2(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 enum drm_connector_status
+dummy_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct dummy_priv2 *priv = conn_to_dummy_priv2(connector);
+
+ return dummy_encoder_detect(&priv->base);
+}
+
+static void dummy_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs dummy_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = dummy_connector_detect,
+ .destroy = dummy_connector_destroy,
+};
+
+static int dummy_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct dummy_priv2 *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+
+ priv->base.encoder = &priv->encoder;
+ 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);
+ 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;
+
+ ret = drm_connector_register(&priv->connector);
+ if (ret)
+ goto err_sysfs;
+
+ priv->connector.encoder = &priv->encoder;
+ drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder);
+
+ return 0;
+
+err_sysfs:
+ drm_connector_cleanup(&priv->connector);
+err_connector:
+ drm_encoder_cleanup(&priv->encoder);
+err_encoder:
+ dummy_destroy(&priv->base);
+ return ret;
+}
+
+static void dummy_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct dummy_priv2 *priv = dev_get_drvdata(dev);
+
+ drm_connector_cleanup(&priv->connector);
+ drm_encoder_cleanup(&priv->encoder);
+ dummy_destroy(&priv->base);
+}
+
+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;
+}
+
+static const struct of_device_id dummy_of_ids[] = {
+ { .compatible = "sil,sii9022-tpi", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dummy_of_ids);
+
+static struct i2c_device_id dummy_ids[] = {
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, dummy_ids);
+
+static struct drm_i2c_encoder_driver dummy_driver = {
+ .i2c_driver = {
+ .probe = dummy_probe,
+ .remove = dummy_remove,
+ .driver = {
+ .name = "dummy_drm_i2c",
+ .of_match_table = dummy_of_ids
+ },
+ .id_table = dummy_ids,
+ },
+ .encoder_init = dummy_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init
+dummy_init(void)
+{
+ return drm_i2c_encoder_register(THIS_MODULE, &dummy_driver);
+}
+
+static void __exit
+dummy_exit(void)
+{
+ drm_i2c_encoder_unregister(&dummy_driver);
+}
+
+MODULE_LICENSE("GPL");
+
+module_init(dummy_init);
+module_exit(dummy_exit);
diff --git a/drivers/gpu/drm/i2c/tda998x_codec.c b/drivers/gpu/drm/i2c/tda998x_codec.c
new file mode 100644
index 000000000000..c84d2b58f42d
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_codec.c
@@ -0,0 +1,248 @@
+/*
+ * ALSA SoC TDA998X CODEC
+ *
+ * Copyright (C) 2014 Jean-Francois Moine
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/tda998x.h>
+
+#include "tda998x_drv.h"
+
+#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static int tda_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
+ u8 *eld = priv->eld;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u8 *sad;
+ int sad_count;
+ unsigned eld_ver, mnl, rate_mask;
+ unsigned max_channels, fmt;
+ u64 formats;
+ struct snd_pcm_hw_constraint_list *rate_constraints =
+ &priv->rate_constraints;
+ static const u32 hdmi_rates[] = {
+ 32000, 44100, 48000, 88200, 96000, 176400, 192000
+ };
+
+ /* check if streaming is already active */
+ if (priv->dai_id != AFMT_NO_AUDIO)
+ return -EBUSY;
+ priv->dai_id = dai->id;
+
+ if (!eld)
+ return 0;
+
+ /* adjust the hw params from the ELD (EDID) */
+ eld_ver = eld[0] >> 3;
+ if (eld_ver != 2 && eld_ver != 31)
+ return 0;
+
+ mnl = eld[4] & 0x1f;
+ if (mnl > 16)
+ return 0;
+
+ sad_count = eld[5] >> 4;
+ sad = eld + 20 + mnl;
+
+ /* Start from the basic audio settings */
+ max_channels = 2;
+ rate_mask = 0;
+ fmt = 0;
+ while (sad_count--) {
+ switch (sad[0] & 0x78) {
+ case 0x08: /* PCM */
+ max_channels = max(max_channels, (sad[0] & 7) + 1u);
+ rate_mask |= sad[1];
+ fmt |= sad[2] & 0x07;
+ break;
+ }
+ sad += 3;
+ }
+
+ /* set the constraints */
+ rate_constraints->list = hdmi_rates;
+ rate_constraints->count = ARRAY_SIZE(hdmi_rates);
+ rate_constraints->mask = rate_mask;
+ snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ rate_constraints);
+
+ formats = 0;
+ if (fmt & 1)
+ formats |= SNDRV_PCM_FMTBIT_S16_LE;
+ if (fmt & 2)
+ formats |= SNDRV_PCM_FMTBIT_S20_3LE;
+ if (fmt & 4)
+ formats |= SNDRV_PCM_FMTBIT_S24_LE;
+ snd_pcm_hw_constraint_mask64(runtime,
+ SNDRV_PCM_HW_PARAM_FORMAT,
+ formats);
+
+ snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ 1, max_channels);
+ return 0;
+}
+
+static int tda_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
+
+ /* Requires an attached display */
+ if (!priv->encoder->crtc)
+ return -ENODEV;
+
+ /* if same input and same parameters, do not do a full switch */
+ if (dai->id == priv->params.audio_format &&
+ params_format(params) == priv->audio_sample_format) {
+ tda998x_audio_start(priv, 0);
+ return 0;
+ }
+ priv->params.audio_sample_rate = params_rate(params);
+ priv->params.audio_format = dai->id;
+ priv->audio_sample_format = params_format(params);
+ priv->params.audio_cfg =
+ priv->audio_ports[dai->id == AFMT_I2S ? 0 : 1];
+ tda998x_audio_start(priv, 1);
+ return 0;
+}
+
+static void tda_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
+
+ tda998x_audio_stop(priv);
+ priv->dai_id = AFMT_NO_AUDIO;
+}
+
+static const struct snd_soc_dai_ops tda_ops = {
+ .startup = tda_startup,
+ .hw_params = tda_hw_params,
+ .shutdown = tda_shutdown,
+};
+
+static struct snd_soc_dai_driver tda998x_dai[] = {
+ {
+ .name = "i2s-hifi",
+ .id = AFMT_I2S,
+ .playback = {
+ .stream_name = "HDMI I2S Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .formats = TDA998X_FORMATS,
+ },
+ .ops = &tda_ops,
+ },
+ {
+ .name = "spdif-hifi",
+ .id = AFMT_SPDIF,
+ .playback = {
+ .stream_name = "HDMI SPDIF Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 22050,
+ .rate_max = 192000,
+ .formats = TDA998X_FORMATS,
+ },
+ .ops = &tda_ops,
+ },
+};
+
+static const struct snd_soc_dapm_widget tda_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("hdmi-out"),
+};
+static const struct snd_soc_dapm_route tda_routes[] = {
+ { "hdmi-out", NULL, "HDMI I2S Playback" },
+ { "hdmi-out", NULL, "HDMI SPDIF Playback" },
+};
+
+static int tda_probe(struct snd_soc_codec *codec)
+{
+ struct i2c_client *i2c_client = to_i2c_client(codec->dev);
+ struct tda998x_priv *priv = i2c_get_clientdata(i2c_client);
+ struct device_node *np = codec->dev->of_node;
+ int i, j, ret;
+ const char *p;
+
+ if (!priv)
+ return -ENODEV;
+ snd_soc_codec_set_drvdata(codec, priv);
+
+ if (!np)
+ return 0;
+
+ /* get the audio input ports*/
+ for (i = 0; i < 2; i++) {
+ u32 port;
+
+ ret = of_property_read_u32_index(np, "audio-ports", i, &port);
+ if (ret) {
+ if (i == 0)
+ dev_err(codec->dev,
+ "bad or missing audio-ports\n");
+ break;
+ }
+ ret = of_property_read_string_index(np, "audio-port-names",
+ i, &p);
+ if (ret) {
+ dev_err(codec->dev,
+ "missing audio-port-names[%d]\n", i);
+ break;
+ }
+ if (strcmp(p, "i2s") == 0) {
+ j = 0;
+ } else if (strcmp(p, "spdif") == 0) {
+ j = 1;
+ } else {
+ dev_err(codec->dev,
+ "bad audio-port-names '%s'\n", p);
+ break;
+ }
+ priv->audio_ports[j] = port;
+ }
+ return 0;
+}
+
+static const struct snd_soc_codec_driver soc_codec_tda998x = {
+ .probe = tda_probe,
+ .dapm_widgets = tda_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tda_widgets),
+ .dapm_routes = tda_routes,
+ .num_dapm_routes = ARRAY_SIZE(tda_routes),
+};
+
+int tda998x_codec_register(struct device *dev)
+{
+ return snd_soc_register_codec(dev,
+ &soc_codec_tda998x,
+ tda998x_dai, ARRAY_SIZE(tda998x_dai));
+}
+
+void tda998x_codec_unregister(struct device *dev)
+{
+ snd_soc_unregister_codec(dev);
+}
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index 424228be79ae..831e3d7f5abc 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -20,6 +20,7 @@
#include <linux/module.h>
#include <linux/irq.h>
#include <sound/asoundef.h>
+#include <sound/pcm_params.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
@@ -28,26 +29,9 @@
#include <drm/drm_of.h>
#include <drm/i2c/tda998x.h>
-#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+#include "tda998x_drv.h"
-struct tda998x_priv {
- struct i2c_client *cec;
- struct i2c_client *hdmi;
- struct mutex mutex;
- struct delayed_work dwork;
- uint16_t rev;
- uint8_t current_page;
- int dpms;
- bool is_hdmi_sink;
- u8 vip_cntrl_0;
- u8 vip_cntrl_1;
- u8 vip_cntrl_2;
- struct tda998x_encoder_params params;
-
- wait_queue_head_t wq_edid;
- volatile int wq_edid_wait;
- struct drm_encoder *encoder;
-};
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
@@ -667,12 +651,11 @@ static void
tda998x_configure_audio(struct tda998x_priv *priv,
struct drm_display_mode *mode, struct tda998x_encoder_params *p)
{
- uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv;
- uint32_t n;
+ uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv, aclk;
+ uint32_t n, cts;
/* Enable audio ports */
reg_write(priv, REG_ENA_AP, p->audio_cfg);
- reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg);
/* Set audio input source */
switch (p->audio_format) {
@@ -681,13 +664,28 @@ tda998x_configure_audio(struct tda998x_priv *priv,
clksel_aip = AIP_CLKSEL_AIP_SPDIF;
clksel_fs = AIP_CLKSEL_FS_FS64SPDIF;
cts_n = CTS_N_M(3) | CTS_N_K(3);
+ aclk = 0; /* no clock */
break;
case AFMT_I2S:
reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S);
clksel_aip = AIP_CLKSEL_AIP_I2S;
clksel_fs = AIP_CLKSEL_FS_ACLK;
- cts_n = CTS_N_M(3) | CTS_N_K(3);
+ /* with I2S input, the CTS_N predivider depends on
+ * the sample width */
+ switch (priv->audio_sample_format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ cts_n = CTS_N_M(3) | CTS_N_K(1);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ cts_n = CTS_N_M(3) | CTS_N_K(2);
+ break;
+ default:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ cts_n = CTS_N_M(3) | CTS_N_K(3);
+ break;
+ }
+ aclk = 1; /* clock enable */
break;
default:
@@ -699,6 +697,7 @@ tda998x_configure_audio(struct tda998x_priv *priv,
reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT |
AIP_CNTRL_0_ACR_MAN); /* auto CTS */
reg_write(priv, REG_CTS_N, cts_n);
+ reg_write(priv, REG_ENA_ACLK, aclk);
/*
* Audio input somehow depends on HDMI line rate which is
@@ -724,9 +723,25 @@ tda998x_configure_audio(struct tda998x_priv *priv,
n = 128 * p->audio_sample_rate / 1000;
/* Write the CTS and N values */
- buf[0] = 0x44;
- buf[1] = 0x42;
- buf[2] = 0x01;
+ if ((n > 0) && (mode->clock > 0)) {
+ /*
+ * The average CTS value is calculated as:
+ *
+ * fTMDS * n / (128 * fs)
+ *
+ * which equates to:
+ *
+ * fTMDS / 1000
+ *
+ * for non-coherent clocks.
+ */
+ cts = mode->clock;
+ } else {
+ cts = 82500;
+ }
+ buf[0] = cts;
+ buf[1] = cts >> 8;
+ buf[2] = cts >> 16;
buf[3] = n;
buf[4] = n >> 8;
buf[5] = n >> 16;
@@ -755,6 +770,24 @@ tda998x_configure_audio(struct tda998x_priv *priv,
tda998x_write_aif(priv, p);
}
+/* tda998x codec interface */
+void tda998x_audio_start(struct tda998x_priv *priv,
+ int full)
+{
+ struct tda998x_encoder_params *p = &priv->params;
+
+ if (!full) {
+ reg_write(priv, REG_ENA_AP, p->audio_cfg);
+ return;
+ }
+ tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p);
+}
+
+void tda998x_audio_stop(struct tda998x_priv *priv)
+{
+ reg_write(priv, REG_ENA_AP, 0);
+}
+
/* DRM encoder functions */
static void tda998x_encoder_set_config(struct tda998x_priv *priv,
@@ -1118,6 +1151,11 @@ tda998x_encoder_get_modes(struct tda998x_priv *priv,
drm_mode_connector_update_edid_property(connector, edid);
n = drm_add_edid_modes(connector, edid);
priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
+
+ /* keep the EDID as ELD for the audio subsystem */
+ drm_edid_to_eld(connector, edid);
+ priv->eld = connector->eld;
+
kfree(edid);
return n;
@@ -1153,6 +1191,8 @@ static void tda998x_destroy(struct tda998x_priv *priv)
cancel_delayed_work_sync(&priv->dwork);
}
+ tda998x_codec_unregister(&priv->hdmi->dev);
+
i2c_unregister_device(priv->cec);
}
@@ -1241,6 +1281,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1);
priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5);
+ priv->params.audio_frame[1] = 1; /* channels - 1 */
+ priv->params.audio_sample_rate = 48000; /* 48kHz */
+
priv->current_page = 0xff;
priv->hdmi = client;
/* CEC I2C address bound to TDA998x I2C addr by configuration pins */
@@ -1251,6 +1294,8 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
priv->dpms = DRM_MODE_DPMS_OFF;
+ i2c_set_clientdata(client, priv);
+
mutex_init(&priv->mutex); /* protect the page access */
/* wake up the device: */
@@ -1337,6 +1382,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
/* enable EDID read irq: */
reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+ /* register the audio CODEC */
+ tda998x_codec_register(&client->dev);
+
if (!np)
return 0; /* non-DT */
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.h b/drivers/gpu/drm/i2c/tda998x_drv.h
new file mode 100644
index 000000000000..a414654b1ebd
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_drv.h
@@ -0,0 +1,34 @@
+/* tda998x private data */
+
+struct tda998x_priv {
+ struct i2c_client *cec;
+ struct i2c_client *hdmi;
+ struct mutex mutex;
+ struct delayed_work dwork;
+ uint16_t rev;
+ uint8_t current_page;
+ int dpms;
+ bool is_hdmi_sink;
+ u8 vip_cntrl_0;
+ u8 vip_cntrl_1;
+ u8 vip_cntrl_2;
+ struct tda998x_encoder_params params;
+
+ wait_queue_head_t wq_edid;
+ volatile int wq_edid_wait;
+ struct drm_encoder *encoder;
+
+ u8 audio_ports[2];
+ int audio_sample_format;
+ int dai_id; /* DAI ID when streaming active */
+
+ u8 *eld;
+
+ struct snd_pcm_hw_constraint_list rate_constraints;
+};
+
+int tda998x_codec_register(struct device *dev);
+void tda998x_codec_unregister(struct device *dev);
+
+void tda998x_audio_start(struct tda998x_priv *priv, int full);
+void tda998x_audio_stop(struct tda998x_priv *priv);
diff --git a/drivers/staging/android/ion/ion.c b/drivers/staging/android/ion/ion.c
index 6e8d8392ca38..16d34971c66b 100644
--- a/drivers/staging/android/ion/ion.c
+++ b/drivers/staging/android/ion/ion.c
@@ -252,8 +252,10 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
allocation via dma_map_sg. The implicit contract here is that
memory coming from the heaps is ready for dma, ie if it has a
cached mapping that mapping has been invalidated */
- for_each_sg(buffer->sg_table->sgl, sg, buffer->sg_table->nents, i)
+ for_each_sg(buffer->sg_table->sgl, sg, buffer->sg_table->nents, i) {
sg_dma_address(sg) = sg_phys(sg);
+ sg_dma_len(sg) = sg->length;
+ }
mutex_lock(&dev->buffer_lock);
ion_buffer_add(dev, buffer);
mutex_unlock(&dev->buffer_lock);
diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h
index 3e419d92cf5a..31757dff5e91 100644
--- a/include/drm/i2c/tda998x.h
+++ b/include/drm/i2c/tda998x.h
@@ -20,6 +20,7 @@ struct tda998x_encoder_params {
u8 audio_frame[6];
enum {
+ AFMT_NO_AUDIO = 0,
AFMT_SPDIF,
AFMT_I2S
} audio_format;
diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf
index 4981662a8ae2..1206f51e05ad 100644
--- a/linaro/configs/vexpress.conf
+++ b/linaro/configs/vexpress.conf
@@ -32,7 +32,13 @@ 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_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 001826e88902..dc7a2ef86706 100644
--- a/linaro/configs/vexpress64.conf
+++ b/linaro/configs/vexpress64.conf
@@ -61,3 +61,25 @@ CONFIG_CPU_FREQ_GOV_USERSPACE=y
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_GOV_INTERACTIVE=y
CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE=y
+CONFIG_DRM=y
+CONFIG_DRM_ARM=y
+CONFIG_DRM_HDLCD=y
+CONFIG_DRM_VIRTUAL_HDLCD=y
+CONFIG_CMA=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_HDMI_CODEC=y
+CONFIG_SND_SOC_SPDIF=y
+CONFIG_SND_SIMPLE_CARD=y
+CONFIG_DMADEVICES=y
+CONFIG_PL330_DMA=y