aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm')
-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.c291
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.c433
-rw-r--r--drivers/gpu/drm/arm/hdlcd_drv.h82
-rw-r--r--drivers/gpu/drm/arm/hdlcd_fb.c682
-rw-r--r--drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c236
-rw-r--r--drivers/gpu/drm/arm/hdlcd_regs.h87
-rw-r--r--drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c242
-rw-r--r--drivers/gpu/drm/arm/hdlcd_virt_encoder.c199
-rw-r--r--drivers/gpu/drm/drm_edid.c15
-rw-r--r--drivers/gpu/drm/drm_encoder_slave.c6
-rw-r--r--drivers/gpu/drm/drm_gem.c83
-rw-r--r--drivers/gpu/drm/drm_gem_cma_helper.c16
-rw-r--r--drivers/gpu/drm/i2c/Kconfig7
-rw-r--r--drivers/gpu/drm/i2c/Makefile5
-rw-r--r--drivers/gpu/drm/i2c/sii9022_drv.c348
-rw-r--r--drivers/gpu/drm/i2c/tda998x_codec.c248
-rw-r--r--drivers/gpu/drm/i2c/tda998x_drv.c1009
-rw-r--r--drivers/gpu/drm/i2c/tda998x_drv.h32
-rwxr-xr-xdrivers/gpu/drm/pl111/Kbuild28
-rwxr-xr-xdrivers/gpu/drm/pl111/Kconfig23
-rwxr-xr-xdrivers/gpu/drm/pl111/Makefile32
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_clcd_ext.h95
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm.h270
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_connector.c170
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_crtc.c421
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_cursor.c331
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_device.c326
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_dma_buf.c615
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_encoder.c107
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_fb.c202
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_funcs.h130
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_gem.c401
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_pl111.c417
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_platform.c151
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_suspend.c43
-rwxr-xr-xdrivers/gpu/drm/pl111/pl111_drm_vma.c307
40 files changed, 7861 insertions, 262 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index b16c50ee769c..a9ca07916a94 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -79,6 +79,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 1c9f24396002..daa41fcb63a8 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -30,6 +30,7 @@ CFLAGS_drm_trace_points.o := -I$(src)
obj-$(CONFIG_DRM) += drm.o
obj-$(CONFIG_DRM_USB) += drm_usb.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..acb046fec516
--- /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..429644c28a2f
--- /dev/null
+++ b/drivers/gpu/drm/arm/Makefile
@@ -0,0 +1,4 @@
+
+hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.o hdlcd_fb.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..9a81467d72f4
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_crtc.c
@@ -0,0 +1,291 @@
+/*
+ * 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.
+ *
+ * Implementation of a CRTC class for the HDLCD driver.
+ */
+
+#include <linux/clk.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.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)
+{
+ drm_crtc_cleanup(crtc);
+}
+
+void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait)
+{
+ struct drm_framebuffer *fb = hdlcd->crtc.fb;
+ struct hdlcd_bo *bo;
+ unsigned int depth, bpp;
+ dma_addr_t scanout_start;
+ int ret;
+
+ drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
+ bo = hdlcd->bo;
+
+ scanout_start = bo->dma_addr + 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);
+ hdlcd->frame_completion.done = 0;
+ do {
+ ret = wait_for_completion_interruptible_timeout(&hdlcd->frame_completion,
+ msecs_to_jiffies(50));
+ } while (ret <= 0);
+ drm_vblank_put(fb->dev, 0);
+ } else {
+ dev_info(fb->dev->dev, "%s: wait called with DPMS set to %d\n",
+ __func__, hdlcd->dpms);
+ }
+}
+
+static int hdlcd_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event)
+{
+ 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->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)
+{
+ drm_vblank_pre_modeset(crtc->dev, 0);
+ 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 bool hdlcd_fb_mode_equal(struct drm_framebuffer *oldfb,
+ struct drm_framebuffer *newfb)
+{
+ if (!oldfb || !newfb)
+ return false;
+
+ if (oldfb->pixel_format == newfb->pixel_format &&
+ oldfb->width == newfb->width &&
+ oldfb->height == newfb->height)
+ return true;
+
+ return false;
+}
+
+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;
+ unsigned char red_width = 0, green_width = 0, blue_width = 0, alpha_width = 0;
+ unsigned int default_color = 0x00000000;
+
+#ifdef HDLCD_SHOW_UNDERRUN
+ default_color = 0x00ff000000;
+#endif
+
+ /* This function gets called when the only change is the start of
+ the scanout buffer. Detect that and bail out early */
+ if (hdlcd->initialised && hdlcd_fb_mode_equal(oldfb, crtc->fb)) {
+ hdlcd_set_scanout(hdlcd, true);
+ return 0;
+ }
+
+ /* Preset the number of bits per colour */
+ drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp);
+ switch (depth) {
+ case 32:
+ alpha_width = 8;
+ case 24:
+ case 8: /* pseudocolor */
+ red_width = 8; green_width = 8; blue_width = 8;
+ break;
+ case 16: /* 565 format */
+ red_width = 5; green_width = 6; blue_width = 5;
+ break;
+ }
+
+ /* switch to using the more useful bytes per pixel */
+ bpp = (bpp + 7) / 8;
+
+ 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;
+
+ /* 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_PIXEL_FORMAT, (bpp - 1) << 3);
+
+ hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, crtc->fb->width * bpp);
+ hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, crtc->fb->height - 1);
+ hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, crtc->fb->width * bpp);
+ 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);
+
+ /*
+ * 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.
+ */
+ hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color |
+ alpha_width | /* offset */
+ (blue_width & 0xf) << 8);
+ hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color |
+ (blue_width + alpha_width) | /* offset */
+ ((green_width & 0xf) << 8));
+ hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color |
+ (blue_width + green_width + alpha_width) | /* offset */
+ ((red_width & 0xf) << 8));
+
+ clk_prepare(hdlcd->clk);
+ clk_set_rate(hdlcd->clk, mode->clock * 1000);
+ clk_enable(hdlcd->clk);
+
+ hdlcd_set_scanout(hdlcd, false);
+ hdlcd->initialised = true;
+
+ return 0;
+}
+
+int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *oldfb)
+{
+ struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+
+ hdlcd_set_scanout(hdlcd, true);
+ 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;
+
+ drm_mode_config_init(dev);
+ hdlcd_drm_mode_config_init(dev);
+
+ ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs);
+ if (ret < 0)
+ goto crtc_setup_err;
+
+ drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs);
+
+ return 0;
+
+crtc_setup_err:
+ drm_mode_config_cleanup(dev);
+
+ return ret;
+}
+
diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c
new file mode 100644
index 000000000000..a3019ab75a40
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_drv.c
@@ -0,0 +1,433 @@
+/*
+ * 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.
+ *
+ * ARM HDLCD Driver
+ */
+
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "hdlcd_drv.h"
+#include "hdlcd_regs.h"
+
+
+static int hdlcd_unload(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+ drm_kms_helper_poll_fini(dev);
+ if (hdlcd->fb_helper)
+ drm_fb_helper_fini(hdlcd->fb_helper);
+
+ drm_vblank_cleanup(dev);
+ drm_mode_config_cleanup(dev);
+
+ drm_irq_uninstall(dev);
+
+ if (!IS_ERR(hdlcd->clk))
+ clk_put(hdlcd->clk);
+
+ platform_set_drvdata(dev->platformdev, NULL);
+
+ if (hdlcd->mmio)
+ iounmap(hdlcd->mmio);
+
+ dev->dev_private = NULL;
+ kfree(hdlcd);
+
+ return 0;
+}
+
+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;
+ phandle slave_phandle;
+ u32 version;
+ int ret;
+
+ hdlcd = kzalloc(sizeof(*hdlcd), GFP_KERNEL);
+ if (!hdlcd) {
+ dev_err(dev->dev, "failed to allocate driver data\n");
+ 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
+ hdlcd->initialised = false;
+ dev->dev_private = hdlcd;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev->dev, "failed to get memory resource\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ hdlcd->mmio = ioremap_nocache(res->start, resource_size(res));
+ if (!hdlcd->mmio) {
+ dev_err(dev->dev, "failed to map control registers area\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ hdlcd->clk = clk_get(dev->dev, "pxlclk");
+ if (IS_ERR(hdlcd->clk)) {
+ dev_err(dev->dev, "unable to get an usable clock\n");
+ ret = PTR_ERR(hdlcd->clk);
+ goto fail;
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node, "i2c-slave", &slave_phandle)) {
+ dev_warn(dev->dev, "no i2c-slave handle provided, disabling physical connector\n");
+ hdlcd->slave_node = NULL;
+ } else
+ hdlcd->slave_node = of_find_node_by_phandle(slave_phandle);
+
+ version = hdlcd_read(hdlcd, HDLCD_REG_VERSION);
+ if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) {
+ dev_err(dev->dev, "unknown product id: 0x%x\n", version);
+ ret = -EINVAL;
+ goto fail;
+ }
+ dev_info(dev->dev, "found ARM HDLCD version r%dp%d\n",
+ (version & HDLCD_VERSION_MAJOR_MASK) >> 8,
+ version & HDLCD_VERSION_MINOR_MASK);
+
+ ret = hdlcd_setup_crtc(dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to create crtc\n");
+ goto fail;
+ }
+
+ /*
+ * It only makes sense to create the virtual connector if we don't have
+ * a physical way of controlling output
+ */
+ if (hdlcd->slave_node) {
+ ret = hdlcd_create_digital_connector(dev, hdlcd);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to create digital connector, trying board setup: %d\n", ret);
+ ret = hdlcd_create_vexpress_connector(dev, hdlcd);
+ }
+
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to create board connector: %d\n", ret);
+ goto fail;
+ }
+ } else {
+ ret = hdlcd_create_virtual_connector(dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to create virtual connector: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ ret = hdlcd_fbdev_init(dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to init the framebuffer (%d)\n", ret);
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, dev);
+
+ ret = drm_irq_install(dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to install IRQ handler\n");
+ goto fail;
+ }
+
+ init_completion(&hdlcd->frame_completion);
+ ret = drm_vblank_init(dev, 1);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to initialise vblank\n");
+ goto fail;
+ } else {
+ dev_info(dev->dev, "initialised vblank\n");
+ }
+
+ drm_kms_helper_poll_init(dev);
+
+ return 0;
+
+fail:
+ hdlcd_unload(dev);
+ return ret;
+}
+
+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;
+
+ drm_modeset_lock_all(dev);
+ if (hdlcd->fb_helper)
+ drm_fb_helper_restore_fbdev_mode(hdlcd->fb_helper);
+ drm_modeset_unlock_all(dev);
+}
+
+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 int 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 int 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 hdlcd_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .mmap = drm_gem_cma_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .llseek = no_llseek,
+};
+
+int drm_gem_dumb_destroy(struct drm_file *file,
+ struct drm_device *dev,
+ uint32_t handle)
+{
+ return drm_gem_handle_delete(file, handle);
+}
+
+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,
+ .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,
+#ifdef CONFIG_DEBUG_FS
+ .debugfs_init = hdlcd_debugfs_init,
+ .debugfs_cleanup = hdlcd_debugfs_cleanup,
+#endif
+ .fops = &hdlcd_fops,
+
+ .name = "hdlcd",
+ .desc = "ARM HDLCD Controller DRM",
+ .date = "20130505",
+ .major = 1,
+ .minor = 0,
+};
+
+
+static int hdlcd_probe(struct platform_device *pdev)
+{
+ return drm_platform_init(&hdlcd_driver, pdev);
+}
+
+static int hdlcd_remove(struct platform_device *pdev)
+{
+ drm_put_dev(platform_get_drvdata(pdev));
+ 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)
+{
+ return platform_driver_register(&hdlcd_platform_driver);
+}
+
+static void __exit hdlcd_exit(void)
+{
+ platform_driver_unregister(&hdlcd_platform_driver);
+}
+
+/* need late_initcall() so we load after i2c driver */
+late_initcall(hdlcd_init);
+module_exit(hdlcd_exit);
+
+MODULE_AUTHOR("Liviu Dudau");
+MODULE_DESCRIPTION("ARM HDLCD DRM driver");
+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..3fc3bddbb841
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_drv.h
@@ -0,0 +1,82 @@
+/*
+ * ARM HDLCD Controller register definition
+ */
+
+#ifndef __HDLCD_DRV_H__
+#define __HDLCD_DRV_H__
+
+struct hdlcd_bo {
+ struct drm_gem_object gem;
+ dma_addr_t dma_addr;
+ void *cpu_addr;
+};
+
+struct hdlcd_drm_private {
+ void __iomem *mmio;
+ struct clk *clk;
+ struct drm_fb_helper *fb_helper;
+ struct hdlcd_bo *bo;
+ struct drm_pending_vblank_event *event;
+ struct drm_crtc crtc;
+ struct device_node *slave_node;
+ 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;
+ bool initialised;
+};
+
+#define to_hdlcd_bo_obj(x) container_of(x, struct hdlcd_bo, gem)
+#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
+
+void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait);
+void hdlcd_drm_mode_config_init(struct drm_device *dev);
+int hdlcd_fbdev_init(struct drm_device *dev);
+
+/* 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.c b/drivers/gpu/drm/arm/hdlcd_fb.c
new file mode 100644
index 000000000000..0e96361dc2a3
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_fb.c
@@ -0,0 +1,682 @@
+/*
+ * 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.
+ *
+ * Implementation of the DRM fbdev compatibility mode for the HDLCD driver.
+ * Mainly used for doing dumb double buffering as expected by the ARM Mali driver.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <linux/dma-buf.h>
+
+#include "hdlcd_drv.h"
+#include "hdlcd_regs.h"
+
+#define MAX_FRAMES 2
+
+#define to_hdlcd_fb(x) container_of(x, struct hdlcd_fb, fb)
+
+struct hdlcd_dmabuf_attachment {
+ struct sg_table *sgt;
+ enum dma_data_direction dir;
+};
+
+static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev,
+ struct drm_mode_fb_cmd2 *mode_cmd);
+
+static int hdlcd_dmabuf_attach(struct dma_buf *dma_buf,
+ struct device *target_dev, struct dma_buf_attachment *attach)
+{
+ struct hdlcd_dmabuf_attachment *hdlcd_attach;
+
+ hdlcd_attach = kzalloc(sizeof(*hdlcd_attach), GFP_KERNEL);
+ if (!hdlcd_attach)
+ return -ENOMEM;
+
+ hdlcd_attach->dir = DMA_NONE;
+ attach->priv = hdlcd_attach;
+
+ return 0;
+}
+
+static void hdlcd_dmabuf_detach(struct dma_buf *dma_buf,
+ struct dma_buf_attachment *attach)
+{
+ struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv;
+ struct sg_table *sgt;
+
+ if (!hdlcd_attach)
+ return;
+
+ sgt = hdlcd_attach->sgt;
+ if (sgt) {
+ sg_free_table(sgt);
+ }
+
+ kfree(sgt);
+ kfree(hdlcd_attach);
+ attach->priv = NULL;
+}
+
+static void hdlcd_dmabuf_release(struct dma_buf *dma_buf)
+{
+ struct hdlcd_drm_private *hdlcd = dma_buf->priv;
+ struct drm_gem_object *obj = &hdlcd->bo->gem;
+
+ if (obj->export_dma_buf == dma_buf) {
+ /* drop the reference the export fd holds */
+ obj->export_dma_buf = NULL;
+ drm_gem_object_unreference_unlocked(obj);
+ }
+}
+
+static struct sg_table *hdlcd_map_dma_buf(struct dma_buf_attachment *attach,
+ enum dma_data_direction dir)
+{
+ struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv;
+ struct hdlcd_drm_private *hdlcd = attach->dmabuf->priv;
+ struct sg_table *sgt;
+ int size, ret;
+
+ if (dir == DMA_NONE || !hdlcd_attach)
+ return ERR_PTR(-EINVAL);
+
+ /* return the cached mapping when possible */
+ if (hdlcd_attach->dir == dir)
+ return hdlcd_attach->sgt;
+
+ /* don't allow two different directions for the same attachment */
+ if (hdlcd_attach->dir != DMA_NONE)
+ return ERR_PTR(-EBUSY);
+
+ size = hdlcd->bo->gem.size;
+
+ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+ if (!sgt) {
+ DRM_ERROR("Failed to allocate sg_table\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+ if (ret < 0) {
+ DRM_ERROR("Failed to allocate page table\n");
+ kfree(sgt);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ sg_dma_len(sgt->sgl) = size;
+ sg_set_page(sgt->sgl, pfn_to_page(PFN_DOWN(hdlcd->bo->dma_addr)), size, 0);
+ sg_dma_address(sgt->sgl) = hdlcd->bo->dma_addr;
+ ret = dma_map_sg(attach->dev, sgt->sgl, sgt->orig_nents, dir);
+ if (!ret) {
+ DRM_ERROR("failed to map sgl with IOMMU\n");
+ sg_free_table(sgt);
+ kfree(sgt);
+ return ERR_PTR(-EIO);
+ }
+ hdlcd_attach->sgt = sgt;
+ hdlcd_attach->dir = dir;
+
+ return sgt;
+}
+
+static void hdlcd_unmap_dma_buf(struct dma_buf_attachment *attach,
+ struct sg_table *sgt, enum dma_data_direction dir)
+{
+ struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv;
+
+ if (hdlcd_attach->dir != DMA_NONE)
+ dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir);
+ sg_free_table(sgt);
+ kfree(sgt);
+ hdlcd_attach->sgt = NULL;
+ hdlcd_attach->dir = DMA_NONE;
+}
+
+static void *hdlcd_dmabuf_kmap(struct dma_buf *dma_buf, unsigned long page_num)
+{
+ return NULL;
+}
+
+static void hdlcd_dmabuf_kunmap(struct dma_buf *dma_buf,
+ unsigned long page_num, void *addr)
+{
+}
+
+static int hdlcd_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma)
+{
+ struct hdlcd_drm_private *hdlcd = dma_buf->priv;
+ struct drm_gem_object *obj = &hdlcd->bo->gem;
+ struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj);
+ int ret;
+
+ mutex_lock(&obj->dev->struct_mutex);
+ ret = drm_gem_mmap_obj(obj, obj->size, vma);
+ mutex_unlock(&obj->dev->struct_mutex);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the
+ * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map
+ * the whole buffer.
+ */
+ vma->vm_flags &= ~VM_PFNMAP;
+ vma->vm_pgoff = 0;
+ ret = dma_mmap_writecombine(obj->dev->dev, vma, bo->cpu_addr, bo->dma_addr,
+ vma->vm_end - vma->vm_start);
+ if (ret)
+ drm_gem_vm_close(vma);
+
+ return 0;
+}
+
+static void *hdlcd_dmabuf_vmap(struct dma_buf *dma_buf)
+{
+ return ERR_PTR(-EINVAL);
+}
+
+static void hdlcd_dmabuf_vunmap(struct dma_buf *dma_buf, void *cpu_addr)
+{
+}
+
+struct dma_buf_ops hdlcd_buf_ops = {
+ .attach = hdlcd_dmabuf_attach,
+ .detach = hdlcd_dmabuf_detach,
+ .map_dma_buf = hdlcd_map_dma_buf,
+ .unmap_dma_buf = hdlcd_unmap_dma_buf,
+ .release = hdlcd_dmabuf_release,
+ .kmap = hdlcd_dmabuf_kmap,
+ .kmap_atomic = hdlcd_dmabuf_kmap,
+ .kunmap = hdlcd_dmabuf_kunmap,
+ .kunmap_atomic = hdlcd_dmabuf_kunmap,
+ .mmap = hdlcd_dmabuf_mmap,
+ .vmap = hdlcd_dmabuf_vmap,
+ .vunmap = hdlcd_dmabuf_vunmap,
+};
+
+/*
+ * 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 dma_buf *dma_buf;
+ uint32_t fd;
+
+ if (copy_from_user(&ebuf, argp, sizeof(ebuf)))
+ return -EFAULT;
+
+ dma_buf = dma_buf_export(hdlcd, &hdlcd_buf_ops,
+ info->screen_size, 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:
+ return -EFAULT;
+}
+
+static int hdlcd_wait_for_vsync(struct fb_info *info)
+{
+#if 0
+ struct drm_fb_helper *helper = info->par;
+ struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+ int ret;
+
+ drm_vblank_get(helper->dev, 0);
+ reinit_completion(&hdlcd->frame_completion);
+ do {
+ ret = wait_for_completion_interruptible_timeout(&hdlcd->frame_completion,
+ msecs_to_jiffies(50));
+ } while (ret == -ERESTARTSYS);
+ drm_vblank_put(helper->dev, 0);
+ if (!ret)
+ return -ETIMEDOUT;
+#endif
+
+ return 0;
+}
+
+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 hdlcd_wait_for_vsync(info);
+ default:
+ printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd);
+ }
+
+ return -EFAULT;
+}
+
+static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ struct drm_fb_helper *helper = info->par;
+ struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+ struct drm_gem_object *obj = &hdlcd->bo->gem;
+ struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj);
+ DEFINE_DMA_ATTRS(attrs);
+ size_t vm_size;
+
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+
+ vma->vm_flags |= VM_IO | VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP;
+ vm_size = vma->vm_end - vma->vm_start;
+
+ if (vm_size > bo->gem.size)
+ return -EINVAL;
+
+ return dma_mmap_attrs(helper->dev->dev, vma, bo->cpu_addr,
+ bo->dma_addr, bo->gem.size, &attrs);
+}
+
+static int hdlcd_fb_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;
+}
+
+static struct fb_ops hdlcd_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_check_var = hdlcd_fb_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,
+ .fb_mmap = hdlcd_fb_mmap,
+};
+
+static struct hdlcd_bo *hdlcd_fb_bo_create(struct drm_device *drm, size_t size)
+{
+ int err;
+ struct hdlcd_bo *bo;
+ struct hdlcd_drm_private *hdlcd = drm->dev_private;
+ DEFINE_DMA_ATTRS(attrs);
+
+ bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+ if (!bo)
+ return ERR_PTR(-ENOMEM);
+
+ size = round_up(size, PAGE_SIZE);
+ err = drm_gem_object_init(drm, &bo->gem, size);
+ if (err)
+ goto err_init;
+
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+ bo->cpu_addr = dma_alloc_attrs(drm->dev, size, &bo->dma_addr,
+ GFP_KERNEL | __GFP_NOWARN, &attrs);
+
+ if (!bo->cpu_addr) {
+ dev_err(drm->dev, "failed to allocate buffer of size %zu\n", size);
+ err = -ENOMEM;
+ goto err_dma;
+ }
+
+#if 0
+ err = drm_gem_create_mmap_offset(&bo->gem);
+ if (err)
+ goto err_map;
+#endif
+
+ hdlcd->bo = bo;
+ return bo;
+
+#if 0
+err_map:
+ dma_free_attrs(drm->dev, size, bo->cpu_addr, bo->dma_addr, &attrs);
+#endif
+err_dma:
+ drm_gem_object_release(&bo->gem);
+err_init:
+ kfree(bo);
+
+ return ERR_PTR(err);
+}
+
+void hdlcd_fb_bo_free(struct hdlcd_bo *bo)
+{
+ DEFINE_DMA_ATTRS(attrs);
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+
+ drm_gem_object_release(&bo->gem);
+ dma_free_attrs(bo->gem.dev->dev, bo->gem.size, bo->cpu_addr, bo->dma_addr, &attrs);
+ kfree(bo);
+}
+
+static int hdlcd_fb_probe(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct hdlcd_drm_private *hdlcd = helper->dev->dev_private;
+ struct fb_info *info;
+ struct hdlcd_bo *bo = hdlcd->bo;
+ struct drm_mode_fb_cmd2 cmd = { 0 };
+ unsigned int bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
+ unsigned long offset;
+ size_t size;
+ int err;
+
+ cmd.width = sizes->surface_width;
+ cmd.height = sizes->surface_height;
+ cmd.pitches[0] = ALIGN(sizes->surface_width * bytes_per_pixel, 64);
+ cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ size = PAGE_ALIGN(cmd.pitches[0] * cmd.height * MAX_FRAMES);
+
+ bo = hdlcd_fb_bo_create(helper->dev, size);
+ if (IS_ERR(bo))
+ return PTR_ERR(bo);
+
+ info = framebuffer_alloc(0, helper->dev->dev);
+ if (!info) {
+ dev_err(helper->dev->dev, "failed to allocate framebuffer info\n");
+ hdlcd_fb_bo_free(bo);
+ return -ENOMEM;
+ }
+
+ helper->fb = hdlcd_fb_alloc(helper->dev, &cmd);
+ helper->fbdev = info;
+
+ info->par = helper;
+ info->flags = FBINFO_FLAG_DEFAULT;
+ info->fbops = &hdlcd_fb_ops;
+
+ err = fb_alloc_cmap(&info->cmap, 256, 0);
+ if (err < 0) {
+ dev_err(helper->dev->dev, "failed to allocate color map: %d\n", err);
+ goto cleanup;
+ }
+
+ drm_fb_helper_fill_fix(info, helper->fb->pitches[0], helper->fb->depth);
+ drm_fb_helper_fill_var(info, helper, helper->fb->width, sizes->surface_height);
+
+ offset = info->var.xoffset * bytes_per_pixel +
+ info->var.yoffset * helper->fb->pitches[0];
+
+ helper->dev->mode_config.fb_base = (resource_size_t)bo->dma_addr;
+ info->screen_base = (void __iomem *)bo->cpu_addr + offset;
+ info->screen_size = size;
+ info->var.yres_virtual = info->var.yres * MAX_FRAMES;
+ info->fix.smem_start = (unsigned long)bo->dma_addr + offset;
+ info->fix.smem_len = size;
+
+ return 0;
+
+cleanup:
+ drm_framebuffer_unregister_private(helper->fb);
+ hdlcd_fb_bo_free(bo);
+ framebuffer_release(info);
+ return err;
+}
+
+static struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = {
+ .fb_probe = hdlcd_fb_probe,
+};
+
+static void hdlcd_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct hdlcd_drm_private *hdlcd = fb->dev->dev_private;
+ drm_gem_object_unreference_unlocked(&hdlcd->bo->gem);
+ drm_framebuffer_cleanup(fb);
+ kfree(hdlcd->bo);
+}
+
+static int hdlcd_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned int *handle)
+{
+ struct hdlcd_drm_private *hdlcd = fb->dev->dev_private;
+ return drm_gem_handle_create(file_priv, &hdlcd->bo->gem, handle);
+}
+
+static int hdlcd_fb_dirty(struct drm_framebuffer *fb,
+ struct drm_file *file_priv, unsigned flags,
+ unsigned color, struct drm_clip_rect *clips,
+ unsigned num_clips)
+{
+ return 0;
+}
+
+static struct drm_framebuffer_funcs hdlcd_fb_funcs = {
+ .destroy = hdlcd_fb_destroy,
+ .create_handle = hdlcd_fb_create_handle,
+ .dirty = hdlcd_fb_dirty,
+};
+
+static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ int err;
+ struct drm_framebuffer *fb;
+
+ fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+ if (!fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(fb, mode_cmd);
+
+ err = drm_framebuffer_init(dev, fb, &hdlcd_fb_funcs);
+ if (err) {
+ dev_err(dev->dev, "failed to initialize framebuffer.\n");
+ kfree(fb);
+ return ERR_PTR(err);
+ }
+
+ return fb;
+}
+
+static 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_object *obj;
+ struct hdlcd_bo *bo;
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+ obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
+ if (!obj) {
+ dev_err(dev->dev, "failed to lookup GEM object\n");
+ return ERR_PTR(-ENXIO);
+ }
+
+ bo = to_hdlcd_bo_obj(obj);
+ hdlcd->fb_helper->fb = hdlcd_fb_alloc(dev, mode_cmd);
+ if (IS_ERR(hdlcd->fb_helper->fb)) {
+ dev_err(dev->dev, "failed to allocate DRM framebuffer\n");
+ }
+ hdlcd->bo = bo;
+
+ return hdlcd->fb_helper->fb;
+}
+
+int hdlcd_fbdev_init(struct drm_device *dev)
+{
+ int ret;
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+ hdlcd->fb_helper = kzalloc(sizeof(*hdlcd->fb_helper), GFP_KERNEL);
+ if (!hdlcd->fb_helper)
+ return -ENOMEM;
+
+ hdlcd->fb_helper->funcs = &hdlcd_fb_helper_funcs;
+ ret = drm_fb_helper_init(dev, hdlcd->fb_helper, dev->mode_config.num_crtc,
+ dev->mode_config.num_connector);
+ 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(hdlcd->fb_helper);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to add connectors.\n");
+ goto err_helper_fini;
+ }
+
+ drm_helper_disable_unused_functions(dev);
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ ret = drm_fb_helper_initial_config(hdlcd->fb_helper, 32);
+ if (ret < 0) {
+ dev_err(dev->dev, "Failed to set initial hw configuration.\n");
+ goto err_helper_fini;
+ }
+
+ return 0;
+
+err_helper_fini:
+ drm_fb_helper_fini(hdlcd->fb_helper);
+
+err_free:
+ kfree(hdlcd->fb_helper);
+ hdlcd->fb_helper = NULL;
+
+ return ret;
+}
+
+static void hdlcd_fb_output_poll_changed(struct drm_device *dev)
+{
+ struct hdlcd_drm_private *hdlcd = dev->dev_private;
+ struct drm_fb_helper *fb_helper = hdlcd->fb_helper;
+
+ drm_fb_helper_hotplug_event(fb_helper);
+}
+
+static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
+ .fb_create = hdlcd_fb_create,
+ .output_poll_changed = hdlcd_fb_output_poll_changed,
+};
+
+
+void hdlcd_drm_mode_config_init(struct drm_device *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;
+}
diff --git a/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c
new file mode 100644
index 000000000000..5f6a4b4ff9f6
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * Theory of operation:
+ *
+ * The DRM framework expects the CRTC -> Encoder -> Connector chain,
+ * where the CRTC is reading from framebuffer, passes data to the
+ * encoder and that formats the signals to something usable by the
+ * attached connector(s). Connectors can use i2c links to talk with
+ * attached monitors.
+ *
+ * The HDMI transmitter is a different beast: it is both and encoder
+ * and a connector in DRM parlance *and* can only be reached via i2c.
+ * It implements an i2c pass through mode for the situation where one
+ * wants to talk with the attached monitor. To complicate things
+ * even further, the VExpress boards that have the SiI9022 chip share
+ * the i2c line between the on-board microcontroller and the CoreTiles.
+ * This leads to a situation where the microcontroller might be able to
+ * talk with the SiI9022 transmitter, but not the CoreTile. And the
+ * micro has a very small brain and a list of hardcoded modes that
+ * it can program into the HDMI transmitter, so only a limited set
+ * of resolutions will be valid.
+ *
+ * This file handles only the case where the i2c connection is available
+ * to the kernel. For the case where we have to ask the microcontroller
+ * to do the modesetting for us see the hdlcd_vexpress_encoder.c file.
+ */
+
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/tda998x.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "hdlcd_drv.h"
+
+static inline struct drm_encoder_slave *
+hdlcd_get_slave_encoder(struct drm_connector * connector)
+{
+ return to_encoder_slave(hdlcd_connector_best_encoder(connector));
+}
+
+static void hdlcd_connector_destroy(struct drm_connector *connector)
+{
+}
+
+static enum drm_connector_status
+hdlcd_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct drm_encoder_slave *slave;
+ if (!connector->encoder)
+ connector->encoder = hdlcd_connector_best_encoder(connector);
+
+ slave = hdlcd_get_slave_encoder(connector);
+ if (!slave || !slave->slave_funcs)
+ return connector_status_unknown;
+
+ return slave->slave_funcs->detect(connector->encoder, connector);
+}
+
+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,
+};
+
+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_hdmi_con_get_modes(struct drm_connector *connector)
+{
+ struct drm_encoder_slave *slave = hdlcd_get_slave_encoder(connector);
+
+ if (slave && slave->slave_funcs)
+ return slave->slave_funcs->get_modes(&slave->base, connector);
+
+ return 0;
+}
+
+static int hdlcd_hdmi_con_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct drm_encoder_slave *slave = hdlcd_get_slave_encoder(connector);
+
+ if (slave && slave->slave_funcs)
+ return slave->slave_funcs->mode_valid(connector->encoder, mode);
+
+ return MODE_ERROR;
+}
+
+static const struct drm_connector_helper_funcs hdlcd_hdmi_con_helper_funcs = {
+ .get_modes = hdlcd_hdmi_con_get_modes,
+ .mode_valid = hdlcd_hdmi_con_mode_valid,
+ .best_encoder = hdlcd_connector_best_encoder,
+};
+
+
+static struct drm_encoder_funcs hdlcd_encoder_funcs = {
+ .destroy = drm_i2c_encoder_destroy,
+};
+
+static void hdlcd_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static struct drm_encoder_helper_funcs hdlcd_encoder_helper_funcs = {
+ .dpms = drm_i2c_encoder_dpms,
+ .save = drm_i2c_encoder_save,
+ .restore = drm_i2c_encoder_restore,
+ .mode_fixup = drm_i2c_encoder_mode_fixup,
+ .prepare = drm_i2c_encoder_prepare,
+ .commit = drm_i2c_encoder_commit,
+ .mode_set = drm_i2c_encoder_mode_set,
+ .detect = drm_i2c_encoder_detect,
+ .disable = hdlcd_hdmi_encoder_disable,
+};
+
+static struct tda998x_encoder_params tda998x_params = {
+ .swap_a = 2,
+ .swap_b = 3,
+ .swap_c = 4,
+ .swap_d = 5,
+ .swap_e = 0,
+ .swap_f = 1,
+};
+
+int hdlcd_create_digital_connector(struct drm_device *dev,
+ struct hdlcd_drm_private *hdlcd)
+{
+ int err;
+ struct i2c_board_info i2c_info;
+ struct drm_encoder_slave *slave;
+ struct drm_connector *connector;
+ struct device_node *node = hdlcd->slave_node;
+
+ slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+ if (!slave)
+ return -ENOMEM;
+
+ slave->base.possible_crtcs = 1;
+ slave->base.possible_clones = 0;
+
+ err = drm_encoder_init(dev, &slave->base, &hdlcd_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS);
+ if (err)
+ goto encoder_init_err;
+
+ drm_encoder_helper_add(&slave->base, &hdlcd_encoder_helper_funcs);
+
+ /* get the driver for the i2c slave node */
+ i2c_info.of_node = node;
+ err = of_modalias_node(node, i2c_info.type, sizeof(i2c_info.type));
+ if (err < 0) {
+ dev_err(dev->dev, "failed to get a module alias for node %s\n",
+ node->full_name);
+ }
+
+ /* Hack: this needs to be specified in the device tree */
+ i2c_info.platform_data = &tda998x_params;
+
+ err = drm_i2c_encoder_init(dev, slave, NULL, &i2c_info);
+ of_node_put(node);
+ if (err)
+ goto connector_alloc_err;
+
+ connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+ if (!connector) {
+ err = -ENOMEM;
+ goto connector_alloc_err;
+ }
+
+ connector->interlace_allowed = false;
+ connector->doublescan_allowed = false;
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ 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_hdmi_con_helper_funcs);
+
+ connector->encoder = &slave->base;
+ err = drm_mode_connector_attach_encoder(connector, &slave->base);
+ if (err) {
+ goto connector_attach_err;
+ }
+
+ drm_sysfs_connector_add(connector);
+
+ slave->base.dev->dev->platform_data = hdlcd;
+ return err;
+
+connector_attach_err:
+ drm_connector_cleanup(connector);
+connector_init_err:
+ kfree(connector);
+connector_alloc_err:
+ drm_encoder_cleanup(&slave->base);
+encoder_init_err:
+ kfree(slave);
+
+ return err;
+}
diff --git a/drivers/gpu/drm/arm/hdlcd_regs.h b/drivers/gpu/drm/arm/hdlcd_regs.h
new file mode 100644
index 000000000000..571dad4a6f37
--- /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_BLUE_SELECT 0x0244 /* rw */
+#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */
+#define HDLCD_REG_RED_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..e96a62343648
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c
@@ -0,0 +1,242 @@
+/*
+ * 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)
+{
+ int i, vrefresh = drm_mode_vrefresh(mode);
+
+ if (vconfig_func) {
+ 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) {
+ dev_err(dev->dev, "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..d9feb066a311
--- /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) {
+ dev_err(dev->dev, "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/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 83f0ba5859c0..4184970c1e36 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -1597,13 +1597,8 @@ drm_mode_std(struct drm_connector *connector, struct edid *edid,
}
/* check whether it can be found in default mode table */
- if (drm_monitor_supports_rb(edid)) {
- mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate,
- true);
- if (mode)
- return mode;
- }
- mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate, false);
+ mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate,
+ drm_monitor_supports_rb(edid));
if (mode)
return mode;
@@ -1994,7 +1989,8 @@ do_inferred_modes(struct detailed_timing *timing, void *c)
closure->edid,
timing);
- if (!version_greater(closure->edid, 1, 1))
+ if (!version_greater(closure->edid, 1, 1) ||
+ !(closure->edid->features & DRM_EDID_FEATURE_DEFAULT_GTF))
return; /* GTF not defined yet */
switch (range->flags) {
@@ -2951,8 +2947,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
num_modes += add_cvt_modes(connector, edid);
num_modes += add_standard_modes(connector, edid);
num_modes += add_established_modes(connector, edid);
- if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF)
- num_modes += add_inferred_modes(connector, edid);
+ num_modes += add_inferred_modes(connector, edid);
num_modes += add_cea_modes(connector, edid);
if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75))
diff --git a/drivers/gpu/drm/drm_encoder_slave.c b/drivers/gpu/drm/drm_encoder_slave.c
index 0cfb60f54766..36c0aa740bcd 100644
--- a/drivers/gpu/drm/drm_encoder_slave.c
+++ b/drivers/gpu/drm/drm_encoder_slave.c
@@ -25,6 +25,7 @@
*/
#include <linux/module.h>
+#include <linux/of_i2c.h>
#include <drm/drm_encoder_slave.h>
@@ -61,7 +62,10 @@ int drm_i2c_encoder_init(struct drm_device *dev,
request_module("%s%s", I2C_MODULE_PREFIX, info->type);
- client = i2c_new_device(adap, info);
+ if (info->of_node)
+ client = of_find_i2c_device_by_node(info->of_node);
+ else
+ client = i2c_new_device(adap, info);
if (!client) {
err = -ENOMEM;
goto fail;
diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index 239ef30f4a62..c9d7081acf59 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -640,6 +640,55 @@ void drm_gem_vm_close(struct vm_area_struct *vma)
}
EXPORT_SYMBOL(drm_gem_vm_close);
+/**
+ * drm_gem_mmap_obj - memory map a GEM object
+ * @obj: the GEM object to map
+ * @obj_size: the object size to be mapped, in bytes
+ * @vma: VMA for the area to be mapped
+ *
+ * Set up the VMA to prepare mapping of the GEM object using the gem_vm_ops
+ * provided by the driver. Depending on their requirements, drivers can either
+ * provide a fault handler in their gem_vm_ops (in which case any accesses to
+ * the object will be trapped, to perform migration, GTT binding, surface
+ * register allocation, or performance monitoring), or mmap the buffer memory
+ * synchronously after calling drm_gem_mmap_obj.
+ *
+ * This function is mainly intended to implement the DMABUF mmap operation, when
+ * the GEM object is not looked up based on its fake offset. To implement the
+ * DRM mmap operation, drivers should use the drm_gem_mmap() function.
+ *
+ * Return 0 or success or -EINVAL if the object size is smaller than the VMA
+ * size, or if no gem_vm_ops are provided.
+ */
+int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
+ struct vm_area_struct *vma)
+{
+ struct drm_device *dev = obj->dev;
+
+ /* Check for valid size. */
+ if (obj_size < vma->vm_end - vma->vm_start)
+ return -EINVAL;
+
+ if (!dev->driver->gem_vm_ops)
+ return -EINVAL;
+
+ vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
+ vma->vm_ops = dev->driver->gem_vm_ops;
+ vma->vm_private_data = obj;
+ vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+
+ /* Take a ref for this mapping of the object, so that the fault
+ * handler can dereference the mmap offset's pointer to the object.
+ * This reference is cleaned up by the corresponding vm_close
+ * (which should happen whether the vma was created by this call, or
+ * by a vm_open due to mremap or partial unmap or whatever).
+ */
+ drm_gem_object_reference(obj);
+
+ drm_vm_open_locked(dev, vma);
+ return 0;
+}
+EXPORT_SYMBOL(drm_gem_mmap_obj);
/**
* drm_gem_mmap - memory map routine for GEM objects
@@ -649,11 +698,9 @@ EXPORT_SYMBOL(drm_gem_vm_close);
* If a driver supports GEM object mapping, mmap calls on the DRM file
* descriptor will end up here.
*
- * If we find the object based on the offset passed in (vma->vm_pgoff will
+ * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
* contain the fake offset we created when the GTT map ioctl was called on
- * the object), we set up the driver fault handler so that any accesses
- * to the object can be trapped, to perform migration, GTT binding, surface
- * register allocation, or performance monitoring.
+ * the object) and map it with a call to drm_gem_mmap_obj().
*/
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{
@@ -661,7 +708,6 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
struct drm_device *dev = priv->minor->dev;
struct drm_gem_mm *mm = dev->mm_private;
struct drm_local_map *map = NULL;
- struct drm_gem_object *obj;
struct drm_hash_item *hash;
int ret = 0;
@@ -682,32 +728,7 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
goto out_unlock;
}
- /* Check for valid size. */
- if (map->size < vma->vm_end - vma->vm_start) {
- ret = -EINVAL;
- goto out_unlock;
- }
-
- obj = map->handle;
- if (!obj->dev->driver->gem_vm_ops) {
- ret = -EINVAL;
- goto out_unlock;
- }
-
- vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
- vma->vm_ops = obj->dev->driver->gem_vm_ops;
- vma->vm_private_data = map->handle;
- vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
-
- /* Take a ref for this mapping of the object, so that the fault
- * handler can dereference the mmap offset's pointer to the object.
- * This reference is cleaned up by the corresponding vm_close
- * (which should happen whether the vma was created by this call, or
- * by a vm_open due to mremap or partial unmap or whatever).
- */
- drm_gem_object_reference(obj);
-
- drm_vm_open_locked(dev, vma);
+ ret = drm_gem_mmap_obj(map->handle, map->size, vma);
out_unlock:
mutex_unlock(&dev->struct_mutex);
diff --git a/drivers/gpu/drm/drm_gem_cma_helper.c b/drivers/gpu/drm/drm_gem_cma_helper.c
index c25b7a0eb0e3..23d9f408ef18 100644
--- a/drivers/gpu/drm/drm_gem_cma_helper.c
+++ b/drivers/gpu/drm/drm_gem_cma_helper.c
@@ -35,8 +35,12 @@ static unsigned int get_gem_mmap_offset(struct drm_gem_object *obj)
static void drm_gem_cma_buf_destroy(struct drm_device *drm,
struct drm_gem_cma_object *cma_obj)
{
- dma_free_writecombine(drm->dev, cma_obj->base.size, cma_obj->vaddr,
- cma_obj->paddr);
+ DEFINE_DMA_ATTRS(attrs);
+
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
+
+ dma_free_attrs(drm->dev, cma_obj->base.size,
+ cma_obj->vaddr, cma_obj->paddr, &attrs);
}
/*
@@ -51,6 +55,9 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
struct drm_gem_cma_object *cma_obj;
struct drm_gem_object *gem_obj;
int ret;
+ DEFINE_DMA_ATTRS(attrs);
+
+ dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs);
size = round_up(size, PAGE_SIZE);
@@ -58,8 +65,8 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
if (!cma_obj)
return ERR_PTR(-ENOMEM);
- cma_obj->vaddr = dma_alloc_writecombine(drm->dev, size,
- &cma_obj->paddr, GFP_KERNEL | __GFP_NOWARN);
+ cma_obj->vaddr = dma_alloc_attrs(drm->dev, size,
+ &cma_obj->paddr, GFP_KERNEL | __GFP_NOWARN, &attrs);
if (!cma_obj->vaddr) {
dev_err(drm->dev, "failed to allocate buffer with size %d\n", size);
ret = -ENOMEM;
@@ -139,7 +146,6 @@ err_handle_create:
void drm_gem_cma_free_object(struct drm_gem_object *gem_obj)
{
struct drm_gem_cma_object *cma_obj;
-
if (gem_obj->map_list.map)
drm_gem_free_mmap_offset(gem_obj);
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
index 4d341db462a2..508e1e6f3194 100644
--- a/drivers/gpu/drm/i2c/Kconfig
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -11,6 +11,13 @@ config DRM_I2C_CH7006
This driver is currently only useful if you're also using
the nouveau driver.
+config DRM_I2C_SII9022
+ tristate "Silicon Image SiI9022 HDMI transmitter"
+ default m if DRM_HDLCD
+ help
+ Support for SiI9022 HDMI transmitter, used on the
+ ARM Versatile Express motherboards.
+
config DRM_I2C_SIL164
tristate "Silicon Image sil164 TMDS transmitter"
default m if DRM_NOUVEAU
diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 43aa33baebed..c1e2ee0004c3 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -3,8 +3,11 @@ ccflags-y := -Iinclude/drm
ch7006-y := ch7006_drv.o ch7006_mode.o
obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
+sii9022-y := sii9022_drv.o
+obj-$(CONFIG_DRM_I2C_SII9022) += sii9022.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/sii9022_drv.c b/drivers/gpu/drm/i2c/sii9022_drv.c
new file mode 100644
index 000000000000..5ba82d0182f5
--- /dev/null
+++ b/drivers/gpu/drm/i2c/sii9022_drv.c
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ * Silicon Image SiI9022 driver for DRM I2C encoder slave
+ */
+
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+
+#define SII9022_INFORMAT_REG 0x09
+#define SII9022_OUTFORMAT_REG 0x0a
+#define SII9022_SYS_CONTROL_REG 0x1a
+# define SII9022_DDC_OUT_MODE (1 << 0)
+# define SII9022_DDC_BUS_STAT (1 << 1)
+# define SII9022_DDC_BUS_GRANT (1 << 2)
+# define SII9022_AV_MUTE (1 << 3)
+# define SII9022_OUTPUT_EN (1 << 4)
+#define SII9022_ID_REG 0x1b
+#define SII9022_POWER_REG 0x1e
+#define SII9022_SEC_CTRL_REG 0x2a
+#define SII9022_SEC_STATUS_REG 0x29
+#define SII9022_SEC_VERSION_REG 0x30
+#define SII9022_INTR_REG 0x3c
+#define SII9022_INTR_STATUS 0x3d
+# define SII9022_HOTPLUG_EVENT (1 << 0)
+# define SII9022_RECEIVER_EVENT (1 << 1)
+# define SII9022_HOTPLUG_STATE (1 << 2)
+# define SII9022_RECEIVER_STATE (1 << 3)
+#define SII9022_VENDOR_ID 0xb0
+#define SII9022_INTERNAL_PAGE 0xbc
+#define SII9022_INTERNAL_INDEX 0xbd
+#define SII9022_INTERNAL_REG 0xbe
+#define SII9022_CTRL_REG 0xc7
+
+
+struct sii9022_video_regs {
+ uint16_t pixel_clock;
+ uint16_t vrefresh;
+ uint16_t cols;
+ uint16_t lines;
+ uint8_t pixel_data;
+};
+
+static void sii9022_write(struct i2c_client *client, uint8_t addr, uint8_t val)
+{
+ uint8_t buf[] = { addr, val };
+ int ret;
+
+ ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error writing to subaddress 0x%x: %d\n", addr, ret);
+}
+
+static uint8_t sii9022_read(struct i2c_client *client, uint8_t addr)
+{
+ uint8_t val;
+ int ret;
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, &val, sizeof(val));
+ if (ret < 0)
+ goto fail;
+
+ return val;
+
+fail:
+ dev_err(&client->dev, "Error reading from subaddress 0x%x: %d\n", addr, ret);
+ return 0;
+}
+
+static void sii9022_encoder_set_config(struct drm_encoder *encoder, void *params)
+{
+}
+
+static void sii9022_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ uint8_t val;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_OFF:
+ val = sii9022_read(client, SII9022_SYS_CONTROL_REG);
+ val |= SII9022_OUTPUT_EN;
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, val);
+ /* wait for AVI InfoFrames to flush */
+ mdelay(128);
+ /* use D2 for OFF as we cannot control the reset pin */
+ sii9022_write(client, SII9022_POWER_REG, 0x2);
+ break;
+ case DRM_MODE_DPMS_ON:
+ val = sii9022_read(client, SII9022_SYS_CONTROL_REG);
+ val &= ~SII9022_OUTPUT_EN;
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, val);
+ /* fall through */
+ default:
+ sii9022_write(client, SII9022_POWER_REG, mode);
+ break;
+ }
+}
+
+static enum drm_connector_status
+sii9022_encoder_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ enum drm_connector_status con_status = connector_status_unknown;
+ uint8_t status = sii9022_read(client, SII9022_INTR_STATUS);
+
+ if (status & SII9022_HOTPLUG_STATE)
+ con_status = connector_status_connected;
+ else
+ con_status = connector_status_disconnected;
+
+ /* clear the event status bits */
+ sii9022_write(client, SII9022_INTR_STATUS,
+ 0xff /*SII9022_HOTPLUG_EVENT | SII9022_RECEIVER_EVENT */);
+ status = sii9022_read(client, SII9022_INTR_STATUS);
+
+ return con_status;
+}
+
+static int sii9022_encoder_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct edid *edid = NULL;
+ uint8_t status;
+ int ret = 0, timeout = 10;
+
+ /* Disable HDCP link security */
+ do {
+ sii9022_write(client, SII9022_SEC_CTRL_REG, 0);
+ status = sii9022_read(client, SII9022_SEC_CTRL_REG);
+ } while (status);
+ status = sii9022_read(client, SII9022_SEC_STATUS_REG);
+
+ /* first, request the pass-through mode in order to read the edid */
+ status = sii9022_read(client, SII9022_SYS_CONTROL_REG);
+ status |= SII9022_DDC_BUS_GRANT;
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, status);
+ do {
+ /* wait for state change */
+ status = sii9022_read(client, SII9022_SYS_CONTROL_REG);
+ --timeout;
+ } while (((status & SII9022_DDC_BUS_STAT) != SII9022_DDC_BUS_STAT) && timeout);
+
+ if (!timeout) {
+ dev_warn(&client->dev, "timeout waiting for DDC bus grant\n");
+ goto release_ddc;
+ }
+
+ /* write back the value read in order to close the i2c switch */
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, status);
+
+ edid = drm_get_edid(connector, client->adapter);
+ if (!edid) {
+ dev_err(&client->dev, "failed to get EDID data\n");
+ ret = -1;
+ }
+
+release_ddc:
+ timeout = 10;
+ do {
+ status &= ~(SII9022_DDC_BUS_STAT | SII9022_DDC_BUS_GRANT);
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, status);
+ status = sii9022_read(client, SII9022_SYS_CONTROL_REG);
+ --timeout;
+ } while ((status & (SII9022_DDC_BUS_STAT | SII9022_DDC_BUS_GRANT)) && timeout);
+
+ if (edid) {
+ drm_mode_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
+ if (drm_detect_hdmi_monitor(edid))
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, status | 1);
+ else
+ sii9022_write(client, SII9022_SYS_CONTROL_REG, status & 0xfe);
+ kfree(edid);
+ }
+
+ return ret;
+}
+
+static bool sii9022_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static int sii9022_encoder_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static void sii9022_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ /* SiI9022 clock is pixclock / 10000 Hz */
+ int clk = adjusted_mode->clock / 10;
+ int i, vrefresh = adjusted_mode->vrefresh * 100;
+ uint8_t buf[15]; /* start address reg + 14 bytes max */
+
+ /* Set Video Mode (8 registers block) */
+ buf[0] = 0; /* start register */
+ buf[1] = clk & 0xff;
+ buf[2] = (clk & 0xff00) >> 8;
+ buf[3] = vrefresh & 0xff;
+ buf[4] = (vrefresh & 0xff00) >> 8;
+ buf[5] = adjusted_mode->crtc_hdisplay & 0xff;
+ buf[6] = (adjusted_mode->crtc_hdisplay & 0xff00) >> 8;
+ buf[7] = adjusted_mode->crtc_vdisplay & 0xff;
+ buf[8] = (adjusted_mode->crtc_vdisplay & 0xff00) >> 8;
+
+ if (i2c_master_send(client, buf, 9) < 0) {
+ dev_err(&client->dev, "Could not write video mode data\n");
+ return;
+ }
+
+ /* input is full range RGB */
+ sii9022_write(client, SII9022_INFORMAT_REG, 0x04);
+ /* output is full range digital RGB */
+ sii9022_write(client, SII9022_OUTFORMAT_REG, 0x17);
+
+ /* set the AVI InfoFrame (14 registers block */
+ buf[0] = 0x0c; /* start register */
+ buf[1] = 0x0e; /* AVI_DBYTE0 = checksum */
+ buf[2] = 0x10; /* AVI_DBYTE1 */
+ buf[3] = 0x50; /* AVI_DBYTE2 (colorimetry) */
+ buf[4] = 0; /* AVI_DBYTE3 (scaling) */
+ buf[5] = drm_match_cea_mode(adjusted_mode);
+ buf[6] = 0; /* AVI_DBYTE5 (pixel repetition factor) */
+ buf[7] = 0;
+ buf[8] = 0;
+ buf[9] = 0;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+ buf[14] = 0;
+
+ /* calculate checksum */
+ buf[1] = 0x82 + 0x02 + 13; /* Identifier code for AVI InfoFrame, length */
+ for (i = 2; i < 15; i++)
+ buf[1] += buf[i];
+ buf[1] = 0x100 - buf[1];
+
+ if (i2c_master_send(client, buf, ARRAY_SIZE(buf)) < 0) {
+ dev_err(&client->dev, "Could not write video mode data\n");
+ return;
+ }
+}
+
+static struct drm_encoder_slave_funcs sii9022_encoder_funcs = {
+ .set_config = sii9022_encoder_set_config,
+ .dpms = sii9022_encoder_dpms,
+ .detect = sii9022_encoder_detect,
+ .get_modes = sii9022_encoder_get_modes,
+ .mode_fixup = sii9022_encoder_mode_fixup,
+ .mode_valid = sii9022_encoder_mode_valid,
+ .mode_set = sii9022_encoder_mode_set,
+};
+
+static int sii9022_encoder_init(struct i2c_client *client,
+ struct drm_device *dev,
+ struct drm_encoder_slave *encoder)
+{
+ encoder->slave_funcs = &sii9022_encoder_funcs;
+
+ return 0;
+}
+
+static struct i2c_device_id sii9022_ids[] = {
+ { "sii9022-tpi", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, sii9022_ids);
+
+static int sii9022_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ int dev_id, dev_rev;
+
+ /* first step is to enable the TPI mode */
+ sii9022_write(client, SII9022_CTRL_REG, 0x00);
+ dev_id = sii9022_read(client, SII9022_ID_REG);
+ dev_rev = sii9022_read(client, SII9022_ID_REG+1);
+ if (dev_id != SII9022_VENDOR_ID) {
+ printk(KERN_INFO "sii9022 not found\n");
+ return -ENODEV;
+ }
+ dev_id = sii9022_read(client, SII9022_SEC_VERSION_REG);
+ dev_info(&client->dev, "found %s chip (rev %01u.%01u)\n",
+ dev_id ? "SiI9024" : "SiI9022",
+ (dev_rev >> 4) & 0xf, dev_rev & 0xf);
+
+ /* disable interrupts */
+ sii9022_write(client, SII9022_INTR_REG, 0x00);
+
+ return 0;
+}
+
+static int sii9022_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+static struct drm_i2c_encoder_driver sii9022_driver = {
+ .i2c_driver = {
+ .probe = sii9022_probe,
+ .remove = sii9022_remove,
+ .driver = {
+ .name = "sii9022-tpi",
+ },
+ .id_table = sii9022_ids,
+ .class = I2C_CLASS_DDC,
+ },
+ .encoder_init = sii9022_encoder_init,
+};
+
+static int __init sii9022_init(void)
+{
+ return drm_i2c_encoder_register(THIS_MODULE, &sii9022_driver);
+}
+
+static void __exit sii9022_exit(void)
+{
+ drm_i2c_encoder_unregister(&sii9022_driver);
+}
+
+module_init(sii9022_init);
+module_exit(sii9022_exit);
+
+MODULE_ALIAS(I2C_MODULE_PREFIX "sii9022-tpi");
+MODULE_AUTHOR("Liviu Dudau <Liviu.Dudau@arm.com>");
+MODULE_DESCRIPTION("Silicon Image SiI9022 HDMI transmitter driver");
+MODULE_LICENSE("GPL v2");
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 e68b58a1aaf9..63723e8fea87 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -17,23 +17,22 @@
+#include <linux/hdmi.h>
#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>
#include <drm/drm_encoder_slave.h>
#include <drm/drm_edid.h>
+#include <drm/i2c/tda998x.h>
+#include "tda998x_drv.h"
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
-struct tda998x_priv {
- struct i2c_client *cec;
- uint16_t rev;
- uint8_t current_page;
- int dpms;
-};
-
#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
/* The TDA9988 series of devices use a paged register scheme.. to simplify
@@ -68,10 +67,13 @@ struct tda998x_priv {
# define I2C_MASTER_DIS_MM (1 << 0)
# define I2C_MASTER_DIS_FILT (1 << 1)
# define I2C_MASTER_APP_STRT_LAT (1 << 2)
+#define REG_FEAT_POWERDOWN REG(0x00, 0x0e) /* read/write */
+# define FEAT_POWERDOWN_SPDIF (1 << 3)
#define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */
#define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */
#define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */
# define INT_FLAGS_2_EDID_BLK_RD (1 << 1)
+#define REG_ENA_ACLK REG(0x00, 0x16) /* read/write */
#define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */
#define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */
#define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */
@@ -110,6 +112,10 @@ struct tda998x_priv {
#define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */
# define VIP_CNTRL_5_CKCASE (1 << 0)
# define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1)
+#define REG_MUX_AP REG(0x00, 0x26) /* read/write */
+# define MUX_AP_SELECT_I2S 0x64
+# define MUX_AP_SELECT_SPDIF 0x40
+#define REG_MUX_VP_VIP_OUT REG(0x00, 0x27) /* read/write */
#define REG_MAT_CONTRL REG(0x00, 0x80) /* write */
# define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0)
# define MAT_CONTRL_MAT_BP (1 << 2)
@@ -130,8 +136,12 @@ struct tda998x_priv {
#define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */
#define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */
#define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */
+#define REG_VS_LINE_STRT_2_MSB REG(0x00, 0xb1) /* write */
+#define REG_VS_LINE_STRT_2_LSB REG(0x00, 0xb2) /* write */
#define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */
#define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */
+#define REG_VS_LINE_END_2_MSB REG(0x00, 0xb5) /* write */
+#define REG_VS_LINE_END_2_LSB REG(0x00, 0xb6) /* write */
#define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */
#define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */
#define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */
@@ -142,21 +152,29 @@ struct tda998x_priv {
#define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */
#define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */
#define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */
+#define REG_VWIN_START_2_MSB REG(0x00, 0xc1) /* write */
+#define REG_VWIN_START_2_LSB REG(0x00, 0xc2) /* write */
+#define REG_VWIN_END_2_MSB REG(0x00, 0xc3) /* write */
+#define REG_VWIN_END_2_LSB REG(0x00, 0xc4) /* write */
#define REG_DE_START_MSB REG(0x00, 0xc5) /* write */
#define REG_DE_START_LSB REG(0x00, 0xc6) /* write */
#define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */
#define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */
#define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */
+# define TBG_CNTRL_0_TOP_TGL (1 << 0)
+# define TBG_CNTRL_0_TOP_SEL (1 << 1)
+# define TBG_CNTRL_0_DE_EXT (1 << 2)
+# define TBG_CNTRL_0_TOP_EXT (1 << 3)
# define TBG_CNTRL_0_FRAME_DIS (1 << 5)
# define TBG_CNTRL_0_SYNC_MTHD (1 << 6)
# define TBG_CNTRL_0_SYNC_ONCE (1 << 7)
#define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */
-# define TBG_CNTRL_1_VH_TGL_0 (1 << 0)
-# define TBG_CNTRL_1_VH_TGL_1 (1 << 1)
-# define TBG_CNTRL_1_VH_TGL_2 (1 << 2)
-# define TBG_CNTRL_1_VHX_EXT_DE (1 << 3)
-# define TBG_CNTRL_1_VHX_EXT_HS (1 << 4)
-# define TBG_CNTRL_1_VHX_EXT_VS (1 << 5)
+# define TBG_CNTRL_1_H_TGL (1 << 0)
+# define TBG_CNTRL_1_V_TGL (1 << 1)
+# define TBG_CNTRL_1_TGL_EN (1 << 2)
+# define TBG_CNTRL_1_X_EXT (1 << 3)
+# define TBG_CNTRL_1_H_EXT (1 << 4)
+# define TBG_CNTRL_1_V_EXT (1 << 5)
# define TBG_CNTRL_1_DWIN_DIS (1 << 6)
#define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */
#define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */
@@ -171,7 +189,14 @@ struct tda998x_priv {
# define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4)
# define HVF_CNTRL_1_SEMI_PLANAR (1 << 6)
#define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */
-
+#define REG_I2S_FORMAT REG(0x00, 0xfc) /* read/write */
+# define I2S_FORMAT(x) (((x) & 3) << 0)
+#define REG_AIP_CLKSEL REG(0x00, 0xfd) /* write */
+# define AIP_CLKSEL_AIP_SPDIF (0 << 3)
+# define AIP_CLKSEL_AIP_I2S (1 << 3)
+# define AIP_CLKSEL_FS_ACLK (0 << 0)
+# define AIP_CLKSEL_FS_MCLK (1 << 0)
+# define AIP_CLKSEL_FS_FS64SPDIF (2 << 0)
/* Page 02h: PLL settings */
#define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */
@@ -179,7 +204,7 @@ struct tda998x_priv {
# define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1)
# define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6)
#define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */
-# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0)
+# define PLL_SERIAL_2_SRL_NOSC(x) ((x) << 0)
# define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4)
#define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */
# define PLL_SERIAL_3_SRL_CCIR (1 << 0)
@@ -194,6 +219,12 @@ struct tda998x_priv {
#define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */
#define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */
#define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */
+# define AUDIO_DIV_SERCLK_1 0
+# define AUDIO_DIV_SERCLK_2 1
+# define AUDIO_DIV_SERCLK_4 2
+# define AUDIO_DIV_SERCLK_8 3
+# define AUDIO_DIV_SERCLK_16 4
+# define AUDIO_DIV_SERCLK_32 5
#define REG_SEL_CLK REG(0x02, 0x11) /* read/write */
# define SEL_CLK_SEL_CLK1 (1 << 0)
# define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1)
@@ -212,6 +243,11 @@ struct tda998x_priv {
/* Page 10h: information frames and packets */
+#define REG_IF1_HB0 REG(0x10, 0x20) /* read/write */
+#define REG_IF2_HB0 REG(0x10, 0x40) /* read/write */
+#define REG_IF3_HB0 REG(0x10, 0x60) /* read/write */
+#define REG_IF4_HB0 REG(0x10, 0x80) /* read/write */
+#define REG_IF5_HB0 REG(0x10, 0xa0) /* read/write */
/* Page 11h: audio settings and content info packets */
@@ -221,14 +257,39 @@ struct tda998x_priv {
# define AIP_CNTRL_0_LAYOUT (1 << 2)
# define AIP_CNTRL_0_ACR_MAN (1 << 5)
# define AIP_CNTRL_0_RST_CTS (1 << 6)
+#define REG_CA_I2S REG(0x11, 0x01) /* read/write */
+# define CA_I2S_CA_I2S(x) (((x) & 31) << 0)
+# define CA_I2S_HBR_CHSTAT (1 << 6)
+#define REG_LATENCY_RD REG(0x11, 0x04) /* read/write */
+#define REG_ACR_CTS_0 REG(0x11, 0x05) /* read/write */
+#define REG_ACR_CTS_1 REG(0x11, 0x06) /* read/write */
+#define REG_ACR_CTS_2 REG(0x11, 0x07) /* read/write */
+#define REG_ACR_N_0 REG(0x11, 0x08) /* read/write */
+#define REG_ACR_N_1 REG(0x11, 0x09) /* read/write */
+#define REG_ACR_N_2 REG(0x11, 0x0a) /* read/write */
+#define REG_CTS_N REG(0x11, 0x0c) /* read/write */
+# define CTS_N_K(x) (((x) & 7) << 0)
+# define CTS_N_M(x) (((x) & 3) << 4)
#define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */
# define ENC_CNTRL_RST_ENC (1 << 0)
# define ENC_CNTRL_RST_SEL (1 << 1)
# define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2)
+#define REG_DIP_FLAGS REG(0x11, 0x0e) /* read/write */
+# define DIP_FLAGS_ACR (1 << 0)
+# define DIP_FLAGS_GC (1 << 1)
+#define REG_DIP_IF_FLAGS REG(0x11, 0x0f) /* read/write */
+# define DIP_IF_FLAGS_IF1 (1 << 1)
+# define DIP_IF_FLAGS_IF2 (1 << 2)
+# define DIP_IF_FLAGS_IF3 (1 << 3)
+# define DIP_IF_FLAGS_IF4 (1 << 4)
+# define DIP_IF_FLAGS_IF5 (1 << 5)
+#define REG_CH_STAT_B(x) REG(0x11, 0x14 + (x)) /* read/write */
/* Page 12h: HDCP and OTP */
#define REG_TX3 REG(0x12, 0x9a) /* read/write */
+#define REG_TX4 REG(0x12, 0x9b) /* read/write */
+# define TX4_PD_RAM (1 << 1)
#define REG_TX33 REG(0x12, 0xb8) /* read/write */
# define TX33_HDMI (1 << 1)
@@ -239,11 +300,16 @@ struct tda998x_priv {
/* CEC registers: (not paged)
*/
+#define REG_CEC_INTSTATUS 0xee /* read */
+# define CEC_INTSTATUS_CEC (1 << 0)
+# define CEC_INTSTATUS_HDMI (1 << 1)
#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */
# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
# define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6)
# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1)
# define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0)
+#define REG_CEC_RXSHPDINTENA 0xfc /* read/write */
+#define REG_CEC_RXSHPDINT 0xfd /* read */
#define REG_CEC_RXSHPDLEV 0xfe /* read */
# define CEC_RXSHPDLEV_RXSENS (1 << 0)
# define CEC_RXSHPDLEV_HPD (1 << 1)
@@ -263,21 +329,21 @@ struct tda998x_priv {
#define TDA19988 0x0301
static void
-cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val)
+cec_write(struct tda998x_priv *priv, uint16_t addr, uint8_t val)
{
- struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+ struct i2c_client *client = priv->cec;
uint8_t buf[] = {addr, val};
int ret;
- ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
}
static uint8_t
-cec_read(struct drm_encoder *encoder, uint8_t addr)
+cec_read(struct tda998x_priv *priv, uint8_t addr)
{
- struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+ struct i2c_client *client = priv->cec;
uint8_t val;
int ret;
@@ -296,32 +362,36 @@ fail:
return 0;
}
-static void
-set_page(struct drm_encoder *encoder, uint16_t reg)
+static int
+set_page(struct tda998x_priv *priv, uint16_t reg)
{
- struct tda998x_priv *priv = to_tda998x_priv(encoder);
-
if (REG2PAGE(reg) != priv->current_page) {
- struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct i2c_client *client = priv->hdmi;
uint8_t buf[] = {
REG_CURPAGE, REG2PAGE(reg)
};
int ret = i2c_master_send(client, buf, sizeof(buf));
- if (ret < 0)
- dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret);
+ if (ret < 0) {
+ dev_err(&client->dev, "setpage %04x err %d\n",
+ reg, ret);
+ return ret;
+ }
priv->current_page = REG2PAGE(reg);
}
+ return 0;
}
static int
-reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
+reg_read_range(struct tda998x_priv *priv, uint16_t reg, char *buf, int cnt)
{
- struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct i2c_client *client = priv->hdmi;
uint8_t addr = REG2ADDR(reg);
int ret;
- set_page(encoder, reg);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ return ret;
ret = i2c_master_send(client, &addr, sizeof(addr));
if (ret < 0)
@@ -338,81 +408,359 @@ fail:
return ret;
}
-static uint8_t
-reg_read(struct drm_encoder *encoder, uint16_t reg)
+static void
+reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt)
+{
+ struct i2c_client *client = priv->hdmi;
+ uint8_t buf[cnt+1];
+ int ret;
+
+ buf[0] = REG2ADDR(reg);
+ memcpy(&buf[1], p, cnt);
+
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ return;
+
+ ret = i2c_master_send(client, buf, cnt + 1);
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static int
+reg_read(struct tda998x_priv *priv, uint16_t reg)
{
uint8_t val = 0;
- reg_read_range(encoder, reg, &val, sizeof(val));
+ int ret;
+
+ ret = reg_read_range(priv, reg, &val, sizeof(val));
+ if (ret < 0)
+ return ret;
return val;
}
static void
-reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+reg_write(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
{
- struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct i2c_client *client = priv->hdmi;
uint8_t buf[] = {REG2ADDR(reg), val};
int ret;
- set_page(encoder, reg);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ return;
- ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
}
static void
-reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val)
+reg_write16(struct tda998x_priv *priv, uint16_t reg, uint16_t val)
{
- struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct i2c_client *client = priv->hdmi;
uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
int ret;
- set_page(encoder, reg);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ return;
- ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ ret = i2c_master_send(client, buf, sizeof(buf));
if (ret < 0)
dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
}
static void
-reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+reg_set(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
{
- reg_write(encoder, reg, reg_read(encoder, reg) | val);
+ int old_val;
+
+ old_val = reg_read(priv, reg);
+ if (old_val >= 0)
+ reg_write(priv, reg, old_val | val);
}
static void
-reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+reg_clear(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
{
- reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
+ int old_val;
+
+ old_val = reg_read(priv, reg);
+ if (old_val >= 0)
+ reg_write(priv, reg, old_val & ~val);
}
static void
-tda998x_reset(struct drm_encoder *encoder)
+tda998x_reset(struct tda998x_priv *priv)
{
/* reset audio and i2c master: */
- reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+ reg_write(priv, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
msleep(50);
- reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+ reg_write(priv, REG_SOFTRESET, 0);
msleep(50);
/* reset transmitter: */
- reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
- reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+ reg_set(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+ reg_clear(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
/* PLL registers common configuration */
- reg_write(encoder, REG_PLL_SERIAL_1, 0x00);
- reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
- reg_write(encoder, REG_PLL_SERIAL_3, 0x00);
- reg_write(encoder, REG_SERIALIZER, 0x00);
- reg_write(encoder, REG_BUFFER_OUT, 0x00);
- reg_write(encoder, REG_PLL_SCG1, 0x00);
- reg_write(encoder, REG_AUDIO_DIV, 0x03);
- reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
- reg_write(encoder, REG_PLL_SCGN1, 0xfa);
- reg_write(encoder, REG_PLL_SCGN2, 0x00);
- reg_write(encoder, REG_PLL_SCGR1, 0x5b);
- reg_write(encoder, REG_PLL_SCGR2, 0x00);
- reg_write(encoder, REG_PLL_SCG2, 0x10);
+ reg_write(priv, REG_PLL_SERIAL_1, 0x00);
+ reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
+ reg_write(priv, REG_PLL_SERIAL_3, 0x00);
+ reg_write(priv, REG_SERIALIZER, 0x00);
+ reg_write(priv, REG_BUFFER_OUT, 0x00);
+ reg_write(priv, REG_PLL_SCG1, 0x00);
+ reg_write(priv, REG_AUDIO_DIV, AUDIO_DIV_SERCLK_8);
+ reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+ reg_write(priv, REG_PLL_SCGN1, 0xfa);
+ reg_write(priv, REG_PLL_SCGN2, 0x00);
+ reg_write(priv, REG_PLL_SCGR1, 0x5b);
+ reg_write(priv, REG_PLL_SCGR2, 0x00);
+ reg_write(priv, REG_PLL_SCG2, 0x10);
+
+ /* Write the default value MUX register */
+ reg_write(priv, REG_MUX_VP_VIP_OUT, 0x24);
+}
+
+/*
+ * only 2 interrupts may occur: screen plug/unplug and EDID read
+ */
+static irqreturn_t tda998x_irq_thread(int irq, void *data)
+{
+ struct tda998x_priv *priv = data;
+ u8 sta, cec, lvl, flag0, flag1, flag2;
+
+ if (!priv)
+ return IRQ_HANDLED;
+ sta = cec_read(priv, REG_CEC_INTSTATUS);
+ cec = cec_read(priv, REG_CEC_RXSHPDINT);
+ lvl = cec_read(priv, REG_CEC_RXSHPDLEV);
+ flag0 = reg_read(priv, REG_INT_FLAGS_0);
+ flag1 = reg_read(priv, REG_INT_FLAGS_1);
+ flag2 = reg_read(priv, REG_INT_FLAGS_2);
+ DRM_DEBUG_DRIVER(
+ "tda irq sta %02x cec %02x lvl %02x f0 %02x f1 %02x f2 %02x\n",
+ sta, cec, lvl, flag0, flag1, flag2);
+ if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) {
+ priv->wq_edid_wait = 0;
+ wake_up(&priv->wq_edid);
+ } else if (cec != 0) { /* HPD change */
+ if (priv->encoder && priv->encoder->dev)
+ drm_helper_hpd_irq_event(priv->encoder->dev);
+ }
+ return IRQ_HANDLED;
+}
+
+static uint8_t tda998x_cksum(uint8_t *buf, size_t bytes)
+{
+ uint8_t sum = 0;
+
+ while (bytes--)
+ sum += *buf++;
+ return (255 - sum) + 1;
+}
+
+#define HB(x) (x)
+#define PB(x) (HB(2) + 1 + (x))
+
+static void
+tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr,
+ uint8_t *buf, size_t size)
+{
+ buf[PB(0)] = tda998x_cksum(buf, size);
+
+ reg_clear(priv, REG_DIP_IF_FLAGS, bit);
+ reg_write_range(priv, addr, buf, size);
+ reg_set(priv, REG_DIP_IF_FLAGS, bit);
+}
+
+static void
+tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
+{
+ u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1];
+
+ memset(buf, 0, sizeof(buf));
+ buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO;
+ buf[HB(1)] = 0x01;
+ buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE;
+ buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */
+ buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */
+ buf[PB(4)] = p->audio_frame[4];
+ buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */
+
+ tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf,
+ sizeof(buf));
+}
+
+static void
+tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
+{
+ u8 buf[PB(HDMI_AVI_INFOFRAME_SIZE) + 1];
+
+ memset(buf, 0, sizeof(buf));
+ buf[HB(0)] = HDMI_INFOFRAME_TYPE_AVI;
+ buf[HB(1)] = 0x02;
+ buf[HB(2)] = HDMI_AVI_INFOFRAME_SIZE;
+ buf[PB(1)] = HDMI_SCAN_MODE_UNDERSCAN;
+ buf[PB(2)] = HDMI_ACTIVE_ASPECT_PICTURE;
+ buf[PB(3)] = HDMI_QUANTIZATION_RANGE_FULL << 2;
+ buf[PB(4)] = drm_match_cea_mode(mode);
+
+ tda998x_write_if(priv, DIP_IF_FLAGS_IF2, REG_IF2_HB0, buf,
+ sizeof(buf));
+}
+
+static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
+{
+ if (on) {
+ reg_set(priv, REG_SOFTRESET, SOFTRESET_AUDIO);
+ reg_clear(priv, REG_SOFTRESET, SOFTRESET_AUDIO);
+ reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+ } else {
+ reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+ }
+}
+
+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, aclk;
+ uint32_t n, cts;
+
+ /* Enable audio ports */
+ reg_write(priv, REG_ENA_AP, p->audio_cfg);
+
+ /* Set audio input source */
+ switch (p->audio_format) {
+ case AFMT_SPDIF:
+ reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF);
+ 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;
+ /* 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:
+ BUG();
+ return;
+ }
+
+ reg_write(priv, REG_AIP_CLKSEL, clksel_aip);
+ 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
+ * related to pixclk. Testing showed that modes with pixclk
+ * >100MHz need a larger divider while <40MHz need the default.
+ * There is no detailed info in the datasheet, so we just
+ * assume 100MHz requires larger divider.
+ */
+ adiv = AUDIO_DIV_SERCLK_8;
+ if (mode->clock > 100000)
+ adiv++; /* AUDIO_DIV_SERCLK_16 */
+
+ /* S/PDIF asks for a larger divider */
+ if (p->audio_format == AFMT_SPDIF)
+ adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */
+
+ reg_write(priv, REG_AUDIO_DIV, adiv);
+
+ /*
+ * This is the approximate value of N, which happens to be
+ * the recommended values for non-coherent clocks.
+ */
+ n = 128 * p->audio_sample_rate / 1000;
+
+ /* Write the CTS and N values */
+ 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;
+ reg_write_range(priv, REG_ACR_CTS_0, buf, 6);
+
+ /* Set CTS clock reference */
+ reg_write(priv, REG_AIP_CLKSEL, clksel_aip | clksel_fs);
+
+ /* Reset CTS generator */
+ reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+ reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+
+ /* Write the channel status */
+ buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT;
+ buf[1] = 0x00;
+ buf[2] = IEC958_AES3_CON_FS_NOTID;
+ buf[3] = IEC958_AES4_CON_ORIGFS_NOTID |
+ IEC958_AES4_CON_MAX_WORDLEN_24;
+ reg_write_range(priv, REG_CH_STAT_B(0), buf, 4);
+
+ tda998x_audio_mute(priv, true);
+ msleep(20);
+ tda998x_audio_mute(priv, false);
+
+ /* Write the audio information packet */
+ 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 */
@@ -420,6 +768,23 @@ tda998x_reset(struct drm_encoder *encoder)
static void
tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+ struct tda998x_encoder_params *p = params;
+
+ priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
+ (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
+ VIP_CNTRL_0_SWAP_B(p->swap_b) |
+ (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0);
+ priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) |
+ (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) |
+ VIP_CNTRL_1_SWAP_D(p->swap_d) |
+ (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0);
+ priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) |
+ (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) |
+ VIP_CNTRL_2_SWAP_F(p->swap_f) |
+ (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
+
+ priv->params = *p;
}
static void
@@ -436,25 +801,20 @@ tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
switch (mode) {
case DRM_MODE_DPMS_ON:
- /* enable audio and video ports */
- reg_write(encoder, REG_ENA_AP, 0xff);
- reg_write(encoder, REG_ENA_VP_0, 0xff);
- reg_write(encoder, REG_ENA_VP_1, 0xff);
- reg_write(encoder, REG_ENA_VP_2, 0xff);
+ /* enable video ports, audio will be enabled later */
+ reg_write(priv, REG_ENA_VP_0, 0xff);
+ reg_write(priv, REG_ENA_VP_1, 0xff);
+ reg_write(priv, REG_ENA_VP_2, 0xff);
/* set muxing after enabling ports: */
- reg_write(encoder, REG_VIP_CNTRL_0,
- VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
- reg_write(encoder, REG_VIP_CNTRL_1,
- VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
- reg_write(encoder, REG_VIP_CNTRL_2,
- VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
+ reg_write(priv, REG_VIP_CNTRL_0, priv->vip_cntrl_0);
+ reg_write(priv, REG_VIP_CNTRL_1, priv->vip_cntrl_1);
+ reg_write(priv, REG_VIP_CNTRL_2, priv->vip_cntrl_2);
break;
case DRM_MODE_DPMS_OFF:
- /* disable audio and video ports */
- reg_write(encoder, REG_ENA_AP, 0x00);
- reg_write(encoder, REG_ENA_VP_0, 0x00);
- reg_write(encoder, REG_ENA_VP_1, 0x00);
- reg_write(encoder, REG_ENA_VP_2, 0x00);
+ /* disable video ports */
+ reg_write(priv, REG_ENA_VP_0, 0x00);
+ reg_write(priv, REG_ENA_VP_1, 0x00);
+ reg_write(priv, REG_ENA_VP_2, 0x00);
break;
}
@@ -494,132 +854,199 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *adjusted_mode)
{
struct tda998x_priv *priv = to_tda998x_priv(encoder);
- uint16_t hs_start, hs_end, line_start, line_end;
- uint16_t vwin_start, vwin_end, de_start, de_end;
- uint16_t ref_pix, ref_line, pix_start2;
+ uint16_t ref_pix, ref_line, n_pix, n_line;
+ uint16_t hs_pix_s, hs_pix_e;
+ uint16_t vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e;
+ uint16_t vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e;
+ uint16_t vwin1_line_s, vwin1_line_e;
+ uint16_t vwin2_line_s, vwin2_line_e;
+ uint16_t de_pix_s, de_pix_e;
uint8_t reg, div, rep;
- hs_start = mode->hsync_start - mode->hdisplay;
- hs_end = mode->hsync_end - mode->hdisplay;
- line_start = 1;
- line_end = 1 + mode->vsync_end - mode->vsync_start;
- vwin_start = mode->vtotal - mode->vsync_start;
- vwin_end = vwin_start + mode->vdisplay;
- de_start = mode->htotal - mode->hdisplay;
- de_end = mode->htotal;
-
- pix_start2 = 0;
- if (mode->flags & DRM_MODE_FLAG_INTERLACE)
- pix_start2 = (mode->htotal / 2) + hs_start;
-
- /* TODO how is this value calculated? It is 2 for all common
- * formats in the tables in out of tree nxp driver (assuming
- * I've properly deciphered their byzantine table system)
+ /*
+ * Internally TDA998x is using ITU-R BT.656 style sync but
+ * we get VESA style sync. TDA998x is using a reference pixel
+ * relative to ITU to sync to the input frame and for output
+ * sync generation. Currently, we are using reference detection
+ * from HS/VS, i.e. REFPIX/REFLINE denote frame start sync point
+ * which is position of rising VS with coincident rising HS.
+ *
+ * Now there is some issues to take care of:
+ * - HDMI data islands require sync-before-active
+ * - TDA998x register values must be > 0 to be enabled
+ * - REFLINE needs an additional offset of +1
+ * - REFPIX needs an addtional offset of +1 for UYUV and +3 for RGB
+ *
+ * So we add +1 to all horizontal and vertical register values,
+ * plus an additional +3 for REFPIX as we are using RGB input only.
*/
- ref_line = 2;
-
- /* this might changes for other color formats from the CRTC: */
- ref_pix = 3 + hs_start;
+ n_pix = mode->htotal;
+ n_line = mode->vtotal;
+
+ hs_pix_e = mode->hsync_end - mode->hdisplay;
+ hs_pix_s = mode->hsync_start - mode->hdisplay;
+ de_pix_e = mode->htotal;
+ de_pix_s = mode->htotal - mode->hdisplay;
+ ref_pix = 3 + hs_pix_s;
+
+ /*
+ * Attached LCD controllers may generate broken sync. Allow
+ * those to adjust the position of the rising VS edge by adding
+ * HSKEW to ref_pix.
+ */
+ if (adjusted_mode->flags & DRM_MODE_FLAG_HSKEW)
+ ref_pix += adjusted_mode->hskew;
+
+ if ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) {
+ ref_line = 1 + mode->vsync_start - mode->vdisplay;
+ vwin1_line_s = mode->vtotal - mode->vdisplay - 1;
+ vwin1_line_e = vwin1_line_s + mode->vdisplay;
+ vs1_pix_s = vs1_pix_e = hs_pix_s;
+ vs1_line_s = mode->vsync_start - mode->vdisplay;
+ vs1_line_e = vs1_line_s +
+ mode->vsync_end - mode->vsync_start;
+ vwin2_line_s = vwin2_line_e = 0;
+ vs2_pix_s = vs2_pix_e = 0;
+ vs2_line_s = vs2_line_e = 0;
+ } else {
+ ref_line = 1 + (mode->vsync_start - mode->vdisplay)/2;
+ vwin1_line_s = (mode->vtotal - mode->vdisplay)/2;
+ vwin1_line_e = vwin1_line_s + mode->vdisplay/2;
+ vs1_pix_s = vs1_pix_e = hs_pix_s;
+ vs1_line_s = (mode->vsync_start - mode->vdisplay)/2;
+ vs1_line_e = vs1_line_s +
+ (mode->vsync_end - mode->vsync_start)/2;
+ vwin2_line_s = vwin1_line_s + mode->vtotal/2;
+ vwin2_line_e = vwin2_line_s + mode->vdisplay/2;
+ vs2_pix_s = vs2_pix_e = hs_pix_s + mode->htotal/2;
+ vs2_line_s = vs1_line_s + mode->vtotal/2 ;
+ vs2_line_e = vs2_line_s +
+ (mode->vsync_end - mode->vsync_start)/2;
+ }
div = 148500 / mode->clock;
-
- DBG("clock=%d, div=%u", mode->clock, div);
- DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u",
- hs_start, hs_end, line_start, line_end);
- DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u",
- vwin_start, vwin_end, de_start, de_end);
- DBG("ref_line=%u, ref_pix=%u, pix_start2=%u",
- ref_line, ref_pix, pix_start2);
+ if (div != 0) {
+ div--;
+ if (div > 3)
+ div = 3;
+ }
/* mute the audio FIFO: */
- reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+ reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
/* set HDMI HDCP mode off: */
- reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
- reg_clear(encoder, REG_TX33, TX33_HDMI);
+ reg_write(priv, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+ reg_clear(priv, REG_TX33, TX33_HDMI);
+ reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
- reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
/* no pre-filter or interpolator: */
- reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
+ reg_write(priv, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
HVF_CNTRL_0_INTPOL(0));
- reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
- reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
+ reg_write(priv, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
+ reg_write(priv, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
VIP_CNTRL_4_BLC(0));
- reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR);
- reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
- reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE);
- reg_write(encoder, REG_SERIALIZER, 0);
- reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
+ reg_clear(priv, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
+ reg_clear(priv, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR |
+ PLL_SERIAL_3_SRL_DE);
+ reg_write(priv, REG_SERIALIZER, 0);
+ reg_write(priv, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
/* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
rep = 0;
- reg_write(encoder, REG_RPT_CNTRL, 0);
- reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
+ reg_write(priv, REG_RPT_CNTRL, 0);
+ reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
- reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
+ reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
PLL_SERIAL_2_SRL_PR(rep));
- reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2);
- reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2);
-
/* set color matrix bypass flag: */
- reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP);
+ reg_write(priv, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP |
+ MAT_CONTRL_MAT_SC(1));
/* set BIAS tmds value: */
- reg_write(encoder, REG_ANA_GENERAL, 0x09);
-
- reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD);
+ reg_write(priv, REG_ANA_GENERAL, 0x09);
- reg_write(encoder, REG_VIP_CNTRL_3, 0);
- reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS);
- if (mode->flags & DRM_MODE_FLAG_NVSYNC)
- reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL);
+ /*
+ * Sync on rising HSYNC/VSYNC
+ */
+ reg = VIP_CNTRL_3_SYNC_HS;
+ /*
+ * TDA19988 requires high-active sync at input stage,
+ * so invert low-active sync provided by master encoder here
+ */
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
- reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
-
- reg_write(encoder, REG_VIDFORMAT, 0x00);
- reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
- reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
- reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
- reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
- reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
- reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start);
- reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start);
- reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end);
- reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start);
- reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end);
- reg_write16(encoder, REG_DE_START_MSB, de_start);
- reg_write16(encoder, REG_DE_STOP_MSB, de_end);
+ reg |= VIP_CNTRL_3_H_TGL;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ reg |= VIP_CNTRL_3_V_TGL;
+ reg_write(priv, REG_VIP_CNTRL_3, reg);
+
+ reg_write(priv, REG_VIDFORMAT, 0x00);
+ reg_write16(priv, REG_REFPIX_MSB, ref_pix);
+ reg_write16(priv, REG_REFLINE_MSB, ref_line);
+ reg_write16(priv, REG_NPIX_MSB, n_pix);
+ reg_write16(priv, REG_NLINE_MSB, n_line);
+ reg_write16(priv, REG_VS_LINE_STRT_1_MSB, vs1_line_s);
+ reg_write16(priv, REG_VS_PIX_STRT_1_MSB, vs1_pix_s);
+ reg_write16(priv, REG_VS_LINE_END_1_MSB, vs1_line_e);
+ reg_write16(priv, REG_VS_PIX_END_1_MSB, vs1_pix_e);
+ reg_write16(priv, REG_VS_LINE_STRT_2_MSB, vs2_line_s);
+ reg_write16(priv, REG_VS_PIX_STRT_2_MSB, vs2_pix_s);
+ reg_write16(priv, REG_VS_LINE_END_2_MSB, vs2_line_e);
+ reg_write16(priv, REG_VS_PIX_END_2_MSB, vs2_pix_e);
+ reg_write16(priv, REG_HS_PIX_START_MSB, hs_pix_s);
+ reg_write16(priv, REG_HS_PIX_STOP_MSB, hs_pix_e);
+ reg_write16(priv, REG_VWIN_START_1_MSB, vwin1_line_s);
+ reg_write16(priv, REG_VWIN_END_1_MSB, vwin1_line_e);
+ reg_write16(priv, REG_VWIN_START_2_MSB, vwin2_line_s);
+ reg_write16(priv, REG_VWIN_END_2_MSB, vwin2_line_e);
+ reg_write16(priv, REG_DE_START_MSB, de_pix_s);
+ reg_write16(priv, REG_DE_STOP_MSB, de_pix_e);
if (priv->rev == TDA19988) {
/* let incoming pixels fill the active space (if any) */
- reg_write(encoder, REG_ENABLE_SPACE, 0x01);
+ reg_write(priv, REG_ENABLE_SPACE, 0x00);
}
- reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
- reg_write16(encoder, REG_REFLINE_MSB, ref_line);
-
- reg = TBG_CNTRL_1_VHX_EXT_DE |
- TBG_CNTRL_1_VHX_EXT_HS |
- TBG_CNTRL_1_VHX_EXT_VS |
- TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
- TBG_CNTRL_1_VH_TGL_2;
- if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
- reg |= TBG_CNTRL_1_VH_TGL_0;
- reg_set(encoder, REG_TBG_CNTRL_1, reg);
+ /*
+ * Always generate sync polarity relative to input sync and
+ * revert input stage toggled sync at output stage
+ */
+ reg = TBG_CNTRL_1_DWIN_DIS | TBG_CNTRL_1_TGL_EN;
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ reg |= TBG_CNTRL_1_H_TGL;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ reg |= TBG_CNTRL_1_V_TGL;
+ reg_write(priv, REG_TBG_CNTRL_1, reg);
/* must be last register set: */
- reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
+ reg_write(priv, REG_TBG_CNTRL_0, 0);
+
+ /* Only setup the info frames if the sink is HDMI */
+ if (priv->is_hdmi_sink) {
+ /* We need to turn HDMI HDCP stuff on to get audio through */
+ reg &= ~TBG_CNTRL_1_DWIN_DIS;
+ reg_write(priv, REG_TBG_CNTRL_1, reg);
+ reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1));
+ reg_set(priv, REG_TX33, TX33_HDMI);
+
+ tda998x_write_avi(priv, adjusted_mode);
+
+ if (priv->params.audio_cfg)
+ tda998x_configure_audio(priv, adjusted_mode,
+ &priv->params);
+ }
}
static enum drm_connector_status
tda998x_encoder_detect(struct drm_encoder *encoder,
struct drm_connector *connector)
{
- uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV);
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+ uint8_t val = cec_read(priv, REG_CEC_RXSHPDLEV);
+
return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
connector_status_disconnected;
}
@@ -627,69 +1054,106 @@ tda998x_encoder_detect(struct drm_encoder *encoder,
static int
read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
uint8_t offset, segptr;
int ret, i;
- /* enable EDID read irq: */
- reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
-
offset = (blk & 1) ? 128 : 0;
segptr = blk / 2;
- reg_write(encoder, REG_DDC_ADDR, 0xa0);
- reg_write(encoder, REG_DDC_OFFS, offset);
- reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60);
- reg_write(encoder, REG_DDC_SEGM, segptr);
+ reg_write(priv, REG_DDC_ADDR, 0xa0);
+ reg_write(priv, REG_DDC_OFFS, offset);
+ reg_write(priv, REG_DDC_SEGM_ADDR, 0x60);
+ reg_write(priv, REG_DDC_SEGM, segptr);
/* enable reading EDID: */
- reg_write(encoder, REG_EDID_CTRL, 0x1);
+ priv->wq_edid_wait = 1;
+ reg_write(priv, REG_EDID_CTRL, 0x1);
/* flag must be cleared by sw: */
- reg_write(encoder, REG_EDID_CTRL, 0x0);
+ reg_write(priv, REG_EDID_CTRL, 0x0);
/* wait for block read to complete: */
- for (i = 100; i > 0; i--) {
- uint8_t val = reg_read(encoder, REG_INT_FLAGS_2);
- if (val & INT_FLAGS_2_EDID_BLK_RD)
- break;
- msleep(1);
+ if (priv->hdmi->irq) {
+ i = wait_event_timeout(priv->wq_edid,
+ !priv->wq_edid_wait,
+ msecs_to_jiffies(100));
+ if (i < 0) {
+ dev_err(&priv->hdmi->dev, "read edid wait err %d\n", i);
+ return i;
+ }
+ } else {
+ for (i = 10; i > 0; i--) {
+ msleep(10);
+ ret = reg_read(priv, REG_INT_FLAGS_2);
+ if (ret < 0)
+ return ret;
+ if (ret & INT_FLAGS_2_EDID_BLK_RD)
+ break;
+ }
}
- if (i == 0)
+ if (i == 0) {
+ dev_err(&priv->hdmi->dev, "read edid timeout\n");
return -ETIMEDOUT;
+ }
- ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH);
+ ret = reg_read_range(priv, REG_EDID_DATA_0, buf, EDID_LENGTH);
if (ret != EDID_LENGTH) {
- dev_err(encoder->dev->dev, "failed to read edid block %d: %d",
- blk, ret);
+ dev_err(&priv->hdmi->dev, "failed to read edid block %d: %d\n",
+ blk, ret);
return ret;
}
- reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
-
return 0;
}
+static int
+read_validate_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
+{
+ bool print_bad_edid = drm_debug & DRM_UT_KMS;
+ int ret;
+ int retries = 1;
+
+ do
+ {
+ bool print_bad = print_bad_edid && (retries == 0);
+
+ ret = read_edid_block(encoder, buf, blk);
+ /* Fail on I2C error */
+ if (ret)
+ break;
+
+ /* But retry checksum errored blocks */
+ if (drm_edid_block_valid(buf, blk, print_bad))
+ break;
+ else
+ ret = -EINVAL;
+ } while (retries-- > 0);
+
+ return ret;
+}
+
static uint8_t *
do_get_edid(struct drm_encoder *encoder)
{
- int j = 0, valid_extensions = 0;
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+ int j, valid_extensions = 0;
uint8_t *block, *new;
- bool print_bad_edid = drm_debug & DRM_UT_KMS;
if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
return NULL;
- /* base block fetch */
- if (read_edid_block(encoder, block, 0))
- goto fail;
+ if (priv->rev == TDA19988)
+ reg_clear(priv, REG_TX4, TX4_PD_RAM);
- if (!drm_edid_block_valid(block, 0, print_bad_edid))
+ /* base block fetch */
+ if (read_validate_edid_block(encoder, block, 0))
goto fail;
/* if there's no extensions, we're done */
if (block[0x7e] == 0)
- return block;
+ goto done;
new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
if (!new)
@@ -698,10 +1162,7 @@ do_get_edid(struct drm_encoder *encoder)
for (j = 1; j <= block[0x7e]; j++) {
uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH;
- if (read_edid_block(encoder, ext_block, j))
- goto fail;
-
- if (!drm_edid_block_valid(ext_block, j, print_bad_edid))
+ if (read_validate_edid_block(encoder, ext_block, j))
goto fail;
valid_extensions++;
@@ -716,10 +1177,16 @@ do_get_edid(struct drm_encoder *encoder)
block = new;
}
+done:
+ if (priv->rev == TDA19988)
+ reg_set(priv, REG_TX4, TX4_PD_RAM);
+
return block;
fail:
- dev_warn(encoder->dev->dev, "failed to read EDID\n");
+ if (priv->rev == TDA19988)
+ reg_set(priv, REG_TX4, TX4_PD_RAM);
+ dev_warn(&priv->hdmi->dev, "failed to read EDID\n");
kfree(block);
return NULL;
}
@@ -728,12 +1195,19 @@ static int
tda998x_encoder_get_modes(struct drm_encoder *encoder,
struct drm_connector *connector)
{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
struct edid *edid = (struct edid *)do_get_edid(encoder);
int n = 0;
if (edid) {
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);
}
@@ -744,7 +1218,13 @@ static int
tda998x_encoder_create_resources(struct drm_encoder *encoder,
struct drm_connector *connector)
{
- DBG("");
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+ if (priv->hdmi->irq)
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ else
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
return 0;
}
@@ -763,6 +1243,17 @@ tda998x_encoder_destroy(struct drm_encoder *encoder)
{
struct tda998x_priv *priv = to_tda998x_priv(encoder);
drm_i2c_encoder_destroy(encoder);
+
+ /* disable all IRQs and free the IRQ handler */
+ cec_write(priv, REG_CEC_RXSHPDINTENA, 0);
+ reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+ if (priv->hdmi->irq)
+ free_irq(priv->hdmi->irq, priv);
+
+ tda998x_codec_unregister(&priv->hdmi->dev);
+
+ if (priv->cec)
+ i2c_unregister_device(priv->cec);
kfree(priv);
}
@@ -800,56 +1291,137 @@ tda998x_encoder_init(struct i2c_client *client,
struct drm_device *dev,
struct drm_encoder_slave *encoder_slave)
{
- struct drm_encoder *encoder = &encoder_slave->base;
struct tda998x_priv *priv;
+ struct device_node *np = client->dev.of_node;
+ u32 video;
+ int rev_lo, rev_hi, ret;
+ unsigned short cec_addr;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
- priv->current_page = 0;
- priv->cec = i2c_new_dummy(client->adapter, 0x34);
+ priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3);
+ 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 */
+ cec_addr = 0x34 + (client->addr & 0x03);
+ priv->cec = i2c_new_dummy(client->adapter, cec_addr);
+ if (!priv->cec) {
+ kfree(priv);
+ return -ENODEV;
+ }
+
+ priv->encoder = &encoder_slave->base;
priv->dpms = DRM_MODE_DPMS_OFF;
encoder_slave->slave_priv = priv;
encoder_slave->slave_funcs = &tda998x_encoder_funcs;
+ i2c_set_clientdata(client, priv);
/* wake up the device: */
- cec_write(encoder, REG_CEC_ENAMODS,
+ cec_write(priv, REG_CEC_ENAMODS,
CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI);
- tda998x_reset(encoder);
+ tda998x_reset(priv);
/* read version: */
- priv->rev = reg_read(encoder, REG_VERSION_LSB) |
- reg_read(encoder, REG_VERSION_MSB) << 8;
+ rev_lo = reg_read(priv, REG_VERSION_LSB);
+ rev_hi = reg_read(priv, REG_VERSION_MSB);
+ if (rev_lo < 0 || rev_hi < 0) {
+ ret = rev_lo < 0 ? rev_lo : rev_hi;
+ goto fail;
+ }
+
+ priv->rev = rev_lo | rev_hi << 8;
/* mask off feature bits: */
priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */
switch (priv->rev) {
- case TDA9989N2: dev_info(dev->dev, "found TDA9989 n2"); break;
- case TDA19989: dev_info(dev->dev, "found TDA19989"); break;
- case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break;
- case TDA19988: dev_info(dev->dev, "found TDA19988"); break;
+ case TDA9989N2:
+ dev_info(&client->dev, "found TDA9989 n2");
+ break;
+ case TDA19989:
+ dev_info(&client->dev, "found TDA19989");
+ break;
+ case TDA19989N2:
+ dev_info(&client->dev, "found TDA19989 n2");
+ break;
+ case TDA19988:
+ dev_info(&client->dev, "found TDA19988");
+ break;
default:
- DBG("found unsupported device: %04x", priv->rev);
+ dev_err(&client->dev, "found unsupported device: %04x\n",
+ priv->rev);
goto fail;
}
/* after reset, enable DDC: */
- reg_write(encoder, REG_DDC_DISABLE, 0x00);
+ reg_write(priv, REG_DDC_DISABLE, 0x00);
/* set clock on DDC channel: */
- reg_write(encoder, REG_TX3, 39);
+ reg_write(priv, REG_TX3, 39);
/* if necessary, disable multi-master: */
if (priv->rev == TDA19989)
- reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
+ reg_set(priv, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
- cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL,
+ cec_write(priv, REG_CEC_FRO_IM_CLK_CTRL,
CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
+ /* initialize the optional IRQ */
+ if (client->irq) {
+ int irqf_trigger;
+
+ /* init read EDID waitqueue */
+ init_waitqueue_head(&priv->wq_edid);
+
+ /* clear pending interrupts */
+ reg_read(priv, REG_INT_FLAGS_0);
+ reg_read(priv, REG_INT_FLAGS_1);
+ reg_read(priv, REG_INT_FLAGS_2);
+
+ irqf_trigger =
+ irqd_get_trigger_type(irq_get_irq_data(client->irq));
+ ret = request_threaded_irq(client->irq, NULL,
+ tda998x_irq_thread,
+ irqf_trigger | IRQF_ONESHOT,
+ "tda998x", priv);
+ if (ret) {
+ dev_err(&client->dev,
+ "failed to request IRQ#%u: %d\n",
+ client->irq, ret);
+ goto fail;
+ }
+
+ /* enable HPD irq */
+ cec_write(priv, REG_CEC_RXSHPDINTENA, CEC_RXSHPDLEV_HPD);
+ }
+
+ /* 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 */
+
+ /* get the optional video properties */
+ ret = of_property_read_u32(np, "video-ports", &video);
+ if (ret == 0) {
+ priv->vip_cntrl_0 = video >> 16;
+ priv->vip_cntrl_1 = video >> 8;
+ priv->vip_cntrl_2 = video;
+ }
+
return 0;
fail:
@@ -864,6 +1436,14 @@ fail:
return -ENXIO;
}
+#ifdef CONFIG_OF
+static const struct of_device_id tda998x_dt_ids[] = {
+ { .compatible = "nxp,tda998x", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tda998x_dt_ids);
+#endif
+
static struct i2c_device_id tda998x_ids[] = {
{ "tda998x", 0 },
{ }
@@ -876,6 +1456,7 @@ static struct drm_i2c_encoder_driver tda998x_driver = {
.remove = tda998x_remove,
.driver = {
.name = "tda998x",
+ .of_match_table = of_match_ptr(tda998x_dt_ids),
},
.id_table = tda998x_ids,
},
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.h b/drivers/gpu/drm/i2c/tda998x_drv.h
new file mode 100644
index 000000000000..e6c8dd5dbe21
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_drv.h
@@ -0,0 +1,32 @@
+/* tda998x private data */
+
+struct tda998x_priv {
+ struct i2c_client *cec;
+ struct i2c_client *hdmi;
+ 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/gpu/drm/pl111/Kbuild b/drivers/gpu/drm/pl111/Kbuild
new file mode 100755
index 000000000000..f10d58c70dff
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Kbuild
@@ -0,0 +1,28 @@
+#
+# (C) COPYRIGHT ARM Limited. All rights reserved.
+#
+# This program is free software and is provided to you under the terms of the
+# GNU General Public License version 2 as published by the Free Software
+# Foundation, and any use by you of this program is subject to the terms
+# of such GNU licence.
+#
+# A copy of the licence is included with the program, and can also be obtained
+# from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+#
+
+pl111_drm-y += pl111_drm_device.o \
+ pl111_drm_connector.o \
+ pl111_drm_crtc.o \
+ pl111_drm_cursor.o \
+ pl111_drm_dma_buf.o \
+ pl111_drm_encoder.o \
+ pl111_drm_fb.o \
+ pl111_drm_gem.o \
+ pl111_drm_pl111.o \
+ pl111_drm_platform.o \
+ pl111_drm_suspend.o \
+ pl111_drm_vma.o
+
+obj-$(CONFIG_DRM_PL111) += pl111_drm.o
diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig
new file mode 100755
index 000000000000..60b465c56c51
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Kconfig
@@ -0,0 +1,23 @@
+#
+# (C) COPYRIGHT ARM Limited. All rights reserved.
+#
+# This program is free software and is provided to you under the terms of the
+# GNU General Public License version 2 as published by the Free Software
+# Foundation, and any use by you of this program is subject to the terms
+# of such GNU licence.
+#
+# A copy of the licence is included with the program, and can also be obtained
+# from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+#
+
+config DRM_PL111
+ tristate "DRM Support for PL111 CLCD Controller"
+ depends on DRM
+ select DRM_KMS_HELPER
+ select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+ help
+ Choose this option for DRM support for the PL111 CLCD controller.
+ If M is selected the module will be called pl111_drm.
+
diff --git a/drivers/gpu/drm/pl111/Makefile b/drivers/gpu/drm/pl111/Makefile
new file mode 100755
index 000000000000..2869f587266b
--- /dev/null
+++ b/drivers/gpu/drm/pl111/Makefile
@@ -0,0 +1,32 @@
+#
+# (C) COPYRIGHT 2011-2013 ARM Limited. All rights reserved.
+#
+# This program is free software and is provided to you under the terms of the
+# GNU General Public License version 2 as published by the Free Software
+# Foundation, and any use by you of this program is subject to the terms
+# of such GNU licence.
+#
+# A copy of the licence is included with the program, and can also be obtained
+# from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+#
+#
+
+
+# linux build system bootstrap for out-of-tree module
+
+# default to building for the host
+ARCH ?= $(shell uname -m)
+
+ifeq ($(KDIR),)
+$(error Must specify KDIR to point to the kernel to target))
+endif
+
+all: pl111_drm
+
+pl111_drm:
+ $(MAKE) ARCH=$(ARCH) -C $(KDIR) M=$(CURDIR) EXTRA_CFLAGS="-I$(CURDIR)/../../../include" CONFIG_DMA_SHARED_BUFFER_USES_KDS=y CONFIG_DRM_PL111=m
+
+clean:
+ $(MAKE) ARCH=$(ARCH) -C $(KDIR) M=$(CURDIR) clean
+
diff --git a/drivers/gpu/drm/pl111/pl111_clcd_ext.h b/drivers/gpu/drm/pl111/pl111_clcd_ext.h
new file mode 100755
index 000000000000..43f212efac6b
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_clcd_ext.h
@@ -0,0 +1,95 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+/**
+ * pl111_clcd_ext.h
+ * Extended CLCD register definitions
+ */
+
+#ifndef PL111_CLCD_EXT_H_
+#define PL111_CLCD_EXT_H_
+
+/*
+ * PL111 cursor register definitions not defined in the kernel's clcd header.
+ *
+ * TODO MIDEGL-1718: move to include/linux/amba/clcd.h
+ */
+
+#define CLCD_CRSR_IMAGE 0x00000800
+#define CLCD_CRSR_IMAGE_MAX_WORDS 256
+#define CLCD_CRSR_IMAGE_WORDS_PER_LINE 4
+#define CLCD_CRSR_IMAGE_PIXELS_PER_WORD 16
+
+#define CLCD_CRSR_LBBP_COLOR_MASK 0x00000003
+#define CLCD_CRSR_LBBP_BACKGROUND 0x0
+#define CLCD_CRSR_LBBP_FOREGROUND 0x1
+#define CLCD_CRSR_LBBP_TRANSPARENT 0x2
+#define CLCD_CRSR_LBBP_INVERSE 0x3
+
+
+#define CLCD_CRSR_CTRL 0x00000c00
+#define CLCD_CRSR_CONFIG 0x00000c04
+#define CLCD_CRSR_PALETTE_0 0x00000c08
+#define CLCD_CRSR_PALETTE_1 0x00000c0c
+#define CLCD_CRSR_XY 0x00000c10
+#define CLCD_CRSR_CLIP 0x00000c14
+#define CLCD_CRSR_IMSC 0x00000c20
+#define CLCD_CRSR_ICR 0x00000c24
+#define CLCD_CRSR_RIS 0x00000c28
+#define CLCD_MIS 0x00000c2c
+
+#define CRSR_CTRL_CRSR_ON (1 << 0)
+#define CRSR_CTRL_CRSR_MAX 3
+#define CRSR_CTRL_CRSR_NUM_SHIFT 4
+#define CRSR_CTRL_CRSR_NUM_MASK \
+ (CRSR_CTRL_CRSR_MAX << CRSR_CTRL_CRSR_NUM_SHIFT)
+#define CRSR_CTRL_CURSOR_0 0
+#define CRSR_CTRL_CURSOR_1 1
+#define CRSR_CTRL_CURSOR_2 2
+#define CRSR_CTRL_CURSOR_3 3
+
+#define CRSR_CONFIG_CRSR_SIZE (1 << 0)
+#define CRSR_CONFIG_CRSR_FRAME_SYNC (1 << 1)
+
+#define CRSR_PALETTE_RED_SHIFT 0
+#define CRSR_PALETTE_GREEN_SHIFT 8
+#define CRSR_PALETTE_BLUE_SHIFT 16
+
+#define CRSR_PALETTE_RED_MASK 0x000000ff
+#define CRSR_PALETTE_GREEN_MASK 0x0000ff00
+#define CRSR_PALETTE_BLUE_MASK 0x00ff0000
+#define CRSR_PALETTE_MASK (~0xff000000)
+
+#define CRSR_XY_MASK 0x000003ff
+#define CRSR_XY_X_SHIFT 0
+#define CRSR_XY_Y_SHIFT 16
+
+#define CRSR_XY_X_MASK CRSR_XY_MASK
+#define CRSR_XY_Y_MASK (CRSR_XY_MASK << CRSR_XY_Y_SHIFT)
+
+#define CRSR_CLIP_MASK 0x3f
+#define CRSR_CLIP_X_SHIFT 0
+#define CRSR_CLIP_Y_SHIFT 8
+
+#define CRSR_CLIP_X_MASK CRSR_CLIP_MASK
+#define CRSR_CLIP_Y_MASK (CRSR_CLIP_MASK << CRSR_CLIP_Y_SHIFT)
+
+#define CRSR_IMSC_CRSR_IM (1<<0)
+#define CRSR_ICR_CRSR_IC (1<<0)
+#define CRSR_RIS_CRSR_RIS (1<<0)
+#define CRSR_MIS_CRSR_MIS (1<<0)
+
+#endif /* PL111_CLCD_EXT_H_ */
diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h
new file mode 100755
index 000000000000..e151e8c02b11
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm.h
@@ -0,0 +1,270 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+#ifndef _PL111_DRM_H_
+#define _PL111_DRM_H_
+
+#define DRIVER_AUTHOR "ARM Ltd."
+#define DRIVER_NAME "pl111_drm"
+#define DRIVER_DESC "DRM module for PL111"
+#define DRIVER_LICENCE "GPL"
+#define DRIVER_ALIAS "platform:pl111_drm"
+#define DRIVER_DATE "20101111"
+#define DRIVER_VERSION "0.2"
+#define DRIVER_MAJOR 2
+#define DRIVER_MINOR 1
+#define DRIVER_PATCHLEVEL 1
+
+/*
+ * Number of flips allowed in flight at any one time. Any more flips requested
+ * beyond this value will cause the caller to block until earlier flips have
+ * completed.
+ *
+ * For performance reasons, this must be greater than the number of buffers
+ * used in the rendering pipeline. Note that the rendering pipeline can contain
+ * different types of buffer, e.g.:
+ * - 2 final framebuffers
+ * - >2 geometry buffers for GPU use-cases
+ * - >2 vertex buffers for GPU use-cases
+ *
+ * For example, a system using 5 geometry buffers could have 5 flips in flight,
+ * and so NR_FLIPS_IN_FLIGHT_THRESHOLD must be 5 or greater.
+ *
+ * Whilst there may be more intermediate buffers (such as vertex/geometry) than
+ * final framebuffers, KDS is used to ensure that GPU rendering waits for the
+ * next off-screen buffer, so it doesn't overwrite an on-screen buffer and
+ * produce tearing.
+ */
+
+/*
+ * Here, we choose a conservative value. A lower value is most likely
+ * suitable for GPU use-cases.
+ */
+#define NR_FLIPS_IN_FLIGHT_THRESHOLD 16
+
+#define CLCD_IRQ_NEXTBASE_UPDATE (1u<<2)
+
+struct pl111_drm_flip_resource;
+
+struct pl111_gem_bo_dma {
+ dma_addr_t fb_dev_addr;
+ void *fb_cpu_addr;
+};
+
+struct pl111_gem_bo_shm {
+ struct page **pages;
+ dma_addr_t *dma_addrs;
+};
+
+struct pl111_gem_bo {
+ struct drm_gem_object gem_object;
+ u32 type;
+ union {
+ struct pl111_gem_bo_dma dma;
+ struct pl111_gem_bo_shm shm;
+ } backing_data;
+ struct sg_table *sgt;
+};
+
+extern struct pl111_drm_dev_private priv;
+
+struct pl111_drm_framebuffer {
+ struct drm_framebuffer fb;
+ struct pl111_gem_bo *bo;
+};
+
+struct pl111_drm_flip_resource {
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ /* This is the kds set associated to the dma_buf we want to flip */
+ struct kds_resource_set *kds_res_set;
+#endif
+ struct drm_framebuffer *fb;
+ struct drm_crtc *crtc;
+ struct list_head link;
+ bool page_flip;
+ struct drm_pending_vblank_event *event;
+};
+
+struct pl111_drm_crtc {
+ struct drm_crtc crtc;
+ int crtc_index;
+
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ /* This protects "old_kds_res_set" and "displaying_fb" */
+ spinlock_t current_displaying_lock;
+ /*
+ * When a buffer is displayed its associated kds resource
+ * will be obtained and stored here. Every time a buffer
+ * flip is completed this old kds set is released and assigned
+ * the kds set of the new buffer.
+ */
+ struct kds_resource_set *old_kds_res_set;
+ /*
+ * Stores which frame buffer is currently being displayed by
+ * this CRTC or NULL if nothing is being displayed. It is used
+ * to tell whether we need to obtain a set of kds resources for
+ * exported buffer objects.
+ */
+ struct drm_framebuffer *displaying_fb;
+#endif
+ struct drm_display_mode *new_mode;
+ struct drm_display_mode *current_mode;
+ int last_bpp;
+
+ /*
+ * This spinlock protects "update_queue", "current_update_res"
+ * and calls to do_flip_to_res() which updates the CLCD base
+ * registers.
+ */
+ spinlock_t base_update_lock;
+ /*
+ * The resource that caused a base address update. Only one can be
+ * pending, hence it's != NULL if there's a pending update
+ */
+ struct pl111_drm_flip_resource *current_update_res;
+ /* Queue of things waiting to update the base address */
+ struct list_head update_queue;
+
+ void (*show_framebuffer_cb)(struct pl111_drm_flip_resource *flip_res,
+ struct drm_framebuffer *fb);
+};
+
+struct pl111_drm_connector {
+ struct drm_connector connector;
+};
+
+struct pl111_drm_encoder {
+ struct drm_encoder encoder;
+};
+
+struct pl111_drm_dev_private {
+ struct pl111_drm_crtc *pl111_crtc;
+
+ struct amba_device *amba_dev;
+ unsigned long mmio_start;
+ __u32 mmio_len;
+ void *regs;
+ struct clk *clk;
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ struct kds_callback kds_cb;
+ struct kds_callback kds_obtain_current_cb;
+#endif
+ /*
+ * Number of flips that were started in show_framebuffer_on_crtc(),
+ * but haven't completed yet - because we do deferred flipping
+ */
+ atomic_t nr_flips_in_flight;
+ wait_queue_head_t wait_for_flips;
+
+ /*
+ * Used to prevent race between pl111_dma_buf_release and
+ * drm_gem_prime_handle_to_fd
+ */
+ struct mutex export_dma_buf_lock;
+
+ uint32_t number_crtcs;
+
+ /* Cache for flip resources used to avoid kmalloc on each page flip */
+ struct kmem_cache *page_flip_slab;
+};
+
+enum pl111_cursor_size {
+ CURSOR_32X32,
+ CURSOR_64X64
+};
+
+enum pl111_cursor_sync {
+ CURSOR_SYNC_NONE,
+ CURSOR_SYNC_VSYNC
+};
+
+
+/**
+ * Buffer allocation function which is more flexible than dumb_create(),
+ * it allows passing driver specific flags to control the kind of buffer
+ * to be allocated.
+ */
+int pl111_drm_gem_create_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv);
+
+/****** TODO MIDEGL-1718: this should be moved to uapi/include/drm/pl111_drm.h ********/
+
+/*
+ * Parameters for different buffer objects:
+ * bit [0]: backing storage
+ * (0 -> SHM)
+ * (1 -> DMA)
+ * bit [2:1]: kind of mapping
+ * (0x0 -> uncached)
+ * (0x1 -> write combine)
+ * (0x2 -> cached)
+ */
+#define PL111_BOT_MASK (0x7)
+#define PL111_BOT_SHM (0x0 << 0)
+#define PL111_BOT_DMA (0x1 << 0)
+#define PL111_BOT_UNCACHED (0x0 << 1)
+#define PL111_BOT_WC (0x1 << 1)
+#define PL111_BOT_CACHED (0x2 << 1)
+
+/**
+ * User-desired buffer creation information structure.
+ *
+ * @size: user-desired memory allocation size.
+ * - this size value would be page-aligned internally.
+ * @flags: user request for setting memory type or cache attributes as a bit op
+ * - PL111_BOT_DMA / PL111_BOT_SHM
+ * - PL111_BOT_UNCACHED / PL111_BOT_WC / PL111_BOT_CACHED
+ * @handle: returned a handle to created gem object.
+ * - this handle will be set by gem module of kernel side.
+ */
+struct drm_pl111_gem_create {
+ uint32_t height;
+ uint32_t width;
+ uint32_t bpp;
+ uint32_t flags;
+ /* handle, pitch, size will be returned */
+ uint32_t handle;
+ uint32_t pitch;
+ uint64_t size;
+};
+
+#define DRM_PL111_GEM_CREATE 0x00
+
+#define DRM_IOCTL_PL111_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + \
+ DRM_PL111_GEM_CREATE, struct drm_pl111_gem_create)
+/****************************************************************************/
+
+#define PL111_FB_FROM_FRAMEBUFFER(drm_fb) \
+ (container_of(drm_fb, struct pl111_drm_framebuffer, fb))
+
+#define PL111_BO_FROM_FRAMEBUFFER(drm_fb) \
+ (container_of(drm_fb, struct pl111_drm_framebuffer, fb)->bo)
+
+#define PL111_BO_FROM_GEM(gem_obj) \
+ container_of(gem_obj, struct pl111_gem_bo, gem_object)
+
+#define to_pl111_crtc(x) container_of(x, struct pl111_drm_crtc, crtc)
+
+#define PL111_ENCODER_FROM_ENCODER(x) \
+ container_of(x, struct pl111_drm_encoder, encoder)
+
+#define PL111_CONNECTOR_FROM_CONNECTOR(x) \
+ container_of(x, struct pl111_drm_connector, connector)
+
+#include "pl111_drm_funcs.h"
+
+#endif /* _PL111_DRM_H_ */
diff --git a/drivers/gpu/drm/pl111/pl111_drm_connector.c b/drivers/gpu/drm/pl111/pl111_drm_connector.c
new file mode 100755
index 000000000000..5d9cf80de618
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_connector.c
@@ -0,0 +1,170 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_connector.c
+ * Implementation of the connector functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+
+static struct {
+ int w, h, type;
+} pl111_drm_modes[] = {
+ { 640, 480, DRM_MODE_TYPE_PREFERRED},
+ { 800, 600, 0},
+ {1024, 768, 0},
+ { -1, -1, -1}
+};
+
+void pl111_connector_destroy(struct drm_connector *connector)
+{
+ struct pl111_drm_connector *pl111_connector =
+ PL111_CONNECTOR_FROM_CONNECTOR(connector);
+
+ DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector);
+
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+ kfree(pl111_connector);
+}
+
+enum drm_connector_status pl111_connector_detect(struct drm_connector
+ *connector, bool force)
+{
+ DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector);
+ return connector_status_connected;
+}
+
+void pl111_connector_dpms(struct drm_connector *connector, int mode)
+{
+ DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector);
+}
+
+struct drm_encoder *
+pl111_connector_helper_best_encoder(struct drm_connector *connector)
+{
+ DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector);
+
+ if (connector->encoder != NULL) {
+ return connector->encoder; /* Return attached encoder */
+ } else {
+ /*
+ * If there is no attached encoder we choose the best candidate
+ * from the list.
+ * For PL111 there is only one encoder so we return the first
+ * one we find.
+ * Other h/w would require a suitable criterion below.
+ */
+ struct drm_encoder *encoder = NULL;
+ struct drm_device *dev = connector->dev;
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list,
+ head) {
+ if (1) { /* criterion ? */
+ break;
+ }
+ }
+ return encoder; /* return best candidate encoder */
+ }
+}
+
+int pl111_connector_helper_get_modes(struct drm_connector *connector)
+{
+ int i = 0;
+ int count = 0;
+
+ DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector);
+
+ while (pl111_drm_modes[i].w != -1) {
+ struct drm_display_mode *mode =
+ drm_mode_find_dmt(connector->dev,
+ pl111_drm_modes[i].w,
+ pl111_drm_modes[i].h,
+ 60
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0))
+ , false
+#endif
+ );
+
+ if (mode != NULL) {
+ mode->type |= pl111_drm_modes[i].type;
+ drm_mode_probed_add(connector, mode);
+ count++;
+ }
+
+ i++;
+ }
+
+ DRM_DEBUG_KMS("found %d modes\n", count);
+
+ return count;
+}
+
+int pl111_connector_helper_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ DRM_DEBUG_KMS("DRM %s on connector=%p\n", __func__, connector);
+ return MODE_OK;
+}
+
+const struct drm_connector_funcs connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = pl111_connector_destroy,
+ .detect = pl111_connector_detect,
+ .dpms = pl111_connector_dpms,
+};
+
+const struct drm_connector_helper_funcs connector_helper_funcs = {
+ .get_modes = pl111_connector_helper_get_modes,
+ .mode_valid = pl111_connector_helper_mode_valid,
+ .best_encoder = pl111_connector_helper_best_encoder,
+};
+
+struct pl111_drm_connector *pl111_connector_create(struct drm_device *dev)
+{
+ struct pl111_drm_connector *pl111_connector;
+
+ pl111_connector = kzalloc(sizeof(struct pl111_drm_connector),
+ GFP_KERNEL);
+
+ if (pl111_connector == NULL) {
+ pr_err("Failed to allocated pl111_drm_connector\n");
+ return NULL;
+ }
+
+ drm_connector_init(dev, &pl111_connector->connector, &connector_funcs,
+ DRM_MODE_CONNECTOR_DVII);
+
+ drm_connector_helper_add(&pl111_connector->connector,
+ &connector_helper_funcs);
+
+ drm_sysfs_connector_add(&pl111_connector->connector);
+
+ return pl111_connector;
+}
+
diff --git a/drivers/gpu/drm/pl111/pl111_drm_crtc.c b/drivers/gpu/drm/pl111/pl111_drm_crtc.c
new file mode 100755
index 000000000000..a06c991dbf23
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_crtc.c
@@ -0,0 +1,421 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_crtc.c
+ * Implementation of the CRTC functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+static int pl111_crtc_num;
+
+void pl111_common_irq(struct pl111_drm_crtc *pl111_crtc)
+{
+ struct drm_device *dev = pl111_crtc->crtc.dev;
+ struct pl111_drm_flip_resource *old_flip_res;
+ struct pl111_gem_bo *bo;
+ unsigned long irq_flags;
+ int flips_in_flight;
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ unsigned long flags;
+#endif
+
+ spin_lock_irqsave(&pl111_crtc->base_update_lock, irq_flags);
+
+ /*
+ * Cache the flip resource that caused the IRQ since it will be
+ * dispatched later. Early return if the IRQ isn't associated to
+ * a base register update.
+ *
+ * TODO MIDBASE-2790: disable IRQs when a flip is not pending.
+ */
+ old_flip_res = pl111_crtc->current_update_res;
+ if (!old_flip_res) {
+ spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags);
+ return;
+ }
+ pl111_crtc->current_update_res = NULL;
+
+ /* Prepare the next flip (if any) of the queue as soon as possible. */
+ if (!list_empty(&pl111_crtc->update_queue)) {
+ struct pl111_drm_flip_resource *flip_res;
+ /* Remove the head of the list */
+ flip_res = list_first_entry(&pl111_crtc->update_queue,
+ struct pl111_drm_flip_resource, link);
+ list_del(&flip_res->link);
+ do_flip_to_res(flip_res);
+ /*
+ * current_update_res will be set, so guarentees that
+ * another flip_res coming in gets queued instead of
+ * handled immediately
+ */
+ }
+ spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags);
+
+ /* Finalize properly the flip that caused the IRQ */
+ DRM_DEBUG_KMS("DRM Finalizing old_flip_res=%p\n", old_flip_res);
+
+ bo = PL111_BO_FROM_FRAMEBUFFER(old_flip_res->fb);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags);
+ release_kds_resource_and_display(old_flip_res);
+ spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags);
+#endif
+ /* Release DMA buffer on this flip */
+ if (bo->gem_object.export_dma_buf != NULL)
+ dma_buf_put(bo->gem_object.export_dma_buf);
+
+ drm_handle_vblank(dev, pl111_crtc->crtc_index);
+
+ /* Wake up any processes waiting for page flip event */
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0))
+ if (old_flip_res->event) {
+ spin_lock_bh(&dev->event_lock);
+ drm_send_vblank_event(dev, pl111_crtc->crtc_index,
+ old_flip_res->event);
+ spin_unlock_bh(&dev->event_lock);
+ }
+#else
+ if (old_flip_res->event) {
+ struct drm_pending_vblank_event *e = old_flip_res->event;
+ struct timeval now;
+ unsigned int seq;
+
+ DRM_DEBUG_KMS("%s: wake up page flip event (%p)\n", __func__,
+ old_flip_res->event);
+
+ spin_lock_bh(&dev->event_lock);
+ seq = drm_vblank_count_and_time(dev, pl111_crtc->crtc_index,
+ &now);
+ e->pipe = pl111_crtc->crtc_index;
+ e->event.sequence = seq;
+ e->event.tv_sec = now.tv_sec;
+ e->event.tv_usec = now.tv_usec;
+
+ list_add_tail(&e->base.link,
+ &e->base.file_priv->event_list);
+
+ wake_up_interruptible(&e->base.file_priv->event_wait);
+ spin_unlock_bh(&dev->event_lock);
+ }
+#endif
+
+ drm_vblank_put(dev, pl111_crtc->crtc_index);
+
+ /*
+ * workqueue.c:process_one_work():
+ * "It is permissible to free the struct work_struct from
+ * inside the function that is called from it"
+ */
+ kmem_cache_free(priv.page_flip_slab, old_flip_res);
+
+ flips_in_flight = atomic_dec_return(&priv.nr_flips_in_flight);
+ if (flips_in_flight == 0 ||
+ flips_in_flight == (NR_FLIPS_IN_FLIGHT_THRESHOLD - 1))
+ wake_up(&priv.wait_for_flips);
+
+ DRM_DEBUG_KMS("DRM release flip_res=%p\n", old_flip_res);
+}
+
+void show_framebuffer_on_crtc_cb(void *cb1, void *cb2)
+{
+ struct pl111_drm_flip_resource *flip_res = cb1;
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc);
+
+ pl111_crtc->show_framebuffer_cb(cb1, cb2);
+}
+
+int show_framebuffer_on_crtc(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, bool page_flip,
+ struct drm_pending_vblank_event *event)
+{
+ struct pl111_gem_bo *bo;
+ struct pl111_drm_flip_resource *flip_res;
+ int flips_in_flight;
+ int old_flips_in_flight;
+
+ crtc->fb = fb;
+
+ bo = PL111_BO_FROM_FRAMEBUFFER(fb);
+ if (bo == NULL) {
+ DRM_DEBUG_KMS("Failed to get pl111_gem_bo object\n");
+ return -EINVAL;
+ }
+
+ /* If this is a full modeset, wait for all outstanding flips to complete
+ * before continuing. This avoids unnecessary complication from being
+ * able to queue up multiple modesets and queues of mixed modesets and
+ * page flips.
+ *
+ * Modesets should be uncommon and will not be performant anyway, so
+ * making them synchronous should have negligible performance impact.
+ */
+ if (!page_flip) {
+ int ret = wait_event_killable(priv.wait_for_flips,
+ atomic_read(&priv.nr_flips_in_flight) == 0);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * There can be more 'early display' flips in flight than there are
+ * buffers, and there is (currently) no explicit bound on the number of
+ * flips. Hence, we need a new allocation for each one.
+ *
+ * Note: this could be optimized down if we knew a bound on the flips,
+ * since an application can only have so many buffers in flight to be
+ * useful/not hog all the memory
+ */
+ flip_res = kmem_cache_alloc(priv.page_flip_slab, GFP_KERNEL);
+ if (flip_res == NULL) {
+ pr_err("kmem_cache_alloc failed to alloc - flip ignored\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * increment flips in flight, whilst blocking when we reach
+ * NR_FLIPS_IN_FLIGHT_THRESHOLD
+ */
+ do {
+ /*
+ * Note: use of assign-and-then-compare in the condition to set
+ * flips_in_flight
+ */
+ int ret = wait_event_killable(priv.wait_for_flips,
+ (flips_in_flight =
+ atomic_read(&priv.nr_flips_in_flight))
+ < NR_FLIPS_IN_FLIGHT_THRESHOLD);
+ if (ret != 0) {
+ kmem_cache_free(priv.page_flip_slab, flip_res);
+ return ret;
+ }
+
+ old_flips_in_flight = atomic_cmpxchg(&priv.nr_flips_in_flight,
+ flips_in_flight, flips_in_flight + 1);
+ } while (old_flips_in_flight != flips_in_flight);
+
+ flip_res->fb = fb;
+ flip_res->crtc = crtc;
+ flip_res->page_flip = page_flip;
+ flip_res->event = event;
+ INIT_LIST_HEAD(&flip_res->link);
+ DRM_DEBUG_KMS("DRM alloc flip_res=%p\n", flip_res);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ if (bo->gem_object.export_dma_buf != NULL) {
+ struct dma_buf *buf = bo->gem_object.export_dma_buf;
+ unsigned long shared[1] = { 0 };
+ struct kds_resource *resource_list[1] = {
+ get_dma_buf_kds_resource(buf) };
+ int err;
+
+ get_dma_buf(buf);
+ DRM_DEBUG_KMS("Got dma_buf %p\n", buf);
+
+ /* Wait for the KDS resource associated with this buffer */
+ err = kds_async_waitall(&flip_res->kds_res_set,
+ &priv.kds_cb, flip_res, fb, 1, shared,
+ resource_list);
+ BUG_ON(err);
+ } else {
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+
+ DRM_DEBUG_KMS("No dma_buf for this flip\n");
+
+ /* No dma-buf attached so just call the callback directly */
+ flip_res->kds_res_set = NULL;
+ pl111_crtc->show_framebuffer_cb(flip_res, fb);
+ }
+#else
+ if (bo->gem_object.export_dma_buf != NULL) {
+ struct dma_buf *buf = bo->gem_object.export_dma_buf;
+
+ get_dma_buf(buf);
+ DRM_DEBUG_KMS("Got dma_buf %p\n", buf);
+ } else {
+ DRM_DEBUG_KMS("No dma_buf for this flip\n");
+ }
+
+ /* No dma-buf attached to this so just call the callback directly */
+ {
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+ pl111_crtc->show_framebuffer_cb(flip_res, fb);
+ }
+#endif
+
+ /* For the same reasons as the wait at the start of this function,
+ * wait for the modeset to complete before continuing.
+ */
+ if (!page_flip) {
+ int ret = wait_event_killable(priv.wait_for_flips,
+ flips_in_flight == 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int pl111_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event)
+{
+ DRM_DEBUG_KMS("%s: crtc=%p, fb=%p, event=%p\n",
+ __func__, crtc, fb, event);
+ return show_framebuffer_on_crtc(crtc, fb, true, event);
+}
+
+int pl111_crtc_helper_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y, struct drm_framebuffer *old_fb)
+{
+ int ret;
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+ struct drm_display_mode *duplicated_mode;
+
+ DRM_DEBUG_KMS("DRM crtc_helper_mode_set, x=%d y=%d bpp=%d\n",
+ adjusted_mode->hdisplay, adjusted_mode->vdisplay,
+ crtc->fb->bits_per_pixel);
+
+ duplicated_mode = drm_mode_duplicate(crtc->dev, adjusted_mode);
+ if (!duplicated_mode)
+ return -ENOMEM;
+
+ pl111_crtc->new_mode = duplicated_mode;
+ ret = show_framebuffer_on_crtc(crtc, crtc->fb, false, NULL);
+ if (ret != 0) {
+ pl111_crtc->new_mode = pl111_crtc->current_mode;
+ drm_mode_destroy(crtc->dev, duplicated_mode);
+ }
+
+ return ret;
+}
+
+void pl111_crtc_helper_prepare(struct drm_crtc *crtc)
+{
+ DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc);
+}
+
+void pl111_crtc_helper_commit(struct drm_crtc *crtc)
+{
+ DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc);
+}
+
+bool pl111_crtc_helper_mode_fixup(struct drm_crtc *crtc,
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0))
+ const struct drm_display_mode *mode,
+#else
+ struct drm_display_mode *mode,
+#endif
+ struct drm_display_mode *adjusted_mode)
+{
+ DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc);
+
+#ifdef CONFIG_ARCH_VEXPRESS
+ /*
+ * 1024x768 with more than 16 bits per pixel may not work
+ * correctly on Versatile Express due to bandwidth issues
+ */
+ if (mode->hdisplay == 1024 && mode->vdisplay == 768 &&
+ crtc->fb->bits_per_pixel > 16) {
+ DRM_INFO("*WARNING* 1024x768 at > 16 bpp may suffer corruption\n");
+ }
+#endif
+
+ return true;
+}
+
+void pl111_crtc_helper_disable(struct drm_crtc *crtc)
+{
+ int ret;
+
+ DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc);
+
+ /* don't disable crtc until no flips in flight as irq will be disabled */
+ ret = wait_event_killable(priv.wait_for_flips, atomic_read(&priv.nr_flips_in_flight) == 0);
+ if(ret) {
+ pr_err("pl111_crtc_helper_disable failed\n");
+ return;
+ }
+ clcd_disable(crtc);
+}
+
+void pl111_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+
+ DRM_DEBUG_KMS("DRM %s on crtc=%p\n", __func__, crtc);
+
+ drm_crtc_cleanup(crtc);
+ kfree(pl111_crtc);
+}
+
+const struct drm_crtc_funcs crtc_funcs = {
+ .cursor_set = pl111_crtc_cursor_set,
+ .cursor_move = pl111_crtc_cursor_move,
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = pl111_crtc_page_flip,
+ .destroy = pl111_crtc_destroy
+};
+
+const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+ .mode_set = pl111_crtc_helper_mode_set,
+ .prepare = pl111_crtc_helper_prepare,
+ .commit = pl111_crtc_helper_commit,
+ .mode_fixup = pl111_crtc_helper_mode_fixup,
+ .disable = pl111_crtc_helper_disable,
+};
+
+struct pl111_drm_crtc *pl111_crtc_create(struct drm_device *dev)
+{
+ struct pl111_drm_crtc *pl111_crtc;
+
+ pl111_crtc = kzalloc(sizeof(struct pl111_drm_crtc), GFP_KERNEL);
+ if (pl111_crtc == NULL) {
+ pr_err("Failed to allocated pl111_drm_crtc\n");
+ return NULL;
+ }
+
+ drm_crtc_init(dev, &pl111_crtc->crtc, &crtc_funcs);
+ drm_crtc_helper_add(&pl111_crtc->crtc, &crtc_helper_funcs);
+
+ pl111_crtc->crtc_index = pl111_crtc_num;
+ pl111_crtc_num++;
+ pl111_crtc->crtc.enabled = 0;
+ pl111_crtc->last_bpp = 0;
+ pl111_crtc->current_update_res = NULL;
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ pl111_crtc->displaying_fb = NULL;
+ pl111_crtc->old_kds_res_set = NULL;
+ spin_lock_init(&pl111_crtc->current_displaying_lock);
+#endif
+ pl111_crtc->show_framebuffer_cb = show_framebuffer_on_crtc_cb_internal;
+ INIT_LIST_HEAD(&pl111_crtc->update_queue);
+ spin_lock_init(&pl111_crtc->base_update_lock);
+
+ return pl111_crtc;
+}
+
diff --git a/drivers/gpu/drm/pl111/pl111_drm_cursor.c b/drivers/gpu/drm/pl111/pl111_drm_cursor.c
new file mode 100755
index 000000000000..87f1007d16dd
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_cursor.c
@@ -0,0 +1,331 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_cursor.c
+ * Implementation of cursor functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "pl111_clcd_ext.h"
+#include "pl111_drm.h"
+
+#define PL111_MAX_CURSOR_WIDTH (64)
+#define PL111_MAX_CURSOR_HEIGHT (64)
+
+#define ARGB_2_LBBP_BINARY_THRESHOLD (1 << 7)
+#define ARGB_ALPHA_SHIFT 24
+#define ARGB_ALPHA_MASK (0xff << ARGB_ALPHA_SHIFT)
+#define ARGB_RED_SHIFT 16
+#define ARGB_RED_MASK (0xff << ARGB_RED_SHIFT)
+#define ARGB_GREEN_SHIFT 8
+#define ARGB_GREEN_MASK (0xff << ARGB_GREEN_SHIFT)
+#define ARGB_BLUE_SHIFT 0
+#define ARGB_BLUE_MASK (0xff << ARGB_BLUE_SHIFT)
+
+
+void pl111_set_cursor_size(enum pl111_cursor_size size)
+{
+ u32 reg_data = readl(priv.regs + CLCD_CRSR_CONFIG);
+
+ if (size == CURSOR_64X64)
+ reg_data |= CRSR_CONFIG_CRSR_SIZE;
+ else
+ reg_data &= ~CRSR_CONFIG_CRSR_SIZE;
+
+ writel(reg_data, priv.regs + CLCD_CRSR_CONFIG);
+}
+
+void pl111_set_cursor_sync(enum pl111_cursor_sync sync)
+{
+ u32 reg_data = readl(priv.regs + CLCD_CRSR_CONFIG);
+
+ if (sync == CURSOR_SYNC_VSYNC)
+ reg_data |= CRSR_CONFIG_CRSR_FRAME_SYNC;
+ else
+ reg_data &= ~CRSR_CONFIG_CRSR_FRAME_SYNC;
+
+ writel(reg_data, priv.regs + CLCD_CRSR_CONFIG);
+}
+
+void pl111_set_cursor(u32 cursor)
+{
+ u32 reg_data = readl(priv.regs + CLCD_CRSR_CTRL);
+
+ reg_data &= ~(CRSR_CTRL_CRSR_MAX << CRSR_CTRL_CRSR_NUM_SHIFT);
+ reg_data |= (cursor & CRSR_CTRL_CRSR_MAX) << CRSR_CTRL_CRSR_NUM_SHIFT;
+
+ writel(reg_data, priv.regs + CLCD_CRSR_CTRL);
+}
+
+void pl111_set_cursor_enable(bool enable)
+{
+ u32 reg_data = readl(priv.regs + CLCD_CRSR_CTRL);
+
+ if (enable)
+ reg_data |= CRSR_CTRL_CRSR_ON;
+ else
+ reg_data &= ~CRSR_CTRL_CRSR_ON;
+
+ writel(reg_data, priv.regs + CLCD_CRSR_CTRL);
+}
+
+void pl111_set_cursor_position(u32 x, u32 y)
+{
+ u32 reg_data = (x & CRSR_XY_MASK) |
+ ((y & CRSR_XY_MASK) << CRSR_XY_Y_SHIFT);
+
+ writel(reg_data, priv.regs + CLCD_CRSR_XY);
+}
+
+void pl111_set_cursor_clipping(u32 x, u32 y)
+{
+ u32 reg_data;
+
+ /*
+ * Do not allow setting clipping values larger than
+ * the cursor size since the cursor is already fully hidden
+ * when x,y = PL111_MAX_CURSOR_WIDTH.
+ */
+ if (x > PL111_MAX_CURSOR_WIDTH)
+ x = PL111_MAX_CURSOR_WIDTH;
+ if (y > PL111_MAX_CURSOR_WIDTH)
+ y = PL111_MAX_CURSOR_WIDTH;
+
+ reg_data = (x & CRSR_CLIP_MASK) |
+ ((y & CRSR_CLIP_MASK) << CRSR_CLIP_Y_SHIFT);
+
+ writel(reg_data, priv.regs + CLCD_CRSR_CLIP);
+}
+
+void pl111_set_cursor_palette(u32 color0, u32 color1)
+{
+ writel(color0 & CRSR_PALETTE_MASK, priv.regs + CLCD_CRSR_PALETTE_0);
+ writel(color1 & CRSR_PALETTE_MASK, priv.regs + CLCD_CRSR_PALETTE_1);
+}
+
+void pl111_cursor_enable(void)
+{
+ pl111_set_cursor_sync(CURSOR_SYNC_VSYNC);
+ pl111_set_cursor_size(CURSOR_64X64);
+ pl111_set_cursor_palette(0x0, 0x00ffffff);
+ pl111_set_cursor_enable(true);
+}
+
+void pl111_cursor_disable(void)
+{
+ pl111_set_cursor_enable(false);
+}
+
+/* shift required to locate pixel into the correct position in
+ * a cursor LBBP word, indexed by x mod 16.
+ */
+static const unsigned char
+x_mod_16_to_value_shift[CLCD_CRSR_IMAGE_PIXELS_PER_WORD] = {
+ 6, 4, 2, 0, 14, 12, 10, 8, 22, 20, 18, 16, 30, 28, 26, 24
+};
+
+/* Pack the pixel value into its correct position in the buffer as specified
+ * for LBBP */
+static inline void
+set_lbbp_pixel(uint32_t *buffer, unsigned int x, unsigned int y,
+ uint32_t value)
+{
+ u32 *cursor_ram = priv.regs + CLCD_CRSR_IMAGE;
+ uint32_t shift;
+ uint32_t data;
+
+ shift = x_mod_16_to_value_shift[x % CLCD_CRSR_IMAGE_PIXELS_PER_WORD];
+
+ /* Get the word containing this pixel */
+ cursor_ram = cursor_ram + (x >> CLCD_CRSR_IMAGE_WORDS_PER_LINE) + (y << 2);
+
+ /* Update pixel in cursor RAM */
+ data = readl(cursor_ram);
+ data &= ~(CLCD_CRSR_LBBP_COLOR_MASK << shift);
+ data |= value << shift;
+ writel(data, cursor_ram);
+}
+
+static u32 pl111_argb_to_lbbp(u32 argb_pix)
+{
+ u32 lbbp_pix = CLCD_CRSR_LBBP_TRANSPARENT;
+ u32 alpha = (argb_pix & ARGB_ALPHA_MASK) >> ARGB_ALPHA_SHIFT;
+ u32 red = (argb_pix & ARGB_RED_MASK) >> ARGB_RED_SHIFT;
+ u32 green = (argb_pix & ARGB_GREEN_MASK) >> ARGB_GREEN_SHIFT;
+ u32 blue = (argb_pix & ARGB_BLUE_MASK) >> ARGB_BLUE_SHIFT;
+
+ /*
+ * Converting from 8 pixel transparency to binary transparency
+ * it's the best we can achieve.
+ */
+ if (alpha & ARGB_2_LBBP_BINARY_THRESHOLD) {
+ u32 gray, max, min;
+
+ /*
+ * Convert to gray using the lightness method:
+ * gray = [max(R,G,B) + min(R,G,B)]/2
+ */
+ min = min(red, green);
+ min = min(min, blue);
+ max = max(red, green);
+ max = max(max, blue);
+ gray = (min + max) >> 1; /* divide by 2 */
+ /* Apply binary threshold to the gray value calculated */
+ if (gray & ARGB_2_LBBP_BINARY_THRESHOLD)
+ lbbp_pix = CLCD_CRSR_LBBP_FOREGROUND;
+ else
+ lbbp_pix = CLCD_CRSR_LBBP_BACKGROUND;
+ }
+
+ return lbbp_pix;
+}
+
+/*
+ * The PL111 hardware cursor supports only LBBP which is a 2bpp format but
+ * the cursor format from userspace is ARGB8888 so we need to convert
+ * to LBBP here.
+ */
+static void pl111_set_cursor_image(u32 *data)
+{
+#ifdef ARGB_LBBP_CONVERSION_DEBUG
+ /* Add 1 on width to insert trailing NULL */
+ char string_cursor[PL111_MAX_CURSOR_WIDTH + 1];
+#endif /* ARGB_LBBP_CONVERSION_DEBUG */
+ unsigned int x;
+ unsigned int y;
+
+ for (y = 0; y < PL111_MAX_CURSOR_HEIGHT; y++) {
+ for (x = 0; x < PL111_MAX_CURSOR_WIDTH; x++) {
+ u32 value = pl111_argb_to_lbbp(*data);
+
+#ifdef ARGB_LBBP_CONVERSION_DEBUG
+ if (value == CLCD_CRSR_LBBP_TRANSPARENT)
+ string_cursor[x] = 'T';
+ else if (value == CLCD_CRSR_LBBP_FOREGROUND)
+ string_cursor[x] = 'F';
+ else if (value == CLCD_CRSR_LBBP_INVERSE)
+ string_cursor[x] = 'I';
+ else
+ string_cursor[x] = 'B';
+
+#endif /* ARGB_LBBP_CONVERSION_DEBUG */
+ set_lbbp_pixel(data, x, y, value);
+ ++data;
+ }
+#ifdef ARGB_LBBP_CONVERSION_DEBUG
+ string_cursor[PL111_MAX_CURSOR_WIDTH] = '\0';
+ DRM_INFO("%s\n", string_cursor);
+#endif /* ARGB_LBBP_CONVERSION_DEBUG */
+ }
+}
+
+int pl111_crtc_cursor_set(struct drm_crtc *crtc,
+ struct drm_file *file_priv,
+ uint32_t handle,
+ uint32_t width,
+ uint32_t height)
+{
+ struct drm_gem_object *obj;
+ struct pl111_gem_bo *bo;
+
+ DRM_DEBUG_KMS("handle = %u, width = %u, height = %u\n",
+ handle, width, height);
+
+ if (!handle) {
+ pl111_cursor_disable();
+ return 0;
+ }
+
+ if ((width != PL111_MAX_CURSOR_WIDTH) ||
+ (height != PL111_MAX_CURSOR_HEIGHT))
+ return -EINVAL;
+
+ obj = drm_gem_object_lookup(crtc->dev, file_priv, handle);
+ if (!obj) {
+ DRM_ERROR("Cannot find cursor object for handle = %d\n",
+ handle);
+ return -ENOENT;
+ }
+
+ /*
+ * We expect a PL111_MAX_CURSOR_WIDTH x PL111_MAX_CURSOR_HEIGHT
+ * ARGB888 buffer object in the input.
+ *
+ */
+ if (obj->size < (PL111_MAX_CURSOR_WIDTH * PL111_MAX_CURSOR_HEIGHT * 4)) {
+ DRM_ERROR("Cannot set cursor with an obj size = %d\n",
+ obj->size);
+ drm_gem_object_unreference_unlocked(obj);
+ return -EINVAL;
+ }
+
+ bo = PL111_BO_FROM_GEM(obj);
+ if (!(bo->type & PL111_BOT_DMA)) {
+ DRM_ERROR("Tried to set cursor with non DMA backed obj = %p\n",
+ obj);
+ drm_gem_object_unreference_unlocked(obj);
+ return -EINVAL;
+ }
+
+ pl111_set_cursor_image(bo->backing_data.dma.fb_cpu_addr);
+
+ /*
+ * Since we copy the contents of the buffer to the HW cursor internal
+ * memory this GEM object is not needed anymore.
+ */
+ drm_gem_object_unreference_unlocked(obj);
+
+ pl111_cursor_enable();
+
+ return 0;
+}
+
+int pl111_crtc_cursor_move(struct drm_crtc *crtc,
+ int x, int y)
+{
+ int x_clip = 0;
+ int y_clip = 0;
+
+ DRM_DEBUG("x %d y %d\n", x, y);
+
+ /*
+ * The cursor image is clipped automatically at the screen limits when
+ * it extends beyond the screen image to the right or bottom but
+ * we must clip it using pl111 HW features for negative values.
+ */
+ if (x < 0) {
+ x_clip = -x;
+ x = 0;
+ }
+ if (y < 0) {
+ y_clip = -y;
+ y = 0;
+ }
+
+ pl111_set_cursor_clipping(x_clip, y_clip);
+ pl111_set_cursor_position(x, y);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_device.c b/drivers/gpu/drm/pl111/pl111_drm_device.c
new file mode 100755
index 000000000000..b8ea5f6725ec
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_device.c
@@ -0,0 +1,326 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_device.c
+ * Implementation of the Linux device driver entrypoints for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+struct pl111_drm_dev_private priv;
+
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+static void initial_kds_obtained(void *cb1, void *cb2)
+{
+ wait_queue_head_t *wait = (wait_queue_head_t *) cb1;
+ bool *cb_has_called = (bool *) cb2;
+
+ *cb_has_called = true;
+ wake_up(wait);
+}
+
+/* Must be called from within current_displaying_lock spinlock */
+void release_kds_resource_and_display(struct pl111_drm_flip_resource *flip_res)
+{
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc);
+ pl111_crtc->displaying_fb = flip_res->fb;
+
+ /* Release the previous buffer */
+ if (pl111_crtc->old_kds_res_set != NULL) {
+ /*
+ * Can flip to the same buffer, but must not release the current
+ * resource set
+ */
+ BUG_ON(pl111_crtc->old_kds_res_set == flip_res->kds_res_set);
+ kds_resource_set_release(&pl111_crtc->old_kds_res_set);
+ }
+ /* Record the current buffer, to release on the next buffer flip */
+ pl111_crtc->old_kds_res_set = flip_res->kds_res_set;
+}
+#endif
+
+void pl111_drm_preclose(struct drm_device *dev, struct drm_file *file_priv)
+{
+ DRM_DEBUG_KMS("DRM %s on dev=%p\n", __func__, dev);
+}
+
+void pl111_drm_lastclose(struct drm_device *dev)
+{
+ DRM_DEBUG_KMS("DRM %s on dev=%p\n", __func__, dev);
+}
+
+/*
+ * pl111 does not have a proper HW counter for vblank IRQs so enable_vblank
+ * and disable_vblank are just no op callbacks.
+ */
+static int pl111_enable_vblank(struct drm_device *dev, int crtc)
+{
+ DRM_DEBUG_KMS("%s: dev=%p, crtc=%d", __func__, dev, crtc);
+ return 0;
+}
+
+static void pl111_disable_vblank(struct drm_device *dev, int crtc)
+{
+ DRM_DEBUG_KMS("%s: dev=%p, crtc=%d", __func__, dev, crtc);
+}
+
+struct drm_mode_config_funcs mode_config_funcs = {
+ .fb_create = pl111_fb_create,
+};
+
+static int pl111_modeset_init(struct drm_device *dev)
+{
+ struct drm_mode_config *mode_config;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ struct pl111_drm_connector *pl111_connector;
+ struct pl111_drm_encoder *pl111_encoder;
+ int ret = 0;
+
+ if (priv == NULL)
+ return -EINVAL;
+
+ drm_mode_config_init(dev);
+ mode_config = &dev->mode_config;
+ mode_config->funcs = &mode_config_funcs;
+ mode_config->min_width = 1;
+ mode_config->max_width = 1024;
+ mode_config->min_height = 1;
+ mode_config->max_height = 768;
+
+ priv->pl111_crtc = pl111_crtc_create(dev);
+ if (priv->pl111_crtc == NULL) {
+ pr_err("Failed to create pl111_drm_crtc\n");
+ ret = -ENOMEM;
+ goto out_config;
+ }
+
+ priv->number_crtcs = 1;
+
+ pl111_connector = pl111_connector_create(dev);
+ if (pl111_connector == NULL) {
+ pr_err("Failed to create pl111_drm_connector\n");
+ ret = -ENOMEM;
+ goto out_config;
+ }
+
+ pl111_encoder = pl111_encoder_create(dev, 1);
+ if (pl111_encoder == NULL) {
+ pr_err("Failed to create pl111_drm_encoder\n");
+ ret = -ENOMEM;
+ goto out_config;
+ }
+
+ ret = drm_mode_connector_attach_encoder(&pl111_connector->connector,
+ &pl111_encoder->encoder);
+ if (ret != 0) {
+ DRM_ERROR("Failed to attach encoder\n");
+ goto out_config;
+ }
+
+ pl111_connector->connector.encoder = &pl111_encoder->encoder;
+
+ goto finish;
+
+out_config:
+ drm_mode_config_cleanup(dev);
+finish:
+ DRM_DEBUG("%s returned %d\n", __func__, ret);
+ return ret;
+}
+
+static void pl111_modeset_fini(struct drm_device *dev)
+{
+ drm_mode_config_cleanup(dev);
+}
+
+static int pl111_drm_load(struct drm_device *dev, unsigned long chipset)
+{
+ int ret = 0;
+
+ pr_info("DRM %s\n", __func__);
+
+ mutex_init(&priv.export_dma_buf_lock);
+ atomic_set(&priv.nr_flips_in_flight, 0);
+ init_waitqueue_head(&priv.wait_for_flips);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ ret = kds_callback_init(&priv.kds_cb, 1, show_framebuffer_on_crtc_cb);
+ if (ret != 0) {
+ pr_err("Failed to initialise KDS callback\n");
+ goto finish;
+ }
+
+ ret = kds_callback_init(&priv.kds_obtain_current_cb, 1,
+ initial_kds_obtained);
+ if (ret != 0) {
+ pr_err("Failed to init KDS obtain callback\n");
+ kds_callback_term(&priv.kds_cb);
+ goto finish;
+ }
+#endif
+
+ /* Create a cache for page flips */
+ priv.page_flip_slab = kmem_cache_create("page flip slab",
+ sizeof(struct pl111_drm_flip_resource), 0, 0, NULL);
+ if (priv.page_flip_slab == NULL) {
+ DRM_ERROR("Failed to create slab\n");
+ ret = -ENOMEM;
+ goto out_kds_callbacks;
+ }
+
+ dev->dev_private = &priv;
+
+ ret = pl111_modeset_init(dev);
+ if (ret != 0) {
+ pr_err("Failed to init modeset\n");
+ goto out_slab;
+ }
+
+ ret = pl111_device_init(dev);
+ if (ret != 0) {
+ DRM_ERROR("Failed to init MMIO and IRQ\n");
+ goto out_modeset;
+ }
+
+ ret = drm_vblank_init(dev, 1);
+ if (ret != 0) {
+ DRM_ERROR("Failed to init vblank\n");
+ goto out_vblank;
+ }
+
+ goto finish;
+
+out_vblank:
+ pl111_device_fini(dev);
+out_modeset:
+ pl111_modeset_fini(dev);
+out_slab:
+ kmem_cache_destroy(priv.page_flip_slab);
+out_kds_callbacks:
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ kds_callback_term(&priv.kds_obtain_current_cb);
+ kds_callback_term(&priv.kds_cb);
+#endif
+finish:
+ DRM_DEBUG_KMS("pl111_drm_load returned %d\n", ret);
+ return ret;
+}
+
+static int pl111_drm_unload(struct drm_device *dev)
+{
+ pr_info("DRM %s\n", __func__);
+
+ kmem_cache_destroy(priv.page_flip_slab);
+
+ drm_vblank_cleanup(dev);
+ pl111_modeset_fini(dev);
+ pl111_device_fini(dev);
+
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ kds_callback_term(&priv.kds_obtain_current_cb);
+ kds_callback_term(&priv.kds_cb);
+#endif
+ return 0;
+}
+
+static struct vm_operations_struct pl111_gem_vm_ops = {
+ .fault = pl111_gem_fault,
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0))
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+#else
+ .open = pl111_gem_vm_open,
+ .close = pl111_gem_vm_close,
+#endif
+};
+
+static const struct file_operations drm_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .mmap = pl111_gem_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .fasync = drm_fasync,
+};
+
+static struct drm_ioctl_desc pl111_ioctls[] = {
+ DRM_IOCTL_DEF_DRV(PL111_GEM_CREATE, pl111_drm_gem_create_ioctl,
+ DRM_CONTROL_ALLOW | DRM_UNLOCKED),
+};
+
+static struct drm_driver driver = {
+ .driver_features =
+ DRIVER_MODESET | DRIVER_FB_DMA | DRIVER_GEM | DRIVER_PRIME,
+ .load = pl111_drm_load,
+ .unload = pl111_drm_unload,
+ .context_dtor = NULL,
+ .preclose = pl111_drm_preclose,
+ .lastclose = pl111_drm_lastclose,
+ .suspend = pl111_drm_suspend,
+ .resume = pl111_drm_resume,
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = pl111_enable_vblank,
+ .disable_vblank = pl111_disable_vblank,
+ .ioctls = pl111_ioctls,
+ .fops = &drm_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+ .patchlevel = DRIVER_PATCHLEVEL,
+ .dumb_create = pl111_dumb_create,
+ .dumb_destroy = pl111_dumb_destroy,
+ .dumb_map_offset = pl111_dumb_map_offset,
+ .gem_free_object = pl111_gem_free_object,
+ .gem_vm_ops = &pl111_gem_vm_ops,
+ .prime_handle_to_fd = &pl111_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_export = &pl111_gem_prime_export,
+ .gem_prime_import = &pl111_gem_prime_import,
+};
+
+int pl111_drm_init(struct platform_device *dev)
+{
+ int ret;
+ pr_info("DRM %s\n", __func__);
+ pr_info("PL111 DRM initialize, driver name: %s, version %d.%d\n",
+ DRIVER_NAME, DRIVER_MAJOR, DRIVER_MINOR);
+ driver.num_ioctls = DRM_ARRAY_SIZE(pl111_ioctls);
+ ret = 0;
+ driver.kdriver.platform_device = dev;
+ return drm_platform_init(&driver, dev);
+
+}
+
+void pl111_drm_exit(struct platform_device *dev)
+{
+ pr_info("DRM %s\n", __func__);
+ drm_platform_exit(&driver, dev);
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c b/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c
new file mode 100755
index 000000000000..a0d65f1d5faf
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_dma_buf.c
@@ -0,0 +1,615 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_dma_buf.c
+ * Implementation of the dma_buf functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+static void obtain_kds_if_currently_displayed(struct drm_device *dev,
+ struct pl111_gem_bo *bo,
+ struct dma_buf *dma_buf)
+{
+ unsigned long shared[1] = { 0 };
+ struct kds_resource *resource_list[1];
+ struct kds_resource_set *kds_res_set;
+ struct drm_crtc *crtc;
+ bool cb_has_called = false;
+ unsigned long flags;
+ int err;
+ DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
+
+ DRM_DEBUG_KMS("Obtaining initial KDS res for bo:%p dma_buf:%p\n",
+ bo, dma_buf);
+
+ resource_list[0] = get_dma_buf_kds_resource(dma_buf);
+ get_dma_buf(dma_buf);
+
+ /*
+ * Can't use kds_waitall(), because kbase will be let through due to
+ * locked ignore'
+ */
+ err = kds_async_waitall(&kds_res_set,
+ &priv.kds_obtain_current_cb, &wake,
+ &cb_has_called, 1, shared, resource_list);
+ BUG_ON(err);
+ wait_event(wake, cb_has_called == true);
+
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+ spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags);
+ if (pl111_crtc->displaying_fb) {
+ struct pl111_drm_framebuffer *pl111_fb;
+ struct drm_framebuffer *fb = pl111_crtc->displaying_fb;
+
+ pl111_fb = PL111_FB_FROM_FRAMEBUFFER(fb);
+
+ if (pl111_fb->bo == bo) {
+ DRM_DEBUG_KMS("Initial KDS resource for bo %p", bo);
+ DRM_DEBUG_KMS(" is being displayed, keeping\n");
+ /* There shouldn't be a previous buffer to release */
+ BUG_ON(pl111_crtc->old_kds_res_set);
+
+ if (kds_res_set == NULL) {
+ err = kds_async_waitall(&kds_res_set,
+ &priv.kds_obtain_current_cb,
+ &wake, &cb_has_called,
+ 1, shared, resource_list);
+ BUG_ON(err);
+ wait_event(wake, cb_has_called == true);
+ }
+
+ /* Current buffer will need releasing on next flip */
+ pl111_crtc->old_kds_res_set = kds_res_set;
+
+ /*
+ * Clear kds_res_set, so a new kds_res_set is allocated
+ * for additional CRTCs
+ */
+ kds_res_set = NULL;
+ }
+ }
+ spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags);
+ }
+
+ /* kds_res_set will be NULL here if any CRTCs are displaying fb */
+ if (kds_res_set != NULL) {
+ DRM_DEBUG_KMS("Initial KDS resource for bo %p", bo);
+ DRM_DEBUG_KMS(" not being displayed, discarding\n");
+ /* They're not being displayed, release them */
+ kds_resource_set_release(&kds_res_set);
+ }
+
+ dma_buf_put(dma_buf);
+}
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0))
+
+static int pl111_dma_buf_mmap(struct dma_buf *buffer,
+ struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj = buffer->priv;
+ struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj);
+ struct drm_device *dev = obj->dev;
+ int ret;
+
+ DRM_DEBUG_KMS("DRM %s on dma_buf=%p\n", __func__, buffer);
+
+ mutex_lock(&dev->struct_mutex);
+ ret = drm_gem_mmap_obj(obj, obj->size, vma);
+ mutex_unlock(&dev->struct_mutex);
+ if (ret)
+ return ret;
+
+ return pl111_bo_mmap(obj, bo, vma, buffer->size);
+}
+
+#else
+
+static int pl111_dma_buf_mmap(struct dma_buf *buffer,
+ struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj = buffer->priv;
+ struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj);
+ struct drm_device *dev = obj->dev;
+
+ DRM_DEBUG_KMS("DRM %s on dma_buf=%p\n", __func__, buffer);
+
+ mutex_lock(&dev->struct_mutex);
+
+ /* Check for valid size. */
+ if (obj->size < vma->vm_end - vma->vm_start)
+ return -EINVAL;
+
+ BUG_ON(!dev->driver->gem_vm_ops);
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0))
+ vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
+#else
+ vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND;
+#endif
+
+ vma->vm_ops = dev->driver->gem_vm_ops;
+ vma->vm_private_data = obj;
+
+ /* Take a ref for this mapping of the object, so that the fault
+ * handler can dereference the mmap offset's pointer to the object.
+ * This reference is cleaned up by the corresponding vm_close
+ * (which should happen whether the vma was created by this call, or
+ * by a vm_open due to mremap or partial unmap or whatever).
+ */
+ drm_gem_object_reference(obj);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
+ pl111_drm_vm_open_locked(dev, vma);
+#else
+ drm_vm_open_locked(dev, vma);
+#endif
+
+ mutex_unlock(&dev->struct_mutex);
+
+ return pl111_bo_mmap(obj, bo, vma, buffer->size);
+}
+
+#endif /* KERNEL_VERSION */
+
+static void pl111_dma_buf_release(struct dma_buf *buf)
+{
+ /*
+ * Need to release the dma_buf's reference on the gem object it was
+ * exported from, and also clear the gem object's export_dma_buf
+ * pointer to this dma_buf as it no longer exists
+ */
+ struct drm_gem_object *obj = (struct drm_gem_object *)buf->priv;
+ struct pl111_gem_bo *bo;
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ struct drm_crtc *crtc;
+ unsigned long flags;
+#endif
+ bo = PL111_BO_FROM_GEM(obj);
+
+ DRM_DEBUG_KMS("Releasing dma_buf %p, drm_gem_obj=%p\n", buf, obj);
+
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ list_for_each_entry(crtc, &bo->gem_object.dev->mode_config.crtc_list,
+ head) {
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+ spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags);
+ if (pl111_crtc->displaying_fb) {
+ struct pl111_drm_framebuffer *pl111_fb;
+ struct drm_framebuffer *fb = pl111_crtc->displaying_fb;
+
+ pl111_fb = PL111_FB_FROM_FRAMEBUFFER(fb);
+ if (pl111_fb->bo == bo) {
+ kds_resource_set_release(&pl111_crtc->old_kds_res_set);
+ pl111_crtc->old_kds_res_set = NULL;
+ }
+ }
+ spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags);
+ }
+#endif
+ mutex_lock(&priv.export_dma_buf_lock);
+
+ obj->export_dma_buf = NULL;
+ drm_gem_object_unreference_unlocked(obj);
+
+ mutex_unlock(&priv.export_dma_buf_lock);
+}
+
+static int pl111_dma_buf_attach(struct dma_buf *buf, struct device *dev,
+ struct dma_buf_attachment *attach)
+{
+ DRM_DEBUG_KMS("Attaching dma_buf %p to device %p attach=%p\n", buf,
+ dev, attach);
+
+ attach->priv = dev;
+
+ return 0;
+}
+
+static void pl111_dma_buf_detach(struct dma_buf *buf,
+ struct dma_buf_attachment *attach)
+{
+ DRM_DEBUG_KMS("Detaching dma_buf %p attach=%p\n", attach->dmabuf,
+ attach);
+}
+
+/* Heavily from exynos_drm_dmabuf.c */
+static struct sg_table *pl111_dma_buf_map_dma_buf(struct dma_buf_attachment
+ *attach,
+ enum dma_data_direction
+ direction)
+{
+ struct drm_gem_object *obj = attach->dmabuf->priv;
+ struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj);
+ struct drm_device *dev = obj->dev;
+ int size, n_pages, nents;
+ struct scatterlist *s, *sg;
+ struct sg_table *sgt;
+ int ret, i;
+
+ DRM_DEBUG_KMS("Mapping dma_buf %p from attach=%p (bo=%p)\n", attach->dmabuf,
+ attach, bo);
+
+ /*
+ * Nothing to do, if we are trying to map a dmabuf that has been imported.
+ * Just return the existing sgt.
+ */
+ if (obj->import_attach) {
+ BUG_ON(!bo->sgt);
+ return bo->sgt;
+ }
+
+ size = obj->size;
+ n_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
+
+ if (bo->type & PL111_BOT_DMA) {
+ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
+ if (!sgt) {
+ DRM_ERROR("Failed to allocate sg_table\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+ if (ret < 0) {
+ DRM_ERROR("Failed to allocate page table\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ sg_dma_len(sgt->sgl) = size;
+ /* We use DMA coherent mappings for PL111_BOT_DMA so we must
+ * use the virtual address returned at buffer allocation */
+ sg_set_buf(sgt->sgl, bo->backing_data.dma.fb_cpu_addr, size);
+ sg_dma_address(sgt->sgl) = bo->backing_data.dma.fb_dev_addr;
+ } else { /* PL111_BOT_SHM */
+ struct page **pages;
+ int pg = 0;
+
+ mutex_lock(&dev->struct_mutex);
+ pages = get_pages(obj);
+ if (IS_ERR(pages)) {
+ dev_err(obj->dev->dev, "could not get pages: %ld\n",
+ PTR_ERR(pages));
+ return ERR_CAST(pages);
+ }
+ sgt = drm_prime_pages_to_sg(pages, n_pages);
+ if (sgt == NULL)
+ return ERR_PTR(-ENOMEM);
+
+ pl111_gem_sync_to_dma(bo);
+
+ /*
+ * At this point the pages have been dma-mapped by either
+ * get_pages() for non cached maps or pl111_gem_sync_to_dma()
+ * for cached. So the physical addresses can be assigned
+ * to the sg entries.
+ * drm_prime_pages_to_sg() may have combined contiguous pages
+ * into chunks so we assign the physical address of the first
+ * page of a chunk to the chunk and check that the physical
+ * addresses of the rest of the pages in that chunk are also
+ * contiguous.
+ */
+ sg = sgt->sgl;
+ nents = sgt->nents;
+
+ for_each_sg(sg, s, nents, i) {
+ int j, n_pages_in_chunk = sg_dma_len(s) >> PAGE_SHIFT;
+
+ sg_dma_address(s) = bo->backing_data.shm.dma_addrs[pg];
+
+ for (j = pg+1; j < pg+n_pages_in_chunk; j++) {
+ BUG_ON(bo->backing_data.shm.dma_addrs[j] !=
+ bo->backing_data.shm.dma_addrs[j-1]+PAGE_SIZE);
+ }
+
+ pg += n_pages_in_chunk;
+ }
+
+ mutex_unlock(&dev->struct_mutex);
+ }
+ bo->sgt = sgt;
+ return sgt;
+}
+
+static void pl111_dma_buf_unmap_dma_buf(struct dma_buf_attachment *attach,
+ struct sg_table *sgt,
+ enum dma_data_direction direction)
+{
+ struct drm_gem_object *obj = attach->dmabuf->priv;
+ struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj);
+
+ DRM_DEBUG_KMS("Unmapping dma_buf %p from attach=%p (bo=%p)\n", attach->dmabuf,
+ attach, bo);
+
+ sg_free_table(sgt);
+ kfree(sgt);
+ bo->sgt = NULL;
+}
+
+/*
+ * There isn't any operation here that can sleep or fail so this callback can
+ * be used for both kmap and kmap_atomic implementations.
+ */
+static void *pl111_dma_buf_kmap(struct dma_buf *dma_buf, unsigned long pageno)
+{
+ struct pl111_gem_bo *bo = dma_buf->priv;
+ void *vaddr = NULL;
+
+ /* Make sure we cannot access outside the memory range */
+ if (((pageno + 1) << PAGE_SHIFT) > bo->gem_object.size)
+ return NULL;
+
+ if (bo->type & PL111_BOT_DMA) {
+ vaddr = (bo->backing_data.dma.fb_cpu_addr +
+ (pageno << PAGE_SHIFT));
+ } else {
+ vaddr = page_address(bo->backing_data.shm.pages[pageno]);
+ }
+
+ return vaddr;
+}
+
+/*
+ * Find a scatterlist that starts in "start" and has "len"
+ * or return a NULL dma_handle.
+ */
+static dma_addr_t pl111_find_matching_sg(struct sg_table *sgt, size_t start,
+ size_t len)
+{
+ struct scatterlist *sg;
+ unsigned int count;
+ size_t size = 0;
+ dma_addr_t dma_handle = 0;
+
+ /* Find a scatterlist that starts in "start" and has "len"
+ * or return error */
+ for_each_sg(sgt->sgl, sg, sgt->nents, count) {
+ if ((size == start) && (len == sg_dma_len(sg))) {
+ dma_handle = sg_dma_address(sg);
+ break;
+ }
+ size += sg_dma_len(sg);
+ }
+ return dma_handle;
+}
+
+static int pl111_dma_buf_begin_cpu(struct dma_buf *dma_buf,
+ size_t start, size_t len,
+ enum dma_data_direction dir)
+{
+ struct pl111_gem_bo *bo = dma_buf->priv;
+ struct sg_table *sgt = bo->sgt;
+ dma_addr_t dma_handle;
+
+ if ((start + len) > bo->gem_object.size)
+ return -EINVAL;
+
+ if (!(bo->type & PL111_BOT_SHM)) {
+ struct device *dev = bo->gem_object.dev->dev;
+
+ dma_handle = pl111_find_matching_sg(sgt, start, len);
+ if (!dma_handle)
+ return -EINVAL;
+
+ dma_sync_single_range_for_cpu(dev, dma_handle, 0, len, dir);
+ }
+ /* PL111_BOT_DMA uses coherents mappings, no need to sync */
+ return 0;
+}
+
+static void pl111_dma_buf_end_cpu(struct dma_buf *dma_buf,
+ size_t start, size_t len,
+ enum dma_data_direction dir)
+{
+ struct pl111_gem_bo *bo = dma_buf->priv;
+ struct sg_table *sgt = bo->sgt;
+ dma_addr_t dma_handle;
+
+ if ((start + len) > bo->gem_object.size)
+ return;
+
+ if (!(bo->type & PL111_BOT_DMA)) {
+ struct device *dev = bo->gem_object.dev->dev;
+
+ dma_handle = pl111_find_matching_sg(sgt, start, len);
+ if (!dma_handle)
+ return;
+
+ dma_sync_single_range_for_device(dev, dma_handle, 0, len, dir);
+ }
+ /* PL111_BOT_DMA uses coherents mappings, no need to sync */
+}
+
+static struct dma_buf_ops pl111_dma_buf_ops = {
+ .release = &pl111_dma_buf_release,
+ .attach = &pl111_dma_buf_attach,
+ .detach = &pl111_dma_buf_detach,
+ .map_dma_buf = &pl111_dma_buf_map_dma_buf,
+ .unmap_dma_buf = &pl111_dma_buf_unmap_dma_buf,
+ .kmap_atomic = &pl111_dma_buf_kmap,
+ .kmap = &pl111_dma_buf_kmap,
+ .begin_cpu_access = &pl111_dma_buf_begin_cpu,
+ .end_cpu_access = &pl111_dma_buf_end_cpu,
+ .mmap = &pl111_dma_buf_mmap,
+};
+
+struct drm_gem_object *pl111_gem_prime_import(struct drm_device *dev,
+ struct dma_buf *dma_buf)
+{
+ struct dma_buf_attachment *attachment;
+ struct drm_gem_object *obj;
+ struct pl111_gem_bo *bo;
+ struct scatterlist *sgl;
+ struct sg_table *sgt;
+ dma_addr_t cont_phys;
+ int ret = 0;
+ int i;
+
+ DRM_DEBUG_KMS("DRM %s on dev=%p dma_buf=%p\n", __func__, dev, dma_buf);
+
+ /* is this one of own objects? */
+ if (dma_buf->ops == &pl111_dma_buf_ops) {
+ obj = dma_buf->priv;
+ /* is it from our device? */
+ if (obj->dev == dev) {
+ /*
+ * Importing dmabuf exported from our own gem increases
+ * refcount on gem itself instead of f_count of dmabuf.
+ */
+ drm_gem_object_reference(obj);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
+ /* before v3.10.0 we assume the caller has taken a ref on the dma_buf
+ * we don't want it for self-imported buffers so drop it here */
+ dma_buf_put(dma_buf);
+#endif
+
+ return obj;
+ }
+ }
+
+ attachment = dma_buf_attach(dma_buf, dev->dev);
+ if (IS_ERR(attachment))
+ return ERR_CAST(attachment);
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+ /* from 3.10.0 we assume the caller has not taken a ref so we take one here */
+ get_dma_buf(dma_buf);
+#endif
+
+ sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL);
+ if (IS_ERR_OR_NULL(sgt)) {
+ ret = PTR_ERR(sgt);
+ goto err_buf_detach;
+ }
+
+ bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+ if (!bo) {
+ DRM_ERROR("%s: failed to allocate buffer object.\n", __func__);
+ ret = -ENOMEM;
+ goto err_unmap_attach;
+ }
+
+ /* Find out whether the buffer is contiguous or not */
+ sgl = sgt->sgl;
+ cont_phys = sg_phys(sgl);
+ bo->type |= PL111_BOT_DMA;
+ for_each_sg(sgt->sgl, sgl, sgt->nents, i) {
+ dma_addr_t real_phys = sg_phys(sgl);
+ if (real_phys != cont_phys) {
+ bo->type &= ~PL111_BOT_DMA;
+ break;
+ }
+ cont_phys += (PAGE_SIZE - sgl->offset);
+ }
+
+ ret = drm_gem_private_object_init(dev, &bo->gem_object,
+ dma_buf->size);
+ if (ret != 0) {
+ DRM_ERROR("DRM could not import DMA GEM obj\n");
+ goto err_free_buffer;
+ }
+
+ if (bo->type & PL111_BOT_DMA) {
+ bo->backing_data.dma.fb_cpu_addr = sg_virt(sgt->sgl);
+ bo->backing_data.dma.fb_dev_addr = sg_phys(sgt->sgl);
+ DRM_DEBUG_KMS("DRM %s pl111_gem_bo=%p, contiguous import\n", __func__, bo);
+ } else { /* PL111_BOT_SHM */
+ DRM_DEBUG_KMS("DRM %s pl111_gem_bo=%p, non contiguous import\n", __func__, bo);
+ }
+
+ bo->gem_object.import_attach = attachment;
+ bo->sgt = sgt;
+
+ return &bo->gem_object;
+
+err_free_buffer:
+ kfree(bo);
+ bo = NULL;
+err_unmap_attach:
+ dma_buf_unmap_attachment(attachment, sgt, DMA_BIDIRECTIONAL);
+err_buf_detach:
+ dma_buf_detach(dma_buf, attachment);
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
+ /* from 3.10.0 we will have taken a ref so drop it here */
+ dma_buf_put(dma_buf);
+#endif
+ return ERR_PTR(ret);
+}
+
+struct dma_buf *pl111_gem_prime_export(struct drm_device *dev,
+ struct drm_gem_object *obj, int flags)
+{
+ struct dma_buf *new_buf;
+ struct pl111_gem_bo *bo;
+ size_t size;
+
+ DRM_DEBUG("DRM %s on dev=%p drm_gem_obj=%p\n", __func__, dev, obj);
+ size = obj->size;
+
+ new_buf = dma_buf_export(obj /*priv */ , &pl111_dma_buf_ops, size,
+ flags | O_RDWR);
+ bo = PL111_BO_FROM_GEM(new_buf->priv);
+
+ /*
+ * bo->gem_object.export_dma_buf not setup until after gem_prime_export
+ * finishes
+ */
+
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ /*
+ * Ensure that we hold the kds resource if it's the currently
+ * displayed buffer.
+ */
+ obtain_kds_if_currently_displayed(dev, bo, new_buf);
+#endif
+
+ DRM_DEBUG("Created dma_buf %p\n", new_buf);
+
+ return new_buf;
+}
+
+int pl111_prime_handle_to_fd(struct drm_device *dev, struct drm_file *file_priv,
+ uint32_t handle, uint32_t flags, int *prime_fd)
+{
+ int result;
+ /*
+ * This will re-use any existing exports, and calls
+ * driver->gem_prime_export to do the first export when needed
+ */
+ DRM_DEBUG_KMS("DRM %s on file_priv=%p, handle=0x%.8x\n", __func__,
+ file_priv, handle);
+
+ mutex_lock(&priv.export_dma_buf_lock);
+ result = drm_gem_prime_handle_to_fd(dev, file_priv, handle, flags,
+ prime_fd);
+ mutex_unlock(&priv.export_dma_buf_lock);
+
+ return result;
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_encoder.c b/drivers/gpu/drm/pl111/pl111_drm_encoder.c
new file mode 100755
index 000000000000..b7dbbf2794ab
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_encoder.c
@@ -0,0 +1,107 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_encoder.c
+ * Implementation of the encoder functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+bool pl111_encoder_helper_mode_fixup(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder);
+ return true;
+}
+
+void pl111_encoder_helper_prepare(struct drm_encoder *encoder)
+{
+ DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder);
+}
+
+void pl111_encoder_helper_commit(struct drm_encoder *encoder)
+{
+ DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder);
+}
+
+void pl111_encoder_helper_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder);
+}
+
+void pl111_encoder_helper_disable(struct drm_encoder *encoder)
+{
+ DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder);
+}
+
+void pl111_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct pl111_drm_encoder *pl111_encoder =
+ PL111_ENCODER_FROM_ENCODER(encoder);
+
+ DRM_DEBUG_KMS("DRM %s on encoder=%p\n", __func__, encoder);
+
+ drm_encoder_cleanup(encoder);
+ kfree(pl111_encoder);
+}
+
+const struct drm_encoder_funcs encoder_funcs = {
+ .destroy = pl111_encoder_destroy,
+};
+
+const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+ .mode_fixup = pl111_encoder_helper_mode_fixup,
+ .prepare = pl111_encoder_helper_prepare,
+ .commit = pl111_encoder_helper_commit,
+ .mode_set = pl111_encoder_helper_mode_set,
+ .disable = pl111_encoder_helper_disable,
+};
+
+struct pl111_drm_encoder *pl111_encoder_create(struct drm_device *dev,
+ int possible_crtcs)
+{
+ struct pl111_drm_encoder *pl111_encoder;
+
+ pl111_encoder = kzalloc(sizeof(struct pl111_drm_encoder), GFP_KERNEL);
+ if (pl111_encoder == NULL) {
+ pr_err("Failed to allocated pl111_drm_encoder\n");
+ return NULL;
+ }
+
+ drm_encoder_init(dev, &pl111_encoder->encoder, &encoder_funcs,
+ DRM_MODE_ENCODER_DAC);
+
+ drm_encoder_helper_add(&pl111_encoder->encoder, &encoder_helper_funcs);
+
+ pl111_encoder->encoder.possible_crtcs = possible_crtcs;
+
+ return pl111_encoder;
+}
+
diff --git a/drivers/gpu/drm/pl111/pl111_drm_fb.c b/drivers/gpu/drm/pl111/pl111_drm_fb.c
new file mode 100755
index 000000000000..546e4d45f4c9
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_fb.c
@@ -0,0 +1,202 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_fb.c
+ * Implementation of the framebuffer functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_crtc.h>
+#include "pl111_drm.h"
+
+static void pl111_fb_destroy(struct drm_framebuffer *framebuffer)
+{
+ struct pl111_drm_framebuffer *pl111_fb;
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ struct drm_crtc *crtc;
+ unsigned long flags;
+#endif
+ DRM_DEBUG_KMS("Destroying framebuffer 0x%p...\n", framebuffer);
+
+ pl111_fb = PL111_FB_FROM_FRAMEBUFFER(framebuffer);
+
+ /*
+ * Because flips are deferred, wait for all previous flips to complete
+ */
+ wait_event(priv.wait_for_flips,
+ atomic_read(&priv.nr_flips_in_flight) == 0);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ /*
+ * Release KDS resources if it's currently being displayed. Only occurs
+ * when the last framebuffer is destroyed.
+ */
+ list_for_each_entry(crtc, &framebuffer->dev->mode_config.crtc_list,
+ head) {
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+ spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags);
+ if (pl111_crtc->displaying_fb == framebuffer) {
+ /* Release the current buffers */
+ if (pl111_crtc->old_kds_res_set != NULL) {
+ DRM_DEBUG_KMS("Releasing KDS resources for ");
+ DRM_DEBUG_KMS("displayed 0x%p\n", framebuffer);
+ kds_resource_set_release(
+ &pl111_crtc->old_kds_res_set);
+ }
+ pl111_crtc->old_kds_res_set = NULL;
+ }
+ spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock,
+ flags);
+ }
+#endif
+ drm_framebuffer_cleanup(framebuffer);
+
+ if ((pl111_fb->bo != NULL) && (&pl111_fb->bo->gem_object != NULL))
+ drm_gem_object_unreference_unlocked(&pl111_fb->bo->gem_object);
+
+ kfree(pl111_fb);
+
+ DRM_DEBUG_KMS("Destroyed framebuffer 0x%p\n", framebuffer);
+}
+
+static int pl111_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned int *handle)
+{
+ struct pl111_gem_bo *bo = PL111_BO_FROM_FRAMEBUFFER(fb);
+ DRM_DEBUG_KMS("DRM %s on fb=%p\n", __func__, fb);
+
+ if (bo == NULL)
+ return -EINVAL;
+
+ return drm_gem_handle_create(file_priv, &bo->gem_object, handle);
+}
+
+const struct drm_framebuffer_funcs fb_funcs = {
+ .destroy = pl111_fb_destroy,
+ .create_handle = pl111_fb_create_handle,
+};
+
+struct drm_framebuffer *pl111_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct pl111_drm_framebuffer *pl111_fb = NULL;
+ struct drm_framebuffer *fb = NULL;
+ struct drm_gem_object *gem_obj;
+ struct pl111_gem_bo *bo;
+ int err = 0;
+ size_t min_size;
+ int bpp;
+ int depth;
+
+ pr_info("DRM %s\n", __func__);
+ gem_obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
+ if (gem_obj == NULL) {
+ DRM_ERROR("Could not get gem obj from handle to create fb\n");
+ err = -ENOENT;
+ goto error;
+ }
+
+ bo = PL111_BO_FROM_GEM(gem_obj);
+ drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp);
+
+ if (mode_cmd->pitches[0] < mode_cmd->width * (bpp >> 3)) {
+ DRM_ERROR("bad pitch %u for plane 0\n", mode_cmd->pitches[0]);
+ err = -EINVAL;
+ goto error;
+ }
+
+ min_size = (mode_cmd->height - 1) * mode_cmd->pitches[0]
+ + mode_cmd->width * (bpp >> 3);
+
+ if (bo->gem_object.size < min_size) {
+ DRM_ERROR("gem obj size < min size\n");
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* We can't scan out SHM so we can't create an fb for it */
+ if (!(bo->type & PL111_BOT_DMA)) {
+ DRM_ERROR("Can't create FB for non-scanout buffer\n");
+ err = -EINVAL;
+ goto error;
+ }
+
+ switch ((char)(mode_cmd->pixel_format & 0xFF)) {
+ case 'Y':
+ case 'U':
+ case 'V':
+ case 'N':
+ case 'T':
+ DRM_ERROR("YUV formats not supported\n");
+ err = -EINVAL;
+ goto error;
+ }
+
+ pl111_fb = kzalloc(sizeof(struct pl111_drm_framebuffer), GFP_KERNEL);
+ if (pl111_fb == NULL) {
+ DRM_ERROR("Could not allocate pl111_drm_framebuffer\n");
+ err = -ENOMEM;
+ goto error;
+ }
+ fb = &pl111_fb->fb;
+
+ err = drm_framebuffer_init(dev, fb, &fb_funcs);
+ if (err) {
+ DRM_ERROR("drm_framebuffer_init failed\n");
+ kfree(fb);
+ fb = NULL;
+ goto error;
+ }
+
+ drm_helper_mode_fill_fb_struct(fb, mode_cmd);
+
+ /* The only framebuffer formats supported by pl111
+ * are 16 bpp or 32 bpp with 24 bit depth.
+ * See clcd_enable()
+ */
+ if (!((fb->bits_per_pixel == 16) ||
+ (fb->bits_per_pixel == 32 && fb->depth == 24))) {
+ DRM_DEBUG_KMS("unsupported pixel format bpp=%d, depth=%d\n", fb->bits_per_pixel, fb->depth);
+ drm_framebuffer_cleanup(fb);
+ kfree(fb);
+ fb = NULL;
+ err = -EINVAL;
+ goto error;
+ }
+
+ pl111_fb->bo = bo;
+
+ DRM_DEBUG_KMS("Created fb 0x%p with gem_obj 0x%p physaddr=0x%.8x\n",
+ fb, gem_obj, bo->backing_data.dma.fb_dev_addr);
+
+ return fb;
+
+error:
+ if (gem_obj != NULL)
+ drm_gem_object_unreference_unlocked(gem_obj);
+
+ return ERR_PTR(err);
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_funcs.h b/drivers/gpu/drm/pl111/pl111_drm_funcs.h
new file mode 100755
index 000000000000..34e0bad994d7
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_funcs.h
@@ -0,0 +1,130 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_funcs.h
+ * Function prototypes for PL111 DRM
+ */
+
+#ifndef PL111_DRM_FUNCS_H_
+#define PL111_DRM_FUNCS_H_
+
+/* Platform Initialisation */
+int pl111_drm_init(struct platform_device *dev);
+void pl111_drm_exit(struct platform_device *dev);
+
+/* KDS Callbacks */
+void show_framebuffer_on_crtc_cb(void *cb1, void *cb2);
+void release_kds_resource_and_display(struct pl111_drm_flip_resource *flip_res);
+
+/* CRTC Functions */
+struct pl111_drm_crtc *pl111_crtc_create(struct drm_device *dev);
+struct pl111_drm_crtc *pl111_crtc_dummy_create(struct drm_device *dev);
+void pl111_crtc_destroy(struct drm_crtc *crtc);
+
+bool pl111_crtc_is_fb_currently_displayed(struct drm_device *dev,
+ struct drm_framebuffer *fb);
+
+int show_framebuffer_on_crtc(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, bool page_flip,
+ struct drm_pending_vblank_event *event);
+
+/* Common IRQ handler */
+void pl111_common_irq(struct pl111_drm_crtc *pl111_crtc);
+
+int pl111_crtc_cursor_set(struct drm_crtc *crtc,
+ struct drm_file *file_priv,
+ uint32_t handle,
+ uint32_t width,
+ uint32_t height);
+int pl111_crtc_cursor_move(struct drm_crtc *crtc,
+ int x, int y);
+
+/* Connector Functions */
+struct pl111_drm_connector *pl111_connector_create(struct drm_device *dev);
+void pl111_connector_destroy(struct drm_connector *connector);
+struct pl111_drm_connector *pl111_connector_dummy_create(struct drm_device
+ *dev);
+
+/* Encoder Functions */
+struct pl111_drm_encoder *pl111_encoder_create(struct drm_device *dev,
+ int possible_crtcs);
+struct pl111_drm_encoder *pl111_encoder_dummy_create(struct drm_device *dev,
+ int possible_crtcs);
+void pl111_encoder_destroy(struct drm_encoder *encoder);
+
+/* Frame Buffer Functions */
+struct drm_framebuffer *pl111_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv,
+ struct drm_mode_fb_cmd2 *mode_cmd);
+
+/* VMA Functions */
+int pl111_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
+int pl111_gem_mmap(struct file *file_priv, struct vm_area_struct *vma);
+struct page **get_pages(struct drm_gem_object *obj);
+void put_pages(struct drm_gem_object *obj, struct page **pages);
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
+void pl111_drm_vm_open_locked(struct drm_device *dev,
+ struct vm_area_struct *vma);
+void pl111_gem_vm_open(struct vm_area_struct *vma);
+void pl111_gem_vm_close(struct vm_area_struct *vma);
+#endif
+
+/* Suspend Functions */
+int pl111_drm_resume(struct drm_device *dev);
+int pl111_drm_suspend(struct drm_device *dev, pm_message_t state);
+
+/* GEM Functions */
+int pl111_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+int pl111_dumb_destroy(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle);
+int pl111_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle,
+ uint64_t *offset);
+void pl111_gem_free_object(struct drm_gem_object *obj);
+
+int pl111_bo_mmap(struct drm_gem_object *obj, struct pl111_gem_bo *bo,
+ struct vm_area_struct *vma, size_t size);
+void pl111_gem_sync_to_cpu(struct pl111_gem_bo *bo, int pgoff);
+void pl111_gem_sync_to_dma(struct pl111_gem_bo *bo);
+
+/* DMA BUF Functions */
+struct drm_gem_object *pl111_gem_prime_import(struct drm_device *dev,
+ struct dma_buf *dma_buf);
+int pl111_prime_handle_to_fd(struct drm_device *dev, struct drm_file *file_priv,
+ uint32_t handle, uint32_t flags, int *prime_fd);
+struct dma_buf *pl111_gem_prime_export(struct drm_device *dev,
+ struct drm_gem_object *obj, int flags);
+
+/* Pl111 Functions */
+void show_framebuffer_on_crtc_cb_internal(struct pl111_drm_flip_resource
+ *flip_res, struct drm_framebuffer *fb);
+int clcd_disable(struct drm_crtc *crtc);
+void do_flip_to_res(struct pl111_drm_flip_resource *flip_res);
+int pl111_amba_probe(struct amba_device *dev, const struct amba_id *id);
+int pl111_amba_remove(struct amba_device *dev);
+
+int pl111_device_init(struct drm_device *dev);
+void pl111_device_fini(struct drm_device *dev);
+
+void pl111_convert_drm_mode_to_timing(struct drm_display_mode *mode,
+ struct clcd_regs *timing);
+void pl111_convert_timing_to_drm_mode(struct clcd_regs *timing,
+ struct drm_display_mode *mode);
+#endif /* PL111_DRM_FUNCS_H_ */
diff --git a/drivers/gpu/drm/pl111/pl111_drm_gem.c b/drivers/gpu/drm/pl111/pl111_drm_gem.c
new file mode 100755
index 000000000000..ed5dcabe0082
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_gem.c
@@ -0,0 +1,401 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_gem.c
+ * Implementation of the GEM functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <asm/cacheflush.h>
+#include <asm/outercache.h>
+#include "pl111_drm.h"
+
+void pl111_gem_free_object(struct drm_gem_object *obj)
+{
+ struct pl111_gem_bo *bo;
+ struct drm_device *dev = obj->dev;
+ DRM_DEBUG_KMS("DRM %s on drm_gem_object=%p\n", __func__, obj);
+
+ bo = PL111_BO_FROM_GEM(obj);
+
+ if (obj->import_attach)
+ drm_prime_gem_destroy(obj, bo->sgt);
+
+ if (obj->map_list.map != NULL)
+ drm_gem_free_mmap_offset(obj);
+
+ /*
+ * Only free the backing memory if the object has not been imported.
+ * If it has been imported, the exporter is in charge to free that
+ * once dmabuf's refcount becomes 0.
+ */
+ if (obj->import_attach)
+ goto imported_out;
+
+ if (bo->type & PL111_BOT_DMA) {
+ dma_free_writecombine(dev->dev, obj->size,
+ bo->backing_data.dma.fb_cpu_addr,
+ bo->backing_data.dma.fb_dev_addr);
+ } else if (bo->backing_data.shm.pages != NULL) {
+ put_pages(obj, bo->backing_data.shm.pages);
+ }
+
+imported_out:
+ drm_gem_object_release(obj);
+
+ kfree(bo);
+
+ DRM_DEBUG_KMS("Destroyed dumb_bo handle 0x%p\n", bo);
+}
+
+static int pl111_gem_object_create(struct drm_device *dev, u64 size,
+ u32 flags, struct drm_file *file_priv,
+ u32 *handle)
+{
+ int ret = 0;
+ struct pl111_gem_bo *bo = NULL;
+
+ bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+ if (bo == NULL) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ bo->type = flags;
+
+#ifndef ARCH_HAS_SG_CHAIN
+ /*
+ * If the ARCH can't chain we can't have non-contiguous allocs larger
+ * than a single sg can hold.
+ * In this case we fall back to using contiguous memory
+ */
+ if (!(bo->type & PL111_BOT_DMA)) {
+ long unsigned int n_pages =
+ PAGE_ALIGN(size) >> PAGE_SHIFT;
+ if (n_pages > SG_MAX_SINGLE_ALLOC) {
+ bo->type |= PL111_BOT_DMA;
+ /*
+ * Non-contiguous allocation request changed to
+ * contigous
+ */
+ DRM_INFO("non-contig alloc to contig %lu > %lu pages.",
+ n_pages, SG_MAX_SINGLE_ALLOC);
+ }
+ }
+#endif
+ if (bo->type & PL111_BOT_DMA) {
+ /* scanout compatible - use physically contiguous buffer */
+ bo->backing_data.dma.fb_cpu_addr =
+ dma_alloc_writecombine(dev->dev, size,
+ &bo->backing_data.dma.fb_dev_addr,
+ GFP_KERNEL);
+ if (bo->backing_data.dma.fb_cpu_addr == NULL) {
+ DRM_ERROR("dma_alloc_writecombine failed\n");
+ ret = -ENOMEM;
+ goto free_bo;
+ }
+
+ ret = drm_gem_private_object_init(dev, &bo->gem_object,
+ size);
+ if (ret != 0) {
+ DRM_ERROR("DRM could not initialise GEM object\n");
+ goto free_dma;
+ }
+ } else { /* PL111_BOT_SHM */
+ /* not scanout compatible - use SHM backed object */
+ ret = drm_gem_object_init(dev, &bo->gem_object, size);
+ if (ret != 0) {
+ DRM_ERROR("DRM could not init SHM backed GEM obj\n");
+ ret = -ENOMEM;
+ goto free_bo;
+ }
+ DRM_DEBUG_KMS("Num bytes: %d\n", bo->gem_object.size);
+ }
+
+ DRM_DEBUG("s=%llu, flags=0x%x, %s 0x%.8lx, type=%d\n",
+ size, flags,
+ (bo->type & PL111_BOT_DMA) ? "physaddr" : "shared page array",
+ (bo->type & PL111_BOT_DMA) ?
+ (unsigned long)bo->backing_data.dma.fb_dev_addr:
+ (unsigned long)bo->backing_data.shm.pages,
+ bo->type);
+
+ ret = drm_gem_handle_create(file_priv, &bo->gem_object, handle);
+ if (ret != 0) {
+ DRM_ERROR("DRM failed to create GEM handle\n");
+ goto obj_release;
+ }
+
+ /* drop reference from allocate - handle holds it now */
+ drm_gem_object_unreference_unlocked(&bo->gem_object);
+
+ return 0;
+
+obj_release:
+ drm_gem_object_release(&bo->gem_object);
+free_dma:
+ if (bo->type & PL111_BOT_DMA)
+ dma_free_writecombine(dev->dev, size,
+ bo->backing_data.dma.fb_cpu_addr,
+ bo->backing_data.dma.fb_dev_addr);
+free_bo:
+ kfree(bo);
+finish:
+ return ret;
+}
+
+int pl111_drm_gem_create_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_pl111_gem_create *args = data;
+ uint32_t bytes_pp;
+
+ /* Round bpp up, to allow for case where bpp<8 */
+ bytes_pp = args->bpp >> 3;
+ if (args->bpp & ((1 << 3) - 1))
+ bytes_pp++;
+
+ if (args->flags & ~PL111_BOT_MASK) {
+ DRM_ERROR("wrong flags: 0x%x\n", args->flags);
+ return -EINVAL;
+ }
+
+ args->pitch = ALIGN(args->width * bytes_pp, 64);
+ args->size = PAGE_ALIGN(args->pitch * args->height);
+
+ DRM_DEBUG_KMS("gem_create w=%d h=%d p=%d bpp=%d b=%d s=%llu f=0x%x\n",
+ args->width, args->height, args->pitch, args->bpp,
+ bytes_pp, args->size, args->flags);
+
+ return pl111_gem_object_create(dev, args->size, args->flags, file_priv,
+ &args->handle);
+}
+
+int pl111_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev, struct drm_mode_create_dumb *args)
+{
+ uint32_t bytes_pp;
+
+ /* Round bpp up, to allow for case where bpp<8 */
+ bytes_pp = args->bpp >> 3;
+ if (args->bpp & ((1 << 3) - 1))
+ bytes_pp++;
+
+ if (args->flags) {
+ DRM_ERROR("flags must be zero: 0x%x\n", args->flags);
+ return -EINVAL;
+ }
+
+ args->pitch = ALIGN(args->width * bytes_pp, 64);
+ args->size = PAGE_ALIGN(args->pitch * args->height);
+
+ DRM_DEBUG_KMS("dumb_create w=%d h=%d p=%d bpp=%d b=%d s=%llu f=0x%x\n",
+ args->width, args->height, args->pitch, args->bpp,
+ bytes_pp, args->size, args->flags);
+
+ return pl111_gem_object_create(dev, args->size,
+ PL111_BOT_DMA | PL111_BOT_UNCACHED,
+ file_priv, &args->handle);
+}
+
+int pl111_dumb_destroy(struct drm_file *file_priv, struct drm_device *dev,
+ uint32_t handle)
+{
+ DRM_DEBUG_KMS("DRM %s on file_priv=%p handle=0x%.8x\n", __func__,
+ file_priv, handle);
+ return drm_gem_handle_delete(file_priv, handle);
+}
+
+int pl111_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *dev, uint32_t handle,
+ uint64_t *offset)
+{
+ struct drm_gem_object *obj;
+ int ret = 0;
+ DRM_DEBUG_KMS("DRM %s on file_priv=%p handle=0x%.8x\n", __func__,
+ file_priv, handle);
+
+ /* GEM does all our handle to object mapping */
+ obj = drm_gem_object_lookup(dev, file_priv, handle);
+ if (obj == NULL) {
+ ret = -ENOENT;
+ goto fail;
+ }
+
+ if (obj->map_list.map == NULL) {
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret != 0) {
+ drm_gem_object_unreference_unlocked(obj);
+ goto fail;
+ }
+ }
+
+ *offset = (uint64_t) obj->map_list.hash.key << PAGE_SHIFT;
+
+ drm_gem_object_unreference_unlocked(obj);
+fail:
+ return ret;
+}
+
+/* sync the buffer for DMA access */
+void pl111_gem_sync_to_dma(struct pl111_gem_bo *bo)
+{
+ struct drm_device *dev = bo->gem_object.dev;
+
+ if (!(bo->type & PL111_BOT_DMA) && (bo->type & PL111_BOT_CACHED)) {
+ int i, npages = bo->gem_object.size >> PAGE_SHIFT;
+ struct page **pages = bo->backing_data.shm.pages;
+ bool dirty = false;
+
+ for (i = 0; i < npages; i++) {
+ if (!bo->backing_data.shm.dma_addrs[i]) {
+ DRM_DEBUG("%s: dma map page=%d bo=%p\n", __func__, i, bo);
+ bo->backing_data.shm.dma_addrs[i] =
+ dma_map_page(dev->dev, pages[i], 0,
+ PAGE_SIZE, DMA_BIDIRECTIONAL);
+ dirty = true;
+ }
+ }
+
+ if (dirty) {
+ DRM_DEBUG("%s: zap ptes (and flush cache) bo=%p\n", __func__, bo);
+ /*
+ * TODO MIDEGL-1813
+ *
+ * Use flush_cache_page() and outer_flush_range() to
+ * flush only the user space mappings of the dirty pages
+ */
+ flush_cache_all();
+ outer_flush_all();
+ unmap_mapping_range(bo->gem_object.filp->f_mapping, 0,
+ bo->gem_object.size, 1);
+ }
+ }
+}
+
+void pl111_gem_sync_to_cpu(struct pl111_gem_bo *bo, int pgoff)
+{
+ struct drm_device *dev = bo->gem_object.dev;
+
+ /*
+ * TODO MIDEGL-1808
+ *
+ * The following check was meant to detect if the CPU is trying to access
+ * a buffer that is currently mapped for DMA accesses, which is illegal
+ * as described by the DMA-API.
+ *
+ * However, some of our tests are trying to do that, which triggers the message
+ * below and avoids dma-unmapping the pages not to annoy the DMA device but that
+ * leads to the test failing because of caches not being properly flushed.
+ */
+
+ /*
+ if (bo->sgt) {
+ DRM_ERROR("%s: the CPU is trying to access a dma-mapped buffer\n", __func__);
+ return;
+ }
+ */
+
+ if (!(bo->type & PL111_BOT_DMA) && (bo->type & PL111_BOT_CACHED) &&
+ bo->backing_data.shm.dma_addrs[pgoff]) {
+ DRM_DEBUG("%s: unmap bo=%p (s=%d), paddr=%08x\n",
+ __func__, bo, bo->gem_object.size,
+ bo->backing_data.shm.dma_addrs[pgoff]);
+ dma_unmap_page(dev->dev, bo->backing_data.shm.dma_addrs[pgoff],
+ PAGE_SIZE, DMA_BIDIRECTIONAL);
+ bo->backing_data.shm.dma_addrs[pgoff] = 0;
+ }
+}
+
+/* Based on omapdrm driver */
+int pl111_bo_mmap(struct drm_gem_object *obj, struct pl111_gem_bo *bo,
+ struct vm_area_struct *vma, size_t size)
+{
+ DRM_DEBUG("DRM %s on drm_gem_object=%p, pl111_gem_bo=%p type=%08x\n",
+ __func__, obj, bo, bo->type);
+
+ vma->vm_flags &= ~VM_PFNMAP;
+ vma->vm_flags |= VM_MIXEDMAP;
+
+ if (bo->type & PL111_BOT_WC) {
+ vma->vm_page_prot =
+ pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+ } else if (bo->type & PL111_BOT_CACHED) {
+ /*
+ * Objects that do not have a filp (DMA backed) can't be
+ * mapped as cached now. Write-combine should be enough.
+ */
+ if (WARN_ON(!obj->filp))
+ return -EINVAL;
+
+ /*
+ * As explained in Documentation/dma-buf-sharing.txt
+ * we need this trick so that we can manually zap ptes
+ * in order to fake coherency.
+ */
+ fput(vma->vm_file);
+ vma->vm_pgoff = 0;
+ get_file(obj->filp);
+ vma->vm_file = obj->filp;
+
+ vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+ } else { /* PL111_BOT_UNCACHED */
+ vma->vm_page_prot =
+ pgprot_noncached(vm_get_page_prot(vma->vm_flags));
+ }
+ return 0;
+}
+
+int pl111_gem_mmap(struct file *file_priv, struct vm_area_struct *vma)
+{
+ int ret;
+ struct drm_file *priv = file_priv->private_data;
+ struct drm_device *dev = priv->minor->dev;
+ struct drm_gem_mm *mm = dev->mm_private;
+ struct drm_local_map *map = NULL;
+ struct drm_hash_item *hash;
+ struct drm_gem_object *obj;
+ struct pl111_gem_bo *bo;
+
+ DRM_DEBUG_KMS("DRM %s\n", __func__);
+
+ drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash);
+ map = drm_hash_entry(hash, struct drm_map_list, hash)->map;
+ obj = map->handle;
+ bo = PL111_BO_FROM_GEM(obj);
+
+ DRM_DEBUG_KMS("DRM %s on pl111_gem_bo %p bo->type 0x%08x\n", __func__, bo, bo->type);
+
+ /* for an imported buffer we let the exporter handle the mmap */
+ if (obj->import_attach)
+ return dma_buf_mmap(obj->import_attach->dmabuf, vma, 0);
+
+ ret = drm_gem_mmap(file_priv, vma);
+ if (ret < 0) {
+ DRM_ERROR("failed to mmap\n");
+ return ret;
+ }
+
+ return pl111_bo_mmap(obj, bo, vma, vma->vm_end - vma->vm_start);
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_pl111.c b/drivers/gpu/drm/pl111/pl111_drm_pl111.c
new file mode 100755
index 000000000000..0b3c6813b36c
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_pl111.c
@@ -0,0 +1,417 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_pl111.c
+ * PL111 specific functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "pl111_drm.h"
+
+/* This can't be called from IRQ context, due to clk_get() and board->enable */
+static int clcd_enable(struct drm_framebuffer *fb)
+{
+ __u32 cntl;
+ struct clcd_board *board;
+
+ pr_info("DRM %s\n", __func__);
+
+ clk_prepare_enable(priv.clk);
+
+ /* Enable and Power Up */
+ cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDPWR | CNTL_LCDVCOMP(1);
+ DRM_DEBUG_KMS("fb->bits_per_pixel = %d\n", fb->bits_per_pixel);
+ if (fb->bits_per_pixel == 16)
+ cntl |= CNTL_LCDBPP16_565;
+ else if (fb->bits_per_pixel == 32 && fb->depth == 24)
+ cntl |= CNTL_LCDBPP24;
+ else
+ BUG_ON(1);
+
+ cntl |= CNTL_BGR;
+
+ writel(cntl, priv.regs + CLCD_PL111_CNTL);
+
+ if (priv.amba_dev->dev.platform_data) {
+ board = priv.amba_dev->dev.platform_data;
+
+ if (board->enable)
+ board->enable(NULL);
+ }
+
+ /* Enable Interrupts */
+ writel(CLCD_IRQ_NEXTBASE_UPDATE, priv.regs + CLCD_PL111_IENB);
+
+ return 0;
+}
+
+int clcd_disable(struct drm_crtc *crtc)
+{
+ struct clcd_board *board;
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc);
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ unsigned long flags;
+#endif
+
+ pr_info("DRM %s\n", __func__);
+
+ /* Disable Interrupts */
+ writel(0x00000000, priv.regs + CLCD_PL111_IENB);
+
+ if (priv.amba_dev->dev.platform_data) {
+ board = priv.amba_dev->dev.platform_data;
+
+ if (board->disable)
+ board->disable(NULL);
+ }
+
+ /* Disable and Power Down */
+ writel(0, priv.regs + CLCD_PL111_CNTL);
+
+ /* Disable clock */
+ clk_disable_unprepare(priv.clk);
+
+ pl111_crtc->last_bpp = 0;
+#ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS
+ spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags);
+ /* Release the previous buffers */
+ if (pl111_crtc->old_kds_res_set != NULL)
+ kds_resource_set_release(&pl111_crtc->old_kds_res_set);
+
+ pl111_crtc->old_kds_res_set = NULL;
+ spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags);
+#endif
+ return 0;
+}
+
+/*
+ * To avoid a possible race where "pl111_crtc->current_update_res" has
+ * been updated (non NULL) but the corresponding scanout buffer has not been
+ * written to the base registers we must always call this function holding
+ * the "base_update_lock" spinlock with IRQs disabled (spin_lock_irqsave()).
+ */
+void do_flip_to_res(struct pl111_drm_flip_resource *flip_res)
+{
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc);
+ struct drm_framebuffer *fb;
+ struct pl111_gem_bo *bo;
+ size_t min_size;
+ fb = flip_res->fb;
+ bo = PL111_BO_FROM_FRAMEBUFFER(fb);
+
+
+
+ min_size = (fb->height - 1) * fb->pitches[0]
+ + fb->width * (fb->bits_per_pixel >> 3);
+
+ BUG_ON(bo->gem_object.size < min_size);
+
+ /* Don't even attempt PL111_BOT_SHM, it's not contiguous */
+ BUG_ON(bo->type != PL111_BOT_DMA);
+
+ /*
+ * Note the buffer for releasing after IRQ, and don't allow any more
+ * updates until then.
+ *
+ * This clcd controller latches the new address on next vsync. Address
+ * latching is indicated by CLCD_IRQ_NEXTBASE_UPDATE, and so we must
+ * wait for that before releasing the previous buffer's kds
+ * resources. Otherwise, we'll allow writers to write to the old buffer
+ * whilst it is still being displayed
+ */
+ pl111_crtc->current_update_res = flip_res;
+
+ DRM_DEBUG_KMS("Displaying fb 0x%p, dumb_bo 0x%p, physaddr %.8x\n",
+ fb, bo, bo->backing_data.dma.fb_dev_addr);
+
+ if (drm_vblank_get(pl111_crtc->crtc.dev, pl111_crtc->crtc_index) < 0)
+ DRM_ERROR("Could not get vblank reference for crtc %d\n",
+ pl111_crtc->crtc_index);
+
+ /* Set the scanout buffer */
+ writel(bo->backing_data.dma.fb_dev_addr, priv.regs + CLCD_UBAS);
+ writel(bo->backing_data.dma.fb_dev_addr +
+ ((fb->height - 1) * fb->pitches[0]), priv.regs + CLCD_LBAS);
+}
+
+void
+show_framebuffer_on_crtc_cb_internal(struct pl111_drm_flip_resource *flip_res,
+ struct drm_framebuffer *fb)
+{
+ unsigned long irq_flags;
+ struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc);
+
+ spin_lock_irqsave(&pl111_crtc->base_update_lock, irq_flags);
+ if (list_empty(&pl111_crtc->update_queue) &&
+ !pl111_crtc->current_update_res) {
+ do_flip_to_res(flip_res);
+ } else {
+ /*
+ * Enqueue the update to occur on a future IRQ
+ * This only happens on triple-or-greater buffering
+ */
+ DRM_DEBUG_KMS("Deferring 3+ buffered flip to fb %p to IRQ\n",
+ fb);
+ list_add_tail(&flip_res->link, &pl111_crtc->update_queue);
+ }
+ spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags);
+
+ if (!flip_res->page_flip && (pl111_crtc->last_bpp == 0 ||
+ pl111_crtc->last_bpp != fb->bits_per_pixel ||
+ !drm_mode_equal(pl111_crtc->new_mode,
+ pl111_crtc->current_mode))) {
+ struct clcd_regs timing;
+
+ pl111_convert_drm_mode_to_timing(pl111_crtc->new_mode, &timing);
+
+ DRM_DEBUG_KMS("Set timing: %08X:%08X:%08X:%08X clk=%ldHz\n",
+ timing.tim0, timing.tim1, timing.tim2,
+ timing.tim3, timing.pixclock);
+
+ /* This is the actual mode setting part */
+ clk_set_rate(priv.clk, timing.pixclock);
+
+ writel(timing.tim0, priv.regs + CLCD_TIM0);
+ writel(timing.tim1, priv.regs + CLCD_TIM1);
+ writel(timing.tim2, priv.regs + CLCD_TIM2);
+ writel(timing.tim3, priv.regs + CLCD_TIM3);
+
+ clcd_enable(fb);
+ pl111_crtc->last_bpp = fb->bits_per_pixel;
+ }
+
+ if (!flip_res->page_flip) {
+ drm_mode_destroy(flip_res->crtc->dev, pl111_crtc->current_mode);
+ pl111_crtc->current_mode = pl111_crtc->new_mode;
+ pl111_crtc->new_mode = NULL;
+ }
+
+ BUG_ON(!pl111_crtc->current_mode);
+
+ /*
+ * If IRQs weren't enabled before, they are now. This will eventually
+ * cause flip_res to be released via pl111_common_irq, which updates
+ * every time the Base Address is latched (i.e. every frame, regardless
+ * of whether we update the base address or not)
+ */
+}
+
+irqreturn_t pl111_irq(int irq, void *data)
+{
+ u32 irq_stat;
+ struct pl111_drm_crtc *pl111_crtc = priv.pl111_crtc;
+
+ irq_stat = readl(priv.regs + CLCD_PL111_MIS);
+
+ if (!irq_stat)
+ return IRQ_NONE;
+
+ if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE)
+ pl111_common_irq(pl111_crtc);
+
+ /* Clear the interrupt once done */
+ writel(irq_stat, priv.regs + CLCD_PL111_ICR);
+
+ return IRQ_HANDLED;
+}
+
+int pl111_device_init(struct drm_device *dev)
+{
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ int ret;
+
+ if (priv == NULL) {
+ pr_err("%s no private data\n", __func__);
+ return -EINVAL;
+ }
+
+ if (priv->amba_dev == NULL) {
+ pr_err("%s no amba device found\n", __func__);
+ return -EINVAL;
+ }
+
+ /* set up MMIO for register access */
+ priv->mmio_start = priv->amba_dev->res.start;
+ priv->mmio_len = resource_size(&priv->amba_dev->res);
+
+ DRM_DEBUG_KMS("mmio_start=%lu, mmio_len=%u\n", priv->mmio_start,
+ priv->mmio_len);
+
+ priv->regs = ioremap(priv->mmio_start, priv->mmio_len);
+ if (priv->regs == NULL) {
+ pr_err("%s failed mmio\n", __func__);
+ return -EINVAL;
+ }
+
+ /* turn off interrupts */
+ writel(0, priv->regs + CLCD_PL111_IENB);
+
+ ret = request_irq(priv->amba_dev->irq[0], pl111_irq, 0,
+ "pl111_irq_handler", NULL);
+ if (ret != 0) {
+ pr_err("%s failed %d\n", __func__, ret);
+ goto out_mmio;
+ }
+
+ goto finish;
+
+out_mmio:
+ iounmap(priv->regs);
+finish:
+ DRM_DEBUG_KMS("pl111_device_init returned %d\n", ret);
+ return ret;
+}
+
+void pl111_device_fini(struct drm_device *dev)
+{
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ u32 cntl;
+
+ if (priv == NULL || priv->regs == NULL)
+ return;
+
+ free_irq(priv->amba_dev->irq[0], NULL);
+
+ cntl = readl(priv->regs + CLCD_PL111_CNTL);
+
+ cntl &= ~CNTL_LCDEN;
+ writel(cntl, priv->regs + CLCD_PL111_CNTL);
+
+ cntl &= ~CNTL_LCDPWR;
+ writel(cntl, priv->regs + CLCD_PL111_CNTL);
+
+ iounmap(priv->regs);
+}
+
+int pl111_amba_probe(struct amba_device *dev, const struct amba_id *id)
+{
+ struct clcd_board *board = dev->dev.platform_data;
+ int ret;
+ pr_info("DRM %s\n", __func__);
+
+ if (!board)
+ dev_warn(&dev->dev, "board data not available\n");
+
+ ret = amba_request_regions(dev, NULL);
+ if (ret != 0) {
+ DRM_ERROR("CLCD: unable to reserve regs region\n");
+ goto out;
+ }
+
+ priv.amba_dev = dev;
+
+ priv.clk = clk_get(&priv.amba_dev->dev, NULL);
+ if (IS_ERR(priv.clk)) {
+ DRM_ERROR("CLCD: unable to get clk.\n");
+ ret = PTR_ERR(priv.clk);
+ goto clk_err;
+ }
+
+ return 0;
+
+clk_err:
+ amba_release_regions(dev);
+out:
+ return ret;
+}
+
+int pl111_amba_remove(struct amba_device *dev)
+{
+ DRM_DEBUG_KMS("DRM %s\n", __func__);
+
+ clk_put(priv.clk);
+
+ amba_release_regions(dev);
+
+ priv.amba_dev = NULL;
+
+ return 0;
+}
+
+void pl111_convert_drm_mode_to_timing(struct drm_display_mode *mode,
+ struct clcd_regs *timing)
+{
+ unsigned int ppl, hsw, hfp, hbp;
+ unsigned int lpp, vsw, vfp, vbp;
+ unsigned int cpl;
+
+ memset(timing, 0, sizeof(struct clcd_regs));
+
+ ppl = (mode->hdisplay / 16) - 1;
+ hsw = mode->hsync_end - mode->hsync_start - 1;
+ hfp = mode->hsync_start - mode->hdisplay - 1;
+ hbp = mode->htotal - mode->hsync_end - 1;
+
+ lpp = mode->vdisplay - 1;
+ vsw = mode->vsync_end - mode->vsync_start - 1;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vbp = mode->vtotal - mode->vsync_end;
+
+ cpl = mode->hdisplay - 1;
+
+ timing->tim0 = (ppl << 2) | (hsw << 8) | (hfp << 16) | (hbp << 24);
+ timing->tim1 = lpp | (vsw << 10) | (vfp << 16) | (vbp << 24);
+ timing->tim2 = TIM2_IVS | TIM2_IHS | TIM2_IPC | TIM2_BCD | (cpl << 16);
+ timing->tim3 = 0;
+
+ timing->pixclock = mode->clock * 1000;
+}
+
+void pl111_convert_timing_to_drm_mode(struct clcd_regs *timing,
+ struct drm_display_mode *mode)
+{
+ unsigned int ppl, hsw, hfp, hbp;
+ unsigned int lpp, vsw, vfp, vbp;
+
+ ppl = (timing->tim0 >> 2) & 0x3f;
+ hsw = (timing->tim0 >> 8) & 0xff;
+ hfp = (timing->tim0 >> 16) & 0xff;
+ hbp = (timing->tim0 >> 24) & 0xff;
+
+ lpp = timing->tim1 & 0x3ff;
+ vsw = (timing->tim1 >> 10) & 0x3f;
+ vfp = (timing->tim1 >> 16) & 0xff;
+ vbp = (timing->tim1 >> 24) & 0xff;
+
+ mode->hdisplay = (ppl + 1) * 16;
+ mode->hsync_start = ((ppl + 1) * 16) + hfp + 1;
+ mode->hsync_end = ((ppl + 1) * 16) + hfp + hsw + 2;
+ mode->htotal = ((ppl + 1) * 16) + hfp + hsw + hbp + 3;
+ mode->hskew = 0;
+
+ mode->vdisplay = lpp + 1;
+ mode->vsync_start = lpp + vfp + 1;
+ mode->vsync_end = lpp + vfp + vsw + 2;
+ mode->vtotal = lpp + vfp + vsw + vbp + 2;
+
+ mode->flags = 0;
+
+ mode->width_mm = 0;
+ mode->height_mm = 0;
+
+ mode->clock = timing->pixclock / 1000;
+ mode->hsync = timing->pixclock / mode->htotal;
+ mode->vrefresh = mode->hsync / mode->vtotal;
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_platform.c b/drivers/gpu/drm/pl111/pl111_drm_platform.c
new file mode 100755
index 000000000000..fe7db7392792
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_platform.c
@@ -0,0 +1,151 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_platform.c
+ * Implementation of the Linux platform device entrypoints for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "pl111_drm.h"
+
+static int pl111_platform_drm_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ pr_info("DRM %s\n", __func__);
+ return 0;
+}
+
+static int pl111_platform_drm_resume(struct platform_device *dev)
+{
+ pr_info("DRM %s\n", __func__);
+ return 0;
+}
+
+int pl111_platform_drm_probe(struct platform_device *dev)
+{
+ pr_info("DRM %s\n", __func__);
+ return pl111_drm_init(dev);
+}
+
+static int pl111_platform_drm_remove(struct platform_device *dev)
+{
+ pr_info("DRM %s\n", __func__);
+ pl111_drm_exit(dev);
+
+ return 0;
+}
+
+static struct amba_id pl111_id_table[] = {
+ {
+ .id = 0x00041110,
+ .mask = 0x000ffffe,
+ },
+ {0, 0},
+};
+
+static struct amba_driver pl111_amba_driver = {
+ .drv = {
+ .name = "clcd-pl11x",
+ },
+ .probe = pl111_amba_probe,
+ .remove = pl111_amba_remove,
+ .id_table = pl111_id_table,
+};
+
+static struct platform_driver platform_drm_driver = {
+ .probe = pl111_platform_drm_probe,
+ .remove = pl111_platform_drm_remove,
+ .suspend = pl111_platform_drm_suspend,
+ .resume = pl111_platform_drm_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRIVER_NAME,
+ },
+};
+
+static const struct platform_device_info pl111_drm_pdevinfo = {
+ .name = DRIVER_NAME,
+ .id = -1,
+ .dma_mask = ~0UL
+};
+
+static struct platform_device *pl111_drm_device;
+
+static int __init pl111_platform_drm_init(void)
+{
+ int ret;
+
+ pr_info("DRM %s\n", __func__);
+
+ pl111_drm_device = platform_device_register_full(&pl111_drm_pdevinfo);
+ if (pl111_drm_device == NULL) {
+ pr_err("DRM platform_device_register_full() failed\n");
+ return -ENOMEM;
+ }
+
+ ret = amba_driver_register(&pl111_amba_driver);
+ if (ret != 0) {
+ pr_err("DRM amba_driver_register() failed %d\n", ret);
+ goto err_amba_reg;
+ }
+
+ ret = platform_driver_register(&platform_drm_driver);
+ if (ret != 0) {
+ pr_err("DRM platform_driver_register() failed %d\n", ret);
+ goto err_pdrv_reg;
+ }
+
+ return 0;
+
+err_pdrv_reg:
+ amba_driver_unregister(&pl111_amba_driver);
+err_amba_reg:
+ platform_device_unregister(pl111_drm_device);
+
+ return ret;
+}
+
+static void __exit pl111_platform_drm_exit(void)
+{
+ pr_info("DRM %s\n", __func__);
+
+ platform_device_unregister(pl111_drm_device);
+ amba_driver_unregister(&pl111_amba_driver);
+ platform_driver_unregister(&platform_drm_driver);
+}
+
+#ifdef MODULE
+module_init(pl111_platform_drm_init);
+#else
+late_initcall(pl111_platform_drm_init);
+#endif
+module_exit(pl111_platform_drm_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_LICENSE(DRIVER_LICENCE);
+MODULE_ALIAS(DRIVER_ALIAS);
diff --git a/drivers/gpu/drm/pl111/pl111_drm_suspend.c b/drivers/gpu/drm/pl111/pl111_drm_suspend.c
new file mode 100755
index 000000000000..a61636f8424f
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_suspend.c
@@ -0,0 +1,43 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_suspend.c
+ * Implementation of the suspend/resume functions for PL111 DRM
+ */
+
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "pl111_drm.h"
+
+int pl111_drm_suspend(struct drm_device *dev, pm_message_t state)
+{
+ pr_info("DRM %s\n", __func__);
+ return 0;
+}
+
+int pl111_drm_resume(struct drm_device *dev)
+{
+ pr_info("DRM %s\n", __func__);
+ return 0;
+}
diff --git a/drivers/gpu/drm/pl111/pl111_drm_vma.c b/drivers/gpu/drm/pl111/pl111_drm_vma.c
new file mode 100755
index 000000000000..c9918f79e939
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_drm_vma.c
@@ -0,0 +1,307 @@
+/*
+ *
+ * (C) COPYRIGHT ARM Limited. All rights reserved.
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ *
+ * A copy of the licence is included with the program, and can also be obtained
+ * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+
+/**
+ * pl111_drm_vma.c
+ * Implementation of the VM functions for PL111 DRM
+ */
+#include <linux/amba/bus.h>
+#include <linux/amba/clcd.h>
+#include <linux/version.h>
+#include <linux/shmem_fs.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "pl111_drm.h"
+
+/* BEGIN drivers/staging/omapdrm/omap_gem_helpers.c */
+/**
+ * drm_gem_put_pages - helper to free backing pages for a GEM object
+ * @obj: obj in question
+ * @pages: pages to free
+ */
+static void _drm_gem_put_pages(struct drm_gem_object *obj, struct page **pages,
+ bool dirty, bool accessed)
+{
+ int i, npages;
+ struct pl111_gem_bo *bo;
+ npages = obj->size >> PAGE_SHIFT;
+ bo = PL111_BO_FROM_GEM(obj);
+ for (i = 0; i < npages; i++) {
+ if (dirty)
+ set_page_dirty(pages[i]);
+ if (accessed)
+ mark_page_accessed(pages[i]);
+ /* Undo the reference we took when populating the table */
+ page_cache_release(pages[i]);
+ }
+ drm_free_large(pages);
+}
+
+void put_pages(struct drm_gem_object *obj, struct page **pages)
+{
+ int i, npages;
+ struct pl111_gem_bo *bo;
+ npages = obj->size >> PAGE_SHIFT;
+ bo = PL111_BO_FROM_GEM(obj);
+ _drm_gem_put_pages(obj, pages, true, true);
+ if (bo->backing_data.shm.dma_addrs) {
+ for (i = 0; i < npages; i++) {
+ /* Filter pages unmapped because of CPU accesses */
+ if (!bo->backing_data.shm.dma_addrs[i])
+ continue;
+ if (!dma_mapping_error(obj->dev->dev,
+ bo->backing_data.shm.dma_addrs[i])) {
+ dma_unmap_page(obj->dev->dev,
+ bo->backing_data.shm.dma_addrs[i],
+ PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+ }
+ }
+ kfree(bo->backing_data.shm.dma_addrs);
+ bo->backing_data.shm.dma_addrs = NULL;
+ }
+}
+
+/**
+ * drm_gem_get_pages - helper to allocate backing pages for a GEM object
+ * @obj: obj in question
+ * @gfpmask: gfp mask of requested pages
+ */
+static struct page **_drm_gem_get_pages(struct drm_gem_object *obj,
+ gfp_t gfpmask)
+{
+ struct inode *inode;
+ struct address_space *mapping;
+ struct page *p, **pages;
+ int i, npages;
+
+ /* This is the shared memory object that backs the GEM resource */
+ inode = obj->filp->f_path.dentry->d_inode;
+ mapping = inode->i_mapping;
+
+ npages = obj->size >> PAGE_SHIFT;
+
+ pages = drm_malloc_ab(npages, sizeof(struct page *));
+ if (pages == NULL)
+ return ERR_PTR(-ENOMEM);
+
+ gfpmask |= mapping_gfp_mask(mapping);
+
+ for (i = 0; i < npages; i++) {
+ p = shmem_read_mapping_page_gfp(mapping, i, gfpmask);
+ if (IS_ERR(p))
+ goto fail;
+ pages[i] = p;
+
+ /*
+ * There is a hypothetical issue w/ drivers that require
+ * buffer memory in the low 4GB.. if the pages are un-
+ * pinned, and swapped out, they can end up swapped back
+ * in above 4GB. If pages are already in memory, then
+ * shmem_read_mapping_page_gfp will ignore the gfpmask,
+ * even if the already in-memory page disobeys the mask.
+ *
+ * It is only a theoretical issue today, because none of
+ * the devices with this limitation can be populated with
+ * enough memory to trigger the issue. But this BUG_ON()
+ * is here as a reminder in case the problem with
+ * shmem_read_mapping_page_gfp() isn't solved by the time
+ * it does become a real issue.
+ *
+ * See this thread: http://lkml.org/lkml/2011/7/11/238
+ */
+ BUG_ON((gfpmask & __GFP_DMA32) &&
+ (page_to_pfn(p) >= 0x00100000UL));
+ }
+
+ return pages;
+
+fail:
+ while (i--)
+ page_cache_release(pages[i]);
+
+ drm_free_large(pages);
+ return ERR_PTR(PTR_ERR(p));
+}
+
+struct page **get_pages(struct drm_gem_object *obj)
+{
+ struct pl111_gem_bo *bo;
+ bo = PL111_BO_FROM_GEM(obj);
+
+ if (bo->backing_data.shm.pages == NULL) {
+ struct page **p;
+ int npages = obj->size >> PAGE_SHIFT;
+ int i;
+
+ p = _drm_gem_get_pages(obj, GFP_KERNEL);
+ if (IS_ERR(p))
+ return ERR_PTR(-ENOMEM);
+
+ bo->backing_data.shm.pages = p;
+
+ if (bo->backing_data.shm.dma_addrs == NULL) {
+ bo->backing_data.shm.dma_addrs =
+ kzalloc(npages * sizeof(dma_addr_t),
+ GFP_KERNEL);
+ if (bo->backing_data.shm.dma_addrs == NULL)
+ goto error_out;
+ }
+
+ if (!(bo->type & PL111_BOT_CACHED)) {
+ for (i = 0; i < npages; ++i) {
+ bo->backing_data.shm.dma_addrs[i] =
+ dma_map_page(obj->dev->dev, p[i], 0, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+ if (dma_mapping_error(obj->dev->dev,
+ bo->backing_data.shm.dma_addrs[i]))
+ goto error_out;
+ }
+ }
+ }
+
+ return bo->backing_data.shm.pages;
+
+error_out:
+ put_pages(obj, bo->backing_data.shm.pages);
+ bo->backing_data.shm.pages = NULL;
+ return ERR_PTR(-ENOMEM);
+}
+
+/* END drivers/staging/omapdrm/omap_gem_helpers.c */
+int pl111_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct page **pages;
+ pgoff_t pgoff;
+ unsigned long pfn;
+ struct drm_gem_object *obj = vma->vm_private_data;
+ struct pl111_gem_bo *bo = PL111_BO_FROM_GEM(obj);
+ struct drm_device *dev = obj->dev;
+ int ret;
+
+ mutex_lock(&dev->struct_mutex);
+
+ /* We don't use vmf->pgoff since that has the fake offset: */
+ pgoff = ((unsigned long)vmf->virtual_address -
+ vma->vm_start) >> PAGE_SHIFT;
+
+ if (bo->type & PL111_BOT_DMA) {
+ pfn = (bo->backing_data.dma.fb_dev_addr >> PAGE_SHIFT) + pgoff;
+ } else { /* PL111_BOT_SHM */
+ pages = get_pages(obj);
+ if (IS_ERR(pages)) {
+ dev_err(obj->dev->dev,
+ "could not get pages: %ld\n", PTR_ERR(pages));
+ ret = PTR_ERR(pages);
+ goto error;
+ }
+ pfn = page_to_pfn(pages[pgoff]);
+ pl111_gem_sync_to_cpu(bo, pgoff);
+ }
+
+ DRM_DEBUG("bo=%p physaddr=0x%.8x for offset 0x%x\n",
+ bo, PFN_PHYS(pfn), PFN_PHYS(pgoff));
+
+ ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn);
+
+error:
+ mutex_unlock(&dev->struct_mutex);
+
+ switch (ret) {
+ case 0:
+ case -ERESTARTSYS:
+ case -EINTR:
+ case -EBUSY:
+ return VM_FAULT_NOPAGE;
+ case -ENOMEM:
+ return VM_FAULT_OOM;
+ default:
+ return VM_FAULT_SIGBUS;
+ }
+}
+
+/*
+ * The core drm_vm_ functions in kernel 3.4 are not ready
+ * to handle dma_buf cases where vma->vm_file->private_data
+ * cannot be accessed to get the device.
+ *
+ * We use these functions from 3.5 instead where the device
+ * pointer is passed explicitly.
+ *
+ * However they aren't exported from the kernel until 3.10
+ */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
+void pl111_drm_vm_open_locked(struct drm_device *dev,
+ struct vm_area_struct *vma)
+{
+ struct drm_vma_entry *vma_entry;
+
+ DRM_DEBUG("0x%08lx,0x%08lx\n",
+ vma->vm_start, vma->vm_end - vma->vm_start);
+ atomic_inc(&dev->vma_count);
+
+ vma_entry = kmalloc(sizeof(*vma_entry), GFP_KERNEL);
+ if (vma_entry) {
+ vma_entry->vma = vma;
+ vma_entry->pid = current->pid;
+ list_add(&vma_entry->head, &dev->vmalist);
+ }
+}
+
+void pl111_drm_vm_close_locked(struct drm_device *dev,
+ struct vm_area_struct *vma)
+{
+ struct drm_vma_entry *pt, *temp;
+
+ DRM_DEBUG("0x%08lx,0x%08lx\n",
+ vma->vm_start, vma->vm_end - vma->vm_start);
+ atomic_dec(&dev->vma_count);
+
+ list_for_each_entry_safe(pt, temp, &dev->vmalist, head) {
+ if (pt->vma == vma) {
+ list_del(&pt->head);
+ kfree(pt);
+ break;
+ }
+ }
+}
+
+void pl111_gem_vm_open(struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj = vma->vm_private_data;
+
+ drm_gem_object_reference(obj);
+
+ mutex_lock(&obj->dev->struct_mutex);
+ pl111_drm_vm_open_locked(obj->dev, vma);
+ mutex_unlock(&obj->dev->struct_mutex);
+}
+
+void pl111_gem_vm_close(struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj = vma->vm_private_data;
+ struct drm_device *dev = obj->dev;
+
+ mutex_lock(&dev->struct_mutex);
+ pl111_drm_vm_close_locked(obj->dev, vma);
+ drm_gem_object_unreference(obj);
+ mutex_unlock(&dev->struct_mutex);
+}
+#endif