diff options
author | Jon Medhurst <tixy@linaro.org> | 2015-06-17 18:15:51 +0100 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2015-06-17 18:15:51 +0100 |
commit | 19300940cb930f977d8bebbc554307c9ffbffe6d (patch) | |
tree | 6360f6f96290e851455a55cb4bb162590d316f7f | |
parent | 90a9b65e757129346379e05233e65dfec586d713 (diff) | |
parent | 19e0040168039760bc75e76e82e78b37daa6c1c1 (diff) |
Merge branch 'tracking-armlt-juno' into integration-linaro-vexpress64
-rw-r--r-- | Documentation/devicetree/bindings/video/arm,hdlcd.txt | 115 | ||||
-rw-r--r-- | arch/arm64/boot/dts/arm/juno-base.dtsi | 72 | ||||
-rw-r--r-- | arch/arm64/boot/dts/arm/juno-clocks.dtsi | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/Kconfig | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_crtc.c | 265 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_drv.c | 525 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_drv.h | 70 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_regs.h | 87 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c | 243 | ||||
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_virt_encoder.c | 199 |
13 files changed, 1608 insertions, 5 deletions
diff --git a/Documentation/devicetree/bindings/video/arm,hdlcd.txt b/Documentation/devicetree/bindings/video/arm,hdlcd.txt new file mode 100644 index 00000000000..51795ce7dff --- /dev/null +++ b/Documentation/devicetree/bindings/video/arm,hdlcd.txt @@ -0,0 +1,115 @@ +ARM HDLCD + +This is a driver for a display controller found on several development +platforms produced by ARM Ltd and in more modern of its' Fast Models. +The HDLCD is an RGB streamer that reads the data from a framebuffer +and sends it to a single digital encoder (DVI or HDMI). + +Required properties: + - compatible: "arm,hdlcd" + - reg: Physical base address and length of the controller's registers. + If a second pair of address and length values is present this specifies + the presence of a DMA coherent memory area that the HDLCD can use as + framebuffer instead of normal CMA memory. + - interrupts: One interrupt used by the display controller to notify the + interrupt controller when any of the interrupt sources programmed in + the interrupt mask register have activated. + - clocks: A list of phandle + clock-specifier pairs, one for each + entry in 'clock-names'. + - clock-names: A list of clock names. For HDLD it should contain: + - "pxlclk" for the clock feeding the output PLL of the controller. + +Optional nodes: + - port: The HDLCD connection to an encoder chip. The connection is modeled + using the OF graph bindings specified in Documentation/devicetree/bindings/graph.txt. + If no port node is specified then the driver will assume that it is + running an emulated device. + - display-timings: If the HDLCD is emulated by the Fast Model, place the + videomode(s) of the desired resolution(s) here. If multiple videomodes + are being used, a 'native-mode' property should be used to indicate the + preferred/default resolution. Refer to + Documentation/devicetree/bindings/video/display-timing.txt for details. + + +Example: + +/ { + ... + + /* Emulated device */ + hdlcd@7ff60000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff60000 0 0x1000>; + interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>; + + display-timings { + native-mode = <&timing0>; + timing0: timing@0 { + /* 1024 x 768 framebufer, standard VGA timings */ + clock-frequency = <65000>; + hactive = <1024>; + vactive = <768>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; + }; + + /* Physical device */ + hdlcd@2b000000 { + compatible = "arm,hdlcd"; + reg = <0 0x2b000000 0 0x1000>; + interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&oscclk5>; + clock-names = "pxlclk"; + port { + hdlcd_output: endpoint@0 { + remote-endpoint = <&tda998x_1_input>; + }; + }; + }; + + /* HDMI I2C bus */ + i2c@7ffa0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x7ffa0000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>; + clock-frequency = <400000>; + i2c-sda-hold-time-ns = <500>; + clocks = <&soc_smc50mhz>; + + hdmi-transmitter@70 { + compatible = "nxp,tda998x"; + reg = <0x70>; + video-ports = <0x234501>; + port@0 { + tda998x_1_input: endpoint { + remote-endpoint = <&hdlcd_output>; + }; + + tda998x_1_output: endpoint { + remote-endpoint = <&hdmi_1_port>; + }; + }; + }; + + }; + + hdmi1: connector@1 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi_1_port: endpoint { + remote-endpoint = <&tda998x_1_output>; + }; + }; + }; + + ... +}; diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi b/arch/arm64/boot/dts/arm/juno-base.dtsi index f8069a98da2..ed93b2b28f4 100644 --- a/arch/arm64/boot/dts/arm/juno-base.dtsi +++ b/arch/arm64/boot/dts/arm/juno-base.dtsi @@ -94,6 +94,34 @@ clocks = <&soc_faxiclk>; clock-names = "apb_pclk"; }; +/* + hdlcd@7ff50000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff50000 0 0x1000>; + interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&scpi_clk 0>; + clock-names = "pxlclk"; + + port { + hdlcd1_output: endpoint@0 { + remote-endpoint = <&tda998x_1_input>; + }; + }; + }; +*/ + hdlcd@7ff60000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff60000 0 0x1000>; + interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&scpi_clk 1>; + clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&tda998x_0_input>; + }; + }; + }; soc_uart0: uart@7ff80000 { compatible = "arm,pl011", "arm,primecell"; @@ -109,18 +137,36 @@ #address-cells = <1>; #size-cells = <0>; interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>; - clock-frequency = <400000>; + clock-frequency = <100000>; i2c-sda-hold-time-ns = <500>; clocks = <&soc_smc50mhz>; - dvi0: dvi-transmitter@70 { + hdmi-transmitter@70 { compatible = "nxp,tda998x"; reg = <0x70>; + port { + tda998x_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + tda998x_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; + }; }; - dvi1: dvi-transmitter@71 { + hdmi-transmitter@71 { compatible = "nxp,tda998x"; reg = <0x71>; + port { +/* tda998x_1_input: endpoint@0 { + remote-endpoint = <&hdlcd1_output>; + }; +*/ + tda998x_1_output: endpoint@1 { + remote-endpoint = <&hdmi1_connector_output>; + }; + }; }; }; @@ -154,6 +200,26 @@ <0x00000008 0x80000000 0x1 0x80000000>; }; + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&tda998x_0_output>; + }; + }; + }; + + hdmi1: connector@1 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi1_connector_output: endpoint { + remote-endpoint = <&tda998x_1_output>; + }; + }; + }; + smb { compatible = "simple-bus"; #address-cells = <2>; diff --git a/arch/arm64/boot/dts/arm/juno-clocks.dtsi b/arch/arm64/boot/dts/arm/juno-clocks.dtsi index 64af7370815..d7b86b15eb2 100644 --- a/arch/arm64/boot/dts/arm/juno-clocks.dtsi +++ b/arch/arm64/boot/dts/arm/juno-clocks.dtsi @@ -60,8 +60,8 @@ scpi_clk: scpi_clocks@3 { compatible = "arm,scpi-variable-clocks"; #clock-cells = <1>; - clock-indices = <3>, <4>; - clock-output-names = "pxlclk0", "pxlclk1"; + clock-indices = <3>, <4>, <5>; + clock-output-names = "pxlclk0", "pxlclk1", "i2s_clock"; }; }; }; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 47f2ce81b41..d357ed178a7 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -88,6 +88,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 7d4944e1a60..a223833bc15 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -34,6 +34,7 @@ CFLAGS_drm_trace_points.o := -I$(src) obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o +obj-$(CONFIG_DRM_ARM) += arm/ obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_TDFX) += tdfx/ obj-$(CONFIG_DRM_R128) += r128/ diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig new file mode 100644 index 00000000000..8de5b511a96 --- /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 00000000000..3bf3ede31ee --- /dev/null +++ b/drivers/gpu/drm/arm/Makefile @@ -0,0 +1,4 @@ + +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_vexpress_encoder.o +hdlcd-$(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 00000000000..8081238b2f1 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of a CRTC class for the HDLCD driver. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/of_graph.h> +#include <linux/platform_data/simplefb.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +/* + * The HDLCD controller is a dumb RGB streamer that gets connected to + * a single HDMI transmitter or in the case of the ARM Models it gets + * emulated by the software that does the actual rendering. + * + */ +static void hdlcd_crtc_destroy(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + of_node_put(hdlcd->crtc.port); + drm_crtc_cleanup(crtc); +} + +void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd) +{ + 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 struct simplefb_format supported_formats[] = SIMPLEFB_FORMATS; + +static int hdlcd_crtc_colour_set(struct hdlcd_drm_private *hdlcd, + uint32_t pixel_format) +{ + unsigned int depth, bpp; + unsigned int default_color = 0x00000000; + struct simplefb_format *format = NULL; + int i; + +#ifdef HDLCD_SHOW_UNDERRUN + default_color = 0x00ff0000; /* show underruns in red */ +#endif + + /* Calculate each colour's number of bits */ + drm_fb_get_bpp_depth(pixel_format, &depth, &bpp); + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].fourcc == pixel_format) + format = &supported_formats[i]; + } + + if (!format) { + DRM_ERROR("Format not supported: 0x%x\n", pixel_format); + return -EINVAL; + } + + /* HDLCD uses 'bytes per pixel' */ + bpp = (bpp + 7) / 8; + hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (bpp - 1) << 3); + + /* + * The format of the HDLCD_REG_<color>_SELECT register is: + * - bits[23:16] - default value for that color component + * - bits[11:8] - number of bits to extract for each color component + * - bits[4:0] - index of the lowest bit to extract + * + * The default color value is used when bits[11:8] read zero, when the + * pixel is outside the visible frame area or when there is a + * buffer underrun. + */ + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color | + format->red.offset | (format->red.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color | + format->green.offset | (format->green.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color | + format->blue.offset | (format->blue.length & 0xf) << 8); + + return 0; +} + +static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, struct drm_framebuffer *oldfb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + unsigned int depth, bpp, polarities, line_length, err; + + drm_vblank_pre_modeset(crtc->dev, 0); + + polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA; + + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + polarities |= HDLCD_POLARITY_HSYNC; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + polarities |= HDLCD_POLARITY_VSYNC; + + drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp); + /* switch to the more useful 'bytes per pixel' HDLCD needs */ + bpp = (bpp + 7) / 8; + line_length = crtc->primary->fb->width * bpp; + + /* Allow max number of outstanding requests and largest burst size */ + hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS, + HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, line_length); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, + crtc->primary->fb->height - 1); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, line_length); + hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH, + mode->vtotal - mode->vsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH, + mode->vsync_start - mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_SYNC, + mode->vsync_end - mode->vsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_DATA, mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH, + mode->htotal - mode->hsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH, + mode->hsync_start - mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_SYNC, + mode->hsync_end - mode->hsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_DATA, mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities); + + err = hdlcd_crtc_colour_set(hdlcd, crtc->primary->fb->pixel_format); + if (err) + return err; + + clk_prepare(hdlcd->clk); + clk_set_rate(hdlcd->clk, mode->crtc_clock * 1000); + clk_enable(hdlcd->clk); + + hdlcd_set_scanout(hdlcd); + + 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, +}; + +int hdlcd_setup_crtc(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + int ret; + + hdlcd->crtc.port = of_graph_get_next_endpoint(dev->platformdev->dev.of_node, NULL); + if (!hdlcd->crtc.port) + return -ENXIO; + + ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs); + if (ret < 0) { + of_node_put(hdlcd->crtc.port); + return ret; + } + + drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs); + return 0; +} + diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c new file mode 100644 index 00000000000..68a8ec424c0 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Driver + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +static void hdlcd_setup_mode_config(struct drm_device *dev); + +static int compare_dev(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +struct drm_encoder * +hdlcd_connector_best_encoder(struct drm_connector *connector) +{ + int enc_id = connector->encoder_ids[0]; + struct drm_mode_object *obj; + struct drm_encoder *encoder; + + if (connector->encoder) + return connector->encoder; + + if (enc_id) { + obj = drm_mode_object_find(connector->dev, enc_id, + DRM_MODE_OBJECT_ENCODER); + if (obj) { + encoder = obj_to_encoder(obj); + return encoder; + } + } + return NULL; + +} + +static int hdlcd_unload(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + drm_kms_helper_poll_fini(dev); + if (hdlcd->fbdev) + 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); + + dma_release_declared_memory(dev->dev); + platform_set_drvdata(dev->platformdev, NULL); + dev->dev_private = NULL; + + 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; + u32 version; + int ret; + + hdlcd = devm_kzalloc(dev->dev, sizeof(*hdlcd), GFP_KERNEL); + if (!hdlcd) + return -ENOMEM; + +#ifdef CONFIG_DEBUG_FS + atomic_set(&hdlcd->buffer_underrun_count, 0); + atomic_set(&hdlcd->bus_error_count, 0); + atomic_set(&hdlcd->vsync_count, 0); + atomic_set(&hdlcd->dma_end_count, 0); +#endif + platform_set_drvdata(pdev, dev); + dev->dev_private = hdlcd; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdlcd->mmio = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hdlcd->mmio)) { + DRM_ERROR("failed to map control registers area\n"); + ret = PTR_ERR(hdlcd->mmio); + goto fail; + } + + version = hdlcd_read(hdlcd, HDLCD_REG_VERSION); + if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { + DRM_ERROR("unknown product id: 0x%x\n", version); + ret = -EINVAL; + goto fail; + } + DRM_INFO("found ARM HDLCD version r%dp%d\n", + (version & HDLCD_VERSION_MAJOR_MASK) >> 8, + version & HDLCD_VERSION_MINOR_MASK); + + /* Get the optional coherent memory resource */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + ret = dma_declare_coherent_memory(dev->dev, res->start, res->start, + resource_size(res), DMA_MEMORY_MAP); + if ((ret & DMA_MEMORY_MAP) == 0) { + DRM_ERROR("failed to declare coherent device memory\n"); + ret = -ENXIO; + goto fail; + } + } + + hdlcd_setup_mode_config(dev); + ret = hdlcd_setup_crtc(dev); + if (ret < 0) { + DRM_ERROR("failed to create crtc\n"); + goto fail; + } + + hdlcd->clk = clk_get(dev->dev, NULL); + if (IS_ERR(hdlcd->clk)) { + ret = PTR_ERR(hdlcd->clk); + goto fail; + } + + ret = component_bind_all(dev->dev, dev); + if (ret) { + DRM_ERROR("Failed to bind all components\n"); + goto fail; + } + + drm_kms_helper_poll_init(dev); + drm_mode_config_reset(dev); + + ret = drm_irq_install(dev, platform_get_irq(pdev, 0)); + if (ret < 0) { + DRM_ERROR("failed to install IRQ handler\n"); + goto fail; + } + + dev->irq_enabled = true; + dev->vblank_disable_allowed = true; + + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); + if (ret < 0) { + DRM_ERROR("failed to initialise vblank\n"); + goto fail; + } + hdlcd->fbdev = drm_fbdev_cma_init(dev, 32, + dev->mode_config.num_crtc, + dev->mode_config.num_connector); + + if (IS_ERR(hdlcd->fbdev)) { + DRM_ERROR("failed to initialise fbdev buffer\n"); + ret = PTR_ERR(hdlcd->fbdev); + hdlcd->fbdev = NULL; + goto fail; + } + + return 0; + +fail: + hdlcd_unload(dev); + + return ret; +} + +static void hdlcd_fb_output_poll_changed(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + if (hdlcd->fbdev) { + 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, +}; + +static void hdlcd_setup_mode_config(struct drm_device *dev) +{ + drm_mode_config_init(dev); + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = HDLCD_MAX_XRES; + dev->mode_config.max_height = HDLCD_MAX_YRES; + dev->mode_config.funcs = &hdlcd_mode_config_funcs; +} + +static void hdlcd_preclose(struct drm_device *dev, struct drm_file *file) +{ +} + +static void hdlcd_lastclose(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + 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, +}; + +struct sg_table *hdlcd_gem_cma_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct sg_table *sgt; + + sgt = drm_gem_cma_prime_get_sg_table(obj); + if (sgt) { + struct drm_gem_cma_object *cma_obj; + + cma_obj = to_drm_gem_cma_obj(obj); + sg_dma_address(sgt->sgl) = cma_obj->paddr; + sg_set_page(sgt->sgl, pfn_to_page(PFN_DOWN(cma_obj->paddr)), + PAGE_ALIGN(obj->size), 0); + } + + return sgt; +} + +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 = hdlcd_gem_cma_prime_get_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = hdlcd_debugfs_init, + .debugfs_cleanup = hdlcd_debugfs_cleanup, +#endif + .fops = &fops, + .name = "hdlcd", + .desc = "ARM HDLCD Controller DRM", + .date = "20130505", + .major = 1, + .minor = 0, +}; + +static int hdlcd_add_components(struct device *dev, struct master *master) +{ + struct device_node *port, *ep = NULL; + int ret = -ENXIO; + + if (!dev->of_node) + return -ENODEV; + + do { + ep = of_graph_get_next_endpoint(dev->of_node, ep); + if (!ep) + break; + + if (!of_device_is_available(ep)) { + of_node_put(ep); + continue; + } + + port = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (!port || !of_device_is_available(port)) { + of_node_put(port); + continue; + } + + ret = component_master_add_child(master, compare_dev, port); + of_node_put(port); + } while (1); + + return ret; +} + +static int hdlcd_drm_bind(struct device *dev) +{ + return drm_platform_init(&hdlcd_driver, to_platform_device(dev)); +} + +static void hdlcd_drm_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops hdlcd_master_ops = { + .add_components = hdlcd_add_components, + .bind = hdlcd_drm_bind, + .unbind = hdlcd_drm_unbind, +}; + +static int hdlcd_probe(struct platform_device *pdev) +{ + return component_master_add(&pdev->dev, &hdlcd_master_ops); +} + +static int hdlcd_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &hdlcd_master_ops); + return 0; +} + +static struct of_device_id hdlcd_of_match[] = { + { .compatible = "arm,hdlcd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hdlcd_of_match); + +static struct platform_driver hdlcd_platform_driver = { + .probe = hdlcd_probe, + .remove = hdlcd_remove, + .driver = { + .name = "hdlcd", + .owner = THIS_MODULE, + .of_match_table = hdlcd_of_match, + }, +}; + +static int __init hdlcd_init(void) +{ + int err = platform_driver_register(&hdlcd_platform_driver); + +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + if (!err) + hdlcd_underrun_init(); +#endif + + return err; +} + +static void __exit hdlcd_exit(void) +{ +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + hdlcd_underrun_close(); +#endif + platform_driver_unregister(&hdlcd_platform_driver); +} + +/* need late_initcall() so we load after i2c driver */ +late_initcall(hdlcd_init); +module_exit(hdlcd_exit); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM HDLCD DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h new file mode 100644 index 00000000000..3585adb0fd8 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -0,0 +1,70 @@ +/* + * 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; +#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_regs.h b/drivers/gpu/drm/arm/hdlcd_regs.h new file mode 100644 index 00000000000..66799ebef6d --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_regs.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_REGS_H__ +#define __HDLCD_REGS_H__ + +/* register offsets */ +#define HDLCD_REG_VERSION 0x0000 /* ro */ +#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */ +#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */ +#define HDLCD_REG_INT_MASK 0x0018 /* rw */ +#define HDLCD_REG_INT_STATUS 0x001c /* ro */ +#define HDLCD_REG_FB_BASE 0x0100 /* rw */ +#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */ +#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */ +#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */ +#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */ +#define HDLCD_REG_V_SYNC 0x0200 /* rw */ +#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */ +#define HDLCD_REG_V_DATA 0x0208 /* rw */ +#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */ +#define HDLCD_REG_H_SYNC 0x0210 /* rw */ +#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */ +#define HDLCD_REG_H_DATA 0x0218 /* rw */ +#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */ +#define HDLCD_REG_POLARITIES 0x0220 /* rw */ +#define HDLCD_REG_COMMAND 0x0230 /* rw */ +#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */ +#define HDLCD_REG_RED_SELECT 0x0244 /* rw */ +#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */ +#define HDLCD_REG_BLUE_SELECT 0x024c /* rw */ + +/* version */ +#define HDLCD_PRODUCT_ID 0x1CDC0000 +#define HDLCD_PRODUCT_MASK 0xFFFF0000 +#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00 +#define HDLCD_VERSION_MINOR_MASK 0x000000FF + +/* interrupts */ +#define HDLCD_INTERRUPT_DMA_END (1 << 0) +#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1) +#define HDLCD_INTERRUPT_VSYNC (1 << 2) +#define HDLCD_INTERRUPT_UNDERRUN (1 << 3) +#define HDLCD_DEBUG_INT_MASK (HDLCD_INTERRUPT_DMA_END | \ + HDLCD_INTERRUPT_BUS_ERROR | \ + HDLCD_INTERRUPT_UNDERRUN) + +/* polarities */ +#define HDLCD_POLARITY_VSYNC (1 << 0) +#define HDLCD_POLARITY_HSYNC (1 << 1) +#define HDLCD_POLARITY_DATAEN (1 << 2) +#define HDLCD_POLARITY_DATA (1 << 3) +#define HDLCD_POLARITY_PIXELCLK (1 << 4) + +/* commands */ +#define HDLCD_COMMAND_DISABLE (0 << 0) +#define HDLCD_COMMAND_ENABLE (1 << 0) + +/* pixel format */ +#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31) +#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31) +#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3) + +/* bus options */ +#define HDLCD_BUS_BURST_MASK 0x01f +#define HDLCD_BUS_MAX_OUTSTAND 0xf00 +#define HDLCD_BUS_BURST_NONE (0 << 0) +#define HDLCD_BUS_BURST_1 (1 << 0) +#define HDLCD_BUS_BURST_2 (1 << 1) +#define HDLCD_BUS_BURST_4 (1 << 2) +#define HDLCD_BUS_BURST_8 (1 << 3) +#define HDLCD_BUS_BURST_16 (1 << 4) + +/* Max resolution supported is 4096x4096, 32bpp */ +#define HDLCD_MAX_XRES 4096 +#define HDLCD_MAX_YRES 4096 + +#define NR_PALETTE 256 + +#endif /* __HDLCD_REGS_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c new file mode 100644 index 00000000000..e116de8481f --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include <linux/vexpress.h> + +#include "hdlcd_drv.h" + +/* + * use the functionality of vexpress-config to set the output mode + * for HDLCD on Versatile Express boards that lack proper control + * of the DDC i2c chip. + */ + +//static struct vexpress_config_func *vconfig_func; + +/* + * Predefined modes that are available through the VExpress micro + */ +static const struct { + int hsize, vsize, vrefresh, dvimode; +} vexpress_dvimodes[] = { + { 640, 480, 60, 0 }, /* VGA */ + { 800, 600, 60, 1 }, /* SVGA */ + { 1024, 768, 60, 2 }, /* XGA */ + { 1280, 1024, 60, 3 }, /* SXGA */ + { 1600, 1200, 60, 4 }, /* UXGA */ + { 1920, 1080, 60, 5 }, /* HD1080 */ +}; + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ +} + +static enum drm_connector_status +hdlcd_connector_detect(struct drm_connector *connector, bool force) +{ + //if (vconfig_func) + return connector_status_connected; + //return connector_status_disconnected; +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static int hdlcd_vexpress_con_get_modes(struct drm_connector *connector) +{ + int i; + struct drm_display_mode *mode; + + /* Add the predefined modes */ + for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) { + mode = drm_mode_find_dmt(connector->dev, + vexpress_dvimodes[i].hsize, + vexpress_dvimodes[i].vsize, + vexpress_dvimodes[i].vrefresh, false); + if (!mode) + continue; + /* prefer the 1280x1024 mode */ + if (i == 3) + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + } + + return i; +} + +/* + * mode valid is only called for detected modes and we know that + * the restricted list is correct ;) + */ +static int hdlcd_vexpress_con_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs hdlcd_vexpress_con_helper_funcs = { + .get_modes = hdlcd_vexpress_con_get_modes, + .mode_valid = hdlcd_vexpress_con_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + + +static void hdlcd_vexpress_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); + /*if (vconfig_func) + vexpress_config_func_put(vconfig_func);*/ +} + +static const struct drm_encoder_funcs hdlcd_vexpress_encoder_funcs = { + .destroy = hdlcd_vexpress_encoder_destroy, +}; + +static void hdlcd_vexpress_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* VExpress micro has no support for DPMS */ +} + +static bool hdlcd_vexpress_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needs to be done here */ + return true; +} + +static void hdlcd_vexpress_encoder_prepare(struct drm_encoder *encoder) +{ + hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_vexpress_encoder_commit(struct drm_encoder *encoder) +{ + hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void hdlcd_vexpress_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + 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) { + DRM_ERROR("failed to get an output connector\n"); + return -ENXIO; + }*/ + + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + if (!encoder) { + err = -ENOMEM; + goto encoder_alloc_fail; + } + + encoder->possible_crtcs = 1; + encoder->possible_clones = 0; + err = drm_encoder_init(dev, encoder, &hdlcd_vexpress_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (err) + goto encoder_init_fail; + + drm_encoder_helper_add(encoder, &hdlcd_vexpress_encoder_helper_funcs); + + connector = kzalloc(sizeof(*connector), GFP_KERNEL); + if (!connector) { + err = -ENOMEM; + goto connector_alloc_err; + } + + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + err = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + if (err) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_vexpress_con_helper_funcs); + + connector->encoder = encoder; + err = drm_mode_connector_attach_encoder(connector, encoder); + if (err) + goto connector_attach_err; + + return 0; + +connector_attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(connector); +connector_alloc_err: + drm_encoder_cleanup(encoder); +encoder_init_fail: + kfree(encoder); +encoder_alloc_fail: + //vexpress_config_func_put(vconfig_func); + + return err; +} diff --git a/drivers/gpu/drm/arm/hdlcd_virt_encoder.c b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c new file mode 100644 index 00000000000..2200c4a46b4 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include "hdlcd_drv.h" + +struct hdlcd_connector { + struct drm_connector connector; + struct display_timings *timings; +}; + +#define conn_to_hdlcd(x) container_of(x, struct hdlcd_connector, connector) + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ + struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector); + + drm_connector_cleanup(connector); + kfree(hdlcd); +} + +static enum drm_connector_status hdlcd_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static int hdlcd_connector_get_modes(struct drm_connector *connector) +{ + struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector); + struct display_timings *timings = hdlcd->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct videomode vm; + + if (videomode_from_timings(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER; + if (timings->native_mode == i) + mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int hdlcd_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs hdlcd_virt_con_helper_funcs = { + .get_modes = hdlcd_connector_get_modes, + .mode_valid = hdlcd_connector_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + +static void hdlcd_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_funcs hdlcd_encoder_funcs = { + .destroy = hdlcd_encoder_destroy, +}; + +static void hdlcd_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool hdlcd_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void hdlcd_encoder_prepare(struct drm_encoder *encoder) +{ + hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_encoder_commit(struct drm_encoder *encoder) +{ + hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void hdlcd_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_helper_funcs hdlcd_encoder_helper_funcs = { + .dpms = hdlcd_encoder_dpms, + .mode_fixup = hdlcd_encoder_mode_fixup, + .prepare = hdlcd_encoder_prepare, + .commit = hdlcd_encoder_commit, + .mode_set = hdlcd_encoder_mode_set, +}; + +int hdlcd_create_virtual_connector(struct drm_device *dev) +{ + struct drm_encoder *encoder; + struct hdlcd_connector *hdlcdc; + struct drm_connector *connector; + int ret; + + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return -ENOMEM; + + encoder->possible_crtcs = 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(dev, encoder, &hdlcd_encoder_funcs, + DRM_MODE_ENCODER_VIRTUAL); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &hdlcd_encoder_helper_funcs); + + hdlcdc = kzalloc(sizeof(*hdlcdc), GFP_KERNEL); + if (!hdlcdc) { + ret = -ENOMEM; + goto connector_alloc_err; + } + + hdlcdc->timings = of_get_display_timings(dev->platformdev->dev.of_node); + if (!hdlcdc->timings) { + DRM_ERROR("failed to get display panel timings\n"); + ret = -ENXIO; + goto connector_init_err; + } + + connector = &hdlcdc->connector; + + /* bogus values, pretend we're a 24" screen */ + connector->display_info.width_mm = 519; + connector->display_info.height_mm = 324; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + ret = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_virt_con_helper_funcs); + + connector->encoder = encoder; + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + + return ret; + +attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(hdlcdc); +connector_alloc_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + kfree(encoder); + + return ret; +}; |