diff options
author | Jon Medhurst <tixy@linaro.org> | 2015-08-04 16:13:53 +0100 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2015-08-04 16:13:53 +0100 |
commit | b08ba4cdea50ace8a6752f4618b4c1270cc5f73e (patch) | |
tree | eb0b79cd71c6d54b671fa2e781a467e6ad3cbcbf | |
parent | ab9e40722736523fc05ecc566f918fdfd8503d92 (diff) | |
parent | 776180c795b58cb149da4f73b178ac205f1d725c (diff) |
Merge branch 'lsk-3.10-armlt-drm-hdlcd' into integration-lsk-3.10-juno-android
Conflicts:
arch/arm64/boot/dts/juno.dts
linaro/configs/vexpress64.conf
26 files changed, 2842 insertions, 49 deletions
diff --git a/Documentation/DocBook/drm.tmpl b/Documentation/DocBook/drm.tmpl index f9df3b872c16..69f7a4e0b52b 100644 --- a/Documentation/DocBook/drm.tmpl +++ b/Documentation/DocBook/drm.tmpl @@ -1162,7 +1162,7 @@ int max_width, max_height;</synopsis> </para> <para> If a page flip can be successfully scheduled the driver must set the - <code>drm_crtc-<fb</code> field to the new framebuffer pointed to + <code>drm_crtc->fb</code> field to the new framebuffer pointed to by <code>fb</code>. This is important so that the reference counting on framebuffers stays balanced. </para> diff --git a/Documentation/devicetree/bindings/gpu/arm,hdlcd.txt b/Documentation/devicetree/bindings/gpu/arm,hdlcd.txt new file mode 100644 index 000000000000..e4af93b25ee0 --- /dev/null +++ b/Documentation/devicetree/bindings/gpu/arm,hdlcd.txt @@ -0,0 +1,64 @@ +ARM HDLCD + +This is a driver for a display controller found on several development +platforms produced by ARM Ltd and in more modern of its' Fast Models. +The HDLCD is an RGB streamer that reads the data from a framebuffer +and sends it to a single HDMI transmitter. + +Required properties: +- compatible: "arm,hdlcd" +- reg: Physical base address and length of the controller's registers. +- interrupts: One interrupt used by the controller to multiplex all + the sources programmed in the interrupt mask register. +- clocks: A list of phandle + clock-specifier pairs, one for each entry + in clock-names. +- clock-names: A list of clock names. For HDLD it should containt: + * "pxlclk" for the clock feeding the output PLL of the controller. + +Optional properties: +- i2c-slave: If the HDLCD is connected to an HDMI transmitter that is + reacheable via i2c, then use the phandle of the DDC slave here. + +Optional children: +- display-timings: If the HDLCD is emulated by the Fast Model, place the + videomode(s) of the desired resolution(s) here. If multiple videomodes + are being used, a 'native-mode' property should be used to indicate the + preferred/default resolution. Refer to + Documentation/devicetree/bindings/video/display-timing.txt for details. + + +Example: + +/ { + ... + + hdlcd@2b000000 { + compatible = "arm,hdlcd"; + reg = <0 0x2b000000 0 0x1000>; + interrupts = <0 85 4>; + i2c-slave = <&dvi_i2c_slave>; + clocks = <&oscclk5>; + clock-names = "pxlclk"; + }; + + /* DVI I2C bus */ + v2m_i2c_dvi: i2c@160000 { + compatible = "arm,versatile-i2c"; + reg = <0x160000 0x1000>; + + #address-cells = <1>; + #size-cells = <0>; + + dvi_i2c_slave: dvi-transmitter@39 { + compatible = "sil,sii9022-tpi", "sil,sii9022"; + reg = <0x39>; + }; + + dvi-transmitter@60 { + compatible = "sil,sii9022-cpi", "sil,sii9022"; + reg = <0x60>; + }; + }; + + ... +}; diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index 9584232ee6b6..314e0d9b0002 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -201,7 +201,7 @@ #address-cells = <1>; #size-cells = <0>; - dvi-transmitter@39 { + dvi_i2c_slave: dvi-transmitter@39 { compatible = "sil,sii9022-tpi", "sil,sii9022"; reg = <0x39>; }; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index f1dc620c5c45..a6650860af23 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -147,6 +147,7 @@ interrupts = <0 85 4>; mode = "1024x768-16@60"; framebuffer = <0 0xff000000 0 0x01000000>; + i2c-slave = <&dvi_i2c_slave>; clocks = <&oscclk5>; clock-names = "pxlclk"; }; diff --git a/arch/arm64/boot/dts/juno.dts b/arch/arm64/boot/dts/juno.dts index a905e8f887c4..e0f6c5f9bbab 100644 --- a/arch/arm64/boot/dts/juno.dts +++ b/arch/arm64/boot/dts/juno.dts @@ -359,6 +359,56 @@ clock-names = "apb_pclk"; }; + hdlcd@7ff60000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff60000 0 0x1000>; + interrupts = <0 85 4>; + clocks = <&pixel_clk 0>; + clock-names = "pxlclk"; + i2c-slave = <&dvi0>; + + /* display-timings { + native-mode = <&timing0>; + timing0: timing@0 { + /* 1024 x 768 framebufer, standard VGA timings * / + clock-frequency = <65000>; + hactive = <1024>; + vactive = <768>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; */ + }; + + /* hdlcd@7ff50000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff50000 0 0x1000>; + interrupts = <0 93 4>; + clocks = <&pixel_clk 1>; + clock-names = "pxlclk"; + i2c-slave = <&dvi1>; + + display-timings { + native-mode = <&timing1>; + timing1: timing@1 { + /* 1024 x 768 framebufer, standard VGA timings * / + clock-frequency = <65000>; + hactive = <1024>; + vactive = <768>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; + }; */ + gpu: gpu@0x2d000000 { compatible = "arm,malit6xx", "arm,mali"; #cooling-cells = <2>; /* min followed by max */ 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 f2d625c72329..c1e2ee0004c3 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -3,6 +3,9 @@ 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 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/include/drm/drmP.h b/include/drm/drmP.h index 63d17ee9eb48..b836d131bb07 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -1648,6 +1648,8 @@ int drm_gem_private_object_init(struct drm_device *dev, void drm_gem_object_handle_free(struct drm_gem_object *obj); void drm_gem_vm_open(struct vm_area_struct *vma); void drm_gem_vm_close(struct vm_area_struct *vma); +int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size, + struct vm_area_struct *vma); int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); #include <drm/drm_global.h> diff --git a/linaro/configs/vexpress64.conf b/linaro/configs/vexpress64.conf index 985a69edf656..686d6bd5fa6d 100644 --- a/linaro/configs/vexpress64.conf +++ b/linaro/configs/vexpress64.conf @@ -70,3 +70,10 @@ CONFIG_DEVFREQ_GOV_PERFORMANCE=y CONFIG_SCPI_THERMAL=y CONFIG_MALI_DEVFREQ=y CONFIG_THERMAL_WRITABLE_TRIPS=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_DRM_VIRTUAL_HDLCD=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_I2C_DESIGNWARE_PLATFORM=y +CONFIG_DRM_I2C_NXP_TDA998X=y |