drm: Add support for ARM's HDLCD controller.
authorLiviu Dudau <Liviu.Dudau@arm.com>
Thu, 12 Dec 2013 17:23:38 +0000 (17:23 +0000)
committerLiviu Dudau <Liviu.Dudau@arm.com>
Mon, 12 May 2014 12:30:53 +0000 (13:30 +0100)
The HDLCD controller is a display controller that supports resolutions
up to 2048x2048 pixels. It is present on various development boards
produced by ARM Ltd and emulated by the latest Fast Models from the
company.

Signed-off-by: Liviu Dudau <Liviu.Dudau@arm.com>
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/arm/Kconfig [new file with mode: 0644]
drivers/gpu/drm/arm/Makefile [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_crtc.c [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_drv.c [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_drv.h [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_regs.h [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c [new file with mode: 0644]
drivers/gpu/drm/arm/hdlcd_virt_encoder.c [new file with mode: 0644]

index d1cc2f6..ee6f493 100644 (file)
@@ -90,6 +90,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
index 48e38ba..3714f92 100644 (file)
@@ -35,6 +35,7 @@ CFLAGS_drm_trace_points.o := -I$(src)
 obj-$(CONFIG_DRM)      += drm.o
 obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
 obj-$(CONFIG_DRM_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 (file)
index 0000000..8de5b51
--- /dev/null
@@ -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 (file)
index 0000000..0f28e22
--- /dev/null
@@ -0,0 +1,4 @@
+
+hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.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 (file)
index 0000000..91ed56c
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * 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)
+{
+       struct drm_framebuffer *fb = hdlcd->crtc.primary->fb;
+       struct drm_gem_cma_object *gem;
+       unsigned int depth, bpp;
+       dma_addr_t scanout_start;
+
+       drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
+       gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+       scanout_start = gem->paddr + fb->offsets[0] +
+               (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8);
+
+       hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
+}
+
+static int hdlcd_crtc_page_flip(struct drm_crtc *crtc,
+                       struct drm_framebuffer *fb,
+                       struct drm_pending_vblank_event *event,
+                       uint32_t flags)
+{
+       struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+
+       if (hdlcd->dpms == DRM_MODE_DPMS_ON) {
+               /* don't schedule any page flipping if one is in progress */
+               if (hdlcd->event)
+                       return -EBUSY;
+
+               hdlcd->event = event;
+               drm_vblank_get(crtc->dev, 0);
+       }
+
+       crtc->primary->fb = fb;
+
+       if (hdlcd->dpms != DRM_MODE_DPMS_ON) {
+               unsigned long flags;
+
+               /* not active, update registers immediately */
+               hdlcd_set_scanout(hdlcd);
+               spin_lock_irqsave(&crtc->dev->event_lock, flags);
+               if (event)
+                       drm_send_vblank_event(crtc->dev, 0, event);
+               spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+       }
+
+       return 0;
+}
+
+static const struct drm_crtc_funcs hdlcd_crtc_funcs = {
+       .destroy        = hdlcd_crtc_destroy,
+       .set_config     = drm_crtc_helper_set_config,
+       .page_flip      = hdlcd_crtc_page_flip,
+};
+
+static void hdlcd_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+       struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
+
+       hdlcd->dpms = mode;
+       if (mode == DRM_MODE_DPMS_ON)
+               hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1);
+       else
+               hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0);
+}
+
+static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc,
+                       const struct drm_display_mode *mode,
+                       struct drm_display_mode *adjusted_mode)
+{
+       return true;
+}
+
+static void hdlcd_crtc_prepare(struct drm_crtc *crtc)
+{
+       hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
+
+static void hdlcd_crtc_commit(struct drm_crtc *crtc)
+{
+       drm_vblank_post_modeset(crtc->dev, 0);
+       hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static 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
+
+       drm_vblank_pre_modeset(crtc->dev, 0);
+
+       /* Preset the number of bits per colour */
+       drm_fb_get_bpp_depth(crtc->primary->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->primary->fb->width * bpp);
+       hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, crtc->primary->fb->height - 1);
+       hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, crtc->primary->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->crtc_clock * 1000);
+       clk_enable(hdlcd->clk);
+
+       hdlcd_set_scanout(hdlcd);
+
+       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,
+       .load_lut       = hdlcd_crtc_load_lut,
+};
+
+static void hdlcd_fb_output_poll_changed(struct drm_device *dev)
+{
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       if (hdlcd->fbdev)
+               drm_fbdev_cma_hotplug_event(hdlcd->fbdev);
+}
+
+static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = {
+       .fb_create = drm_fb_cma_create,
+       .output_poll_changed = hdlcd_fb_output_poll_changed,
+};
+
+int hdlcd_setup_crtc(struct drm_device *dev)
+{
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       int ret;
+
+       drm_mode_config_init(dev);
+
+       dev->mode_config.min_width = 0;
+       dev->mode_config.min_height = 0;
+       dev->mode_config.max_width = HDLCD_MAX_XRES;
+       dev->mode_config.max_height = HDLCD_MAX_YRES;
+       dev->mode_config.funcs = &hdlcd_mode_config_funcs;
+
+       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 (file)
index 0000000..08341ef
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * 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 <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->fbdev)
+               drm_fbdev_cma_fini(hdlcd->fbdev);
+
+       drm_vblank_cleanup(dev);
+       drm_mode_config_cleanup(dev);
+
+       drm_irq_uninstall(dev);
+
+       if (!IS_ERR(hdlcd->clk))
+               clk_put(hdlcd->clk);
+
+       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
+       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)
+                       ret = hdlcd_create_vexpress_connector(dev, hdlcd);
+
+               if (ret < 0) {
+                       dev_err(dev->dev, "failed to create digital connector\n");
+                       goto fail;
+               }
+       } else {
+               ret = hdlcd_create_virtual_connector(dev);
+               if (ret < 0) {
+                       dev_err(dev->dev, "failed to create virtual connector\n");
+                       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;
+       }
+
+       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");
+       }
+
+       hdlcd->fbdev = drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc,
+                                       dev->mode_config.num_connector);
+       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_fbdev_cma_restore_mode(hdlcd->fbdev);
+}
+
+static irqreturn_t hdlcd_irq(int irq, void *arg)
+{
+       struct drm_device *dev = arg;
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       unsigned long irq_status;
+
+       irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS);
+
+#ifdef CONFIG_DEBUG_FS
+       if (irq_status & HDLCD_INTERRUPT_UNDERRUN) {
+               atomic_inc(&hdlcd->buffer_underrun_count);
+       }
+       if (irq_status & HDLCD_INTERRUPT_DMA_END) {
+               atomic_inc(&hdlcd->dma_end_count);
+       }
+       if (irq_status & HDLCD_INTERRUPT_BUS_ERROR) {
+               atomic_inc(&hdlcd->bus_error_count);
+       }
+       if (irq_status & HDLCD_INTERRUPT_VSYNC) {
+               atomic_inc(&hdlcd->vsync_count);
+       }
+#endif
+       if (irq_status & HDLCD_INTERRUPT_VSYNC) {
+               struct drm_pending_vblank_event *event;
+               unsigned long flags;
+
+               hdlcd_set_scanout(hdlcd);
+
+               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);
+       }
+
+       /* 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)
+{
+#ifdef CONFIG_DEBUG_FS
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+       /* enable debug interrupts */
+       irq_mask |= HDLCD_DEBUG_INT_MASK;
+
+       hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask);
+#endif
+       return 0;
+}
+
+static void hdlcd_irq_uninstall(struct drm_device *dev)
+{
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       /* disable all the interrupts that we might have enabled */
+       unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+#ifdef CONFIG_DEBUG_FS
+       /* disable debug interrupts */
+       irq_mask &= ~HDLCD_DEBUG_INT_MASK;
+#endif
+
+       /* disable vsync interrupts */
+       irq_mask &= ~HDLCD_INTERRUPT_VSYNC;
+
+       hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask);
+}
+
+static int hdlcd_enable_vblank(struct drm_device *dev, int crtc)
+{
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+       hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask | HDLCD_INTERRUPT_VSYNC);
+
+       return 0;
+}
+
+static void hdlcd_disable_vblank(struct drm_device *dev, int crtc)
+{
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+       unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK);
+
+       hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask & ~HDLCD_INTERRUPT_VSYNC);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int hdlcd_show_underrun_count(struct seq_file *m, void *arg)
+{
+       struct drm_info_node *node = (struct drm_info_node *)m->private;
+       struct drm_device *dev = node->minor->dev;
+       struct hdlcd_drm_private *hdlcd = dev->dev_private;
+
+       seq_printf(m, "underrun : %d\n", atomic_read(&hdlcd->buffer_underrun_count));
+       seq_printf(m, "dma_end  : %d\n", atomic_read(&hdlcd->dma_end_count));
+       seq_printf(m, "bus_error: %d\n", atomic_read(&hdlcd->bus_error_count));
+       seq_printf(m, "vsync    : %d\n", atomic_read(&hdlcd->vsync_count));
+       return 0;
+}
+
+static struct drm_info_list hdlcd_debugfs_list[] = {
+       { "interrupt_count", hdlcd_show_underrun_count, 0 },
+};
+
+static int hdlcd_debugfs_init(struct drm_minor *minor)
+{
+       return drm_debugfs_create_files(hdlcd_debugfs_list,
+               ARRAY_SIZE(hdlcd_debugfs_list), minor->debugfs_root, minor);
+}
+
+static void hdlcd_debugfs_cleanup(struct drm_minor *minor)
+{
+       drm_debugfs_remove_files(hdlcd_debugfs_list,
+               ARRAY_SIZE(hdlcd_debugfs_list), minor);
+}
+#endif
+
+static const struct file_operations fops = {
+       .owner          = THIS_MODULE,
+       .open           = drm_open,
+       .release        = drm_release,
+       .unlocked_ioctl = drm_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl   = drm_compat_ioctl,
+#endif
+       .poll           = drm_poll,
+       .read           = drm_read,
+       .llseek         = no_llseek,
+       .mmap           = drm_gem_cma_mmap,
+};
+
+static struct drm_driver hdlcd_driver = {
+       .driver_features        = DRIVER_HAVE_IRQ | DRIVER_GEM |
+                                       DRIVER_MODESET | DRIVER_PRIME,
+       .load                   = hdlcd_load,
+       .unload                 = hdlcd_unload,
+       .preclose               = hdlcd_preclose,
+       .lastclose              = hdlcd_lastclose,
+       .irq_handler            = hdlcd_irq,
+       .irq_preinstall         = hdlcd_irq_preinstall,
+       .irq_postinstall        = hdlcd_irq_postinstall,
+       .irq_uninstall          = hdlcd_irq_uninstall,
+       .get_vblank_counter     = drm_vblank_count,
+       .enable_vblank          = hdlcd_enable_vblank,
+       .disable_vblank         = hdlcd_disable_vblank,
+       .gem_free_object        = drm_gem_cma_free_object,
+       .gem_vm_ops             = &drm_gem_cma_vm_ops,
+       .dumb_create            = drm_gem_cma_dumb_create,
+       .dumb_map_offset        = drm_gem_cma_dumb_map_offset,
+       .dumb_destroy           = drm_gem_dumb_destroy,
+       .prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
+       .gem_prime_export       = drm_gem_prime_export,
+       .gem_prime_import       = drm_gem_prime_import,
+       .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+       .gem_prime_vmap         = drm_gem_cma_prime_vmap,
+       .gem_prime_vunmap       = drm_gem_cma_prime_vunmap,
+       .gem_prime_mmap         = drm_gem_cma_prime_mmap,
+#ifdef CONFIG_DEBUG_FS
+       .debugfs_init           = hdlcd_debugfs_init,
+       .debugfs_cleanup        = hdlcd_debugfs_cleanup,
+#endif
+       .fops                   = &fops,
+       .name                   = "hdlcd",
+       .desc                   = "ARM HDLCD Controller DRM",
+       .date                   = "20130505",
+       .major                  = 1,
+       .minor                  = 0,
+};
+
+
+static int hdlcd_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)
+{
+       int err = platform_driver_register(&hdlcd_platform_driver);
+
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+       if (!err)
+               hdlcd_underrun_init();
+#endif
+
+       return err;
+}
+
+static void __exit hdlcd_exit(void)
+{
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+       hdlcd_underrun_close();
+#endif
+       platform_driver_unregister(&hdlcd_platform_driver);
+}
+
+module_init(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 (file)
index 0000000..408e982
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ *  ARM HDLCD Controller register definition
+ */
+
+#ifndef __HDLCD_DRV_H__
+#define __HDLCD_DRV_H__
+
+struct hdlcd_drm_private {
+       void __iomem                    *mmio;
+       struct clk                      *clk;
+       struct drm_fbdev_cma            *fbdev;
+       struct drm_framebuffer          *fb;
+       struct drm_pending_vblank_event *event;
+       struct drm_crtc                 crtc;
+       struct device_node              *slave_node;
+#ifdef CONFIG_DEBUG_FS
+       atomic_t buffer_underrun_count;
+       atomic_t bus_error_count;
+       atomic_t vsync_count;
+       atomic_t dma_end_count;
+#endif
+       int dpms;
+};
+
+#define crtc_to_hdlcd_priv(x)  container_of(x, struct hdlcd_drm_private, crtc)
+
+static inline void
+hdlcd_write(struct hdlcd_drm_private *hdlcd, unsigned int reg, u32 value)
+{
+       writel(value, hdlcd->mmio + reg);
+}
+
+static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg)
+{
+       return readl(hdlcd->mmio + reg);
+}
+
+/*
+ * Developers using HDLCD may wish to enable these settings if
+ * display disruption is apparent and you suspect HDLCD
+ * access to RAM may be starved.
+ *
+ * Turn HDLCD default color to red instead of default black so
+ * that it's easier to see data underruns (compared to other
+ * visual disruptions)
+ */
+#define HDLCD_SHOW_UNDERRUN
+
+/* setup the crtc subclass */
+int hdlcd_setup_crtc(struct drm_device *dev);
+
+/* functions for creating a suitable connector */
+extern int hdlcd_create_digital_connector(struct drm_device *dev,
+                                       struct hdlcd_drm_private *hdlcd);
+extern int hdlcd_create_vexpress_connector(struct drm_device *dev,
+                                       struct hdlcd_drm_private *hdlcd);
+#ifdef CONFIG_DRM_VIRTUAL_HDLCD
+extern int hdlcd_create_virtual_connector(struct drm_device *dev);
+#else
+static inline int hdlcd_create_virtual_connector(struct drm_device *dev)
+{
+       return -ENXIO;
+}
+#endif
+
+void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd);
+
+/* 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_hdmi_encoder.c b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c
new file mode 100644 (file)
index 0000000..f7964b7
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * 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 <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)
+               return connector_status_unknown;
+
+       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 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,
+};
+
+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);
+       }
+
+       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 (file)
index 0000000..571dad4
--- /dev/null
@@ -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 (file)
index 0000000..e96a623
--- /dev/null
@@ -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 (file)
index 0000000..d9feb06
--- /dev/null
@@ -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;
+};