diff options
author | Jon Medhurst <tixy@linaro.org> | 2017-02-20 15:58:20 +0000 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2017-02-20 15:58:20 +0000 |
commit | 8bb1326164dfa97224e52a2b31db1f0cc9bdf721 (patch) | |
tree | 4ea6c75ceac84626656271cd7c97068dd1bbb2cb | |
parent | a90cfd84335a0a551f75c48f752b8ed91c35bc97 (diff) | |
parent | 9818e36e1c9c310832e399aba30dec6251d42057 (diff) |
Merge branch '4.4-armlt-hdlcd' into 4.4-armlt
# Conflicts:
# arch/arm64/boot/dts/arm/juno-base.dtsi
# linaro/configs/vexpress64.conf
23 files changed, 2836 insertions, 11 deletions
diff --git a/Documentation/devicetree/bindings/video/arm,hdlcd.txt b/Documentation/devicetree/bindings/video/arm,hdlcd.txt new file mode 100644 index 000000000000..51795ce7dff1 --- /dev/null +++ b/Documentation/devicetree/bindings/video/arm,hdlcd.txt @@ -0,0 +1,115 @@ +ARM HDLCD + +This is a driver for a display controller found on several development +platforms produced by ARM Ltd and in more modern of its' Fast Models. +The HDLCD is an RGB streamer that reads the data from a framebuffer +and sends it to a single digital encoder (DVI or HDMI). + +Required properties: + - compatible: "arm,hdlcd" + - reg: Physical base address and length of the controller's registers. + If a second pair of address and length values is present this specifies + the presence of a DMA coherent memory area that the HDLCD can use as + framebuffer instead of normal CMA memory. + - interrupts: One interrupt used by the display controller to notify the + interrupt controller when any of the interrupt sources programmed in + the interrupt mask register have activated. + - clocks: A list of phandle + clock-specifier pairs, one for each + entry in 'clock-names'. + - clock-names: A list of clock names. For HDLD it should contain: + - "pxlclk" for the clock feeding the output PLL of the controller. + +Optional nodes: + - port: The HDLCD connection to an encoder chip. The connection is modeled + using the OF graph bindings specified in Documentation/devicetree/bindings/graph.txt. + If no port node is specified then the driver will assume that it is + running an emulated device. + - display-timings: If the HDLCD is emulated by the Fast Model, place the + videomode(s) of the desired resolution(s) here. If multiple videomodes + are being used, a 'native-mode' property should be used to indicate the + preferred/default resolution. Refer to + Documentation/devicetree/bindings/video/display-timing.txt for details. + + +Example: + +/ { + ... + + /* Emulated device */ + hdlcd@7ff60000 { + compatible = "arm,hdlcd"; + reg = <0 0x7ff60000 0 0x1000>; + interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>; + + display-timings { + native-mode = <&timing0>; + timing0: timing@0 { + /* 1024 x 768 framebufer, standard VGA timings */ + clock-frequency = <65000>; + hactive = <1024>; + vactive = <768>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; + }; + + /* Physical device */ + hdlcd@2b000000 { + compatible = "arm,hdlcd"; + reg = <0 0x2b000000 0 0x1000>; + interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&oscclk5>; + clock-names = "pxlclk"; + port { + hdlcd_output: endpoint@0 { + remote-endpoint = <&tda998x_1_input>; + }; + }; + }; + + /* HDMI I2C bus */ + i2c@7ffa0000 { + compatible = "snps,designware-i2c"; + reg = <0x0 0x7ffa0000 0x0 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>; + clock-frequency = <400000>; + i2c-sda-hold-time-ns = <500>; + clocks = <&soc_smc50mhz>; + + hdmi-transmitter@70 { + compatible = "nxp,tda998x"; + reg = <0x70>; + video-ports = <0x234501>; + port@0 { + tda998x_1_input: endpoint { + remote-endpoint = <&hdlcd_output>; + }; + + tda998x_1_output: endpoint { + remote-endpoint = <&hdmi_1_port>; + }; + }; + }; + + }; + + hdmi1: connector@1 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi_1_port: endpoint { + remote-endpoint = <&tda998x_1_output>; + }; + }; + }; + + ... +}; diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index 21b02874bea3..f55784037021 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -222,6 +222,9 @@ dvi-transmitter@39 { compatible = "sil,sii9022-tpi", "sil,sii9022"; reg = <0x39>; + + v2m_dvi_port: port { + }; }; dvi-transmitter@60 { @@ -245,7 +248,7 @@ reg-shift = <2>; }; - clcd@1f0000 { + v2m_clcd: clcd@1f0000 { compatible = "arm,pl111", "arm,primecell"; reg = <0x1f0000 0x1000>; interrupt-names = "combined"; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 9420053acc14..d4331294741b 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -57,6 +57,22 @@ interrupts = <0 85 4>; clocks = <&oscclk5>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2b0a0000 { @@ -281,3 +297,17 @@ /include/ "vexpress-v2m-rs1.dtsi" }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 17f63f7dfd9e..c2ba4d10e41f 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -111,6 +111,22 @@ interrupts = <0 85 4>; clocks = <&oscclk5>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2b0a0000 { @@ -639,3 +655,17 @@ /include/ "vexpress-v2m-rs1.dtsi" }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index d2709b73316b..83815ee3dda5 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -59,6 +59,22 @@ interrupts = <0 85 4>; clocks = <&oscclk3>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2a150000 { @@ -251,3 +267,17 @@ /include/ "vexpress-v2m-rs1.dtsi" }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi b/arch/arm64/boot/dts/arm/juno-base.dtsi index 077624ebf708..db4fd5e494c1 100644 --- a/arch/arm64/boot/dts/arm/juno-base.dtsi +++ b/arch/arm64/boot/dts/arm/juno-base.dtsi @@ -114,8 +114,8 @@ scpi_clk: scpi-clk { compatible = "arm,scpi-variable-clocks"; #clock-cells = <1>; - clock-indices = <3>; - clock-output-names = "pxlclk"; + clock-indices = <3>, <4>, <5>; + clock-output-names = "pxlclk", "pxlclk1", "i2sclk"; }; }; @@ -195,7 +195,7 @@ /include/ "juno-clocks.dtsi" - dma@7ff00000 { + dma0: dma@7ff00000 { compatible = "arm,pl330", "arm,primecell"; reg = <0x0 0x7ff00000 0 0x1000>; #dma-cells = <1>; @@ -213,7 +213,7 @@ clocks = <&soc_faxiclk>; clock-names = "apb_pclk"; }; - +/* hdlcd@7ff50000 { compatible = "arm,hdlcd"; reg = <0 0x7ff50000 0 0x1000>; @@ -227,7 +227,7 @@ }; }; }; - +*/ hdlcd@7ff60000 { compatible = "arm,hdlcd"; reg = <0 0x7ff60000 0 0x1000>; @@ -256,13 +256,16 @@ #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>; - hdmi-transmitter@70 { + hdmi_transmitter0: hdmi-transmitter@70 { compatible = "nxp,tda998x"; reg = <0x70>; + audio-ports = <0x03>, <0x04>; + audio-port-names = "i2s", "spdif"; + #sound-dai-cells = <1>; port { tda998x_0_input: tda998x-0-endpoint { remote-endpoint = <&hdlcd0_output>; @@ -274,10 +277,10 @@ compatible = "nxp,tda998x"; reg = <0x71>; port { - tda998x_1_input: tda998x-1-endpoint { +/* tda998x_1_input: tda998x-1-endpoint { remote-endpoint = <&hdlcd1_output>; }; - }; +*/ }; }; }; @@ -323,6 +326,36 @@ }; }; + soc_i2s: i2s@7ff90000 { + compatible = "snps,designware-i2s"; + reg = <0x0 0x7ff90000 0x0 0x1000>; + clocks = <&scpi_clk 5>, <&soc_refclk100mhz>; + clock-names = "i2sclk", "apb_pclk"; + #sound-dai-cells = <0>; + dmas = <&dma0 5>; + dma-names = "tx"; + }; + + hdmi_audio: hdmi_audio@0 { + compatible = "linux,hdmi-audio"; + #sound-dai-cells = <0>; + status = "okay"; + }; + + sound { + compatible = "simple-audio-card"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,cpu { + sound-dai = <&soc_i2s>; + }; + + simple-audio-card,codec { + sound-dai = <&hdmi_transmitter0 0>; + }; + }; + smb@08000000 { compatible = "simple-bus"; #address-cells = <2>; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index c4bf9a1cf4a6..3fd9124a828b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -106,6 +106,8 @@ config DRM_TDFX Choose this option if you have a 3dfx Banshee or Voodoo3 (or later), graphics card. If M is selected, the module will be called tdfx. +source "drivers/gpu/drm/arm/Kconfig" + config DRM_R128 tristate "ATI Rage 128" depends on DRM && PCI diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1e9ff4c3e3db..6b42d7553719 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -35,6 +35,7 @@ CFLAGS_drm_trace_points.o := -I$(src) obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o +obj-$(CONFIG_DRM_ARM) += arm/ obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_TDFX) += tdfx/ obj-$(CONFIG_DRM_R128) += r128/ diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig new file mode 100644 index 000000000000..8de5b511a963 --- /dev/null +++ b/drivers/gpu/drm/arm/Kconfig @@ -0,0 +1,26 @@ +config DRM_ARM + bool "ARM Ltd. drivers" + depends on DRM && OF && (ARM || ARM64) + select DMA_CMA + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select VIDEOMODE_HELPERS + help + Choose this option to select drivers for ARM's devices + +config DRM_HDLCD + tristate "ARM HDLCD" + depends on DRM_ARM + select I2C + help + Choose this option if you have an ARM High Definition Colour LCD + controller. + + If M is selected the module will be called hdlcd. + +config DRM_VIRTUAL_HDLCD + bool "Support for virtual HDLCD" + depends on DRM_HDLCD + help + Enable support for virtual HDLCD as emulated by ARM's Fast Models. diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile new file mode 100644 index 000000000000..9a96f9b6340d --- /dev/null +++ b/drivers/gpu/drm/arm/Makefile @@ -0,0 +1,4 @@ + +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_vexpress_encoder.o hdlcd_fb_helper.o +hdlcd-$(CONFIG_DRM_VIRTUAL_HDLCD) += hdlcd_virt_encoder.o +obj-$(CONFIG_DRM_HDLCD) += hdlcd.o diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c new file mode 100644 index 000000000000..aa435cb06a21 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of a CRTC class for the HDLCD driver. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include "hdlcd_fb_helper.h" +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/of_graph.h> +#include <linux/platform_data/simplefb.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +/* + * The HDLCD controller is a dumb RGB streamer that gets connected to + * a single HDMI transmitter or in the case of the ARM Models it gets + * emulated by the software that does the actual rendering. + * + */ +static void hdlcd_crtc_destroy(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + of_node_put(hdlcd->crtc.port); + drm_crtc_cleanup(crtc); +} + +void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait) +{ + struct drm_framebuffer *fb = hdlcd->crtc.primary->fb; + struct drm_gem_cma_object *gem; + unsigned int depth, bpp; + dma_addr_t scanout_start; + + drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); + gem = hdlcd_fb_get_gem_obj(fb, 0); + + scanout_start = gem->paddr + fb->offsets[0] + + (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8); + + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + + if (wait && hdlcd->dpms == DRM_MODE_DPMS_ON) { + drm_vblank_get(fb->dev, 0); + /* Clear any interrupt that may be from before we changed scanout */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + /* Wait for next interrupt so we know scanout change is live */ + reinit_completion(&hdlcd->frame_completion); + wait_for_completion_interruptible(&hdlcd->frame_completion); + + drm_vblank_put(fb->dev, 0); + } +} + +static int hdlcd_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t flags) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + if (hdlcd->dpms == DRM_MODE_DPMS_ON) { + /* don't schedule any page flipping if one is in progress */ + if (hdlcd->event) + return -EBUSY; + + hdlcd->event = event; + drm_vblank_get(crtc->dev, 0); + } + + crtc->primary->fb = fb; + + if (hdlcd->dpms == DRM_MODE_DPMS_ON) { + hdlcd_set_scanout(hdlcd, true); + } else { + unsigned long flags; + + /* not active, update registers immediately */ + hdlcd_set_scanout(hdlcd, false); + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (event) + drm_send_vblank_event(crtc->dev, 0, event); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } + + return 0; +} + +static const struct drm_crtc_funcs hdlcd_crtc_funcs = { + .destroy = hdlcd_crtc_destroy, + .set_config = drm_crtc_helper_set_config, + .page_flip = hdlcd_crtc_page_flip, +}; + +static void hdlcd_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + hdlcd->dpms = mode; + if (mode == DRM_MODE_DPMS_ON) + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1); + else + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); +} + +static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void hdlcd_crtc_prepare(struct drm_crtc *crtc) +{ + hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_crtc_commit(struct drm_crtc *crtc) +{ + drm_vblank_post_modeset(crtc->dev, 0); + hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static struct simplefb_format supported_formats[] = SIMPLEFB_FORMATS; + +static int hdlcd_crtc_colour_set(struct hdlcd_drm_private *hdlcd, + uint32_t pixel_format) +{ + unsigned int depth, bpp; + unsigned int default_color = 0x00000000; + struct simplefb_format *format = NULL; + int i; + +#ifdef HDLCD_SHOW_UNDERRUN + default_color = 0x00ff0000; /* show underruns in red */ +#endif + + /* Calculate each colour's number of bits */ + drm_fb_get_bpp_depth(pixel_format, &depth, &bpp); + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].fourcc == pixel_format) + format = &supported_formats[i]; + } + + if (!format) { + DRM_ERROR("Format not supported: 0x%x\n", pixel_format); + return -EINVAL; + } + + /* HDLCD uses 'bytes per pixel' */ + bpp = (bpp + 7) / 8; + hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (bpp - 1) << 3); + + /* + * The format of the HDLCD_REG_<color>_SELECT register is: + * - bits[23:16] - default value for that color component + * - bits[11:8] - number of bits to extract for each color component + * - bits[4:0] - index of the lowest bit to extract + * + * The default color value is used when bits[11:8] read zero, when the + * pixel is outside the visible frame area or when there is a + * buffer underrun. + */ + if(!config_enabled(CONFIG_ARM)) { + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color | + format->red.offset | (format->red.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color | + format->green.offset | (format->green.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color | + format->blue.offset | (format->blue.length & 0xf) << 8); + } else { + /* + * This is a hack to swap read and blue when building for + * 32-bit ARM, because Versatile Express motherboard seems + * to be wired up differently. + */ + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color | + format->red.offset | (format->red.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color | + format->green.offset | (format->green.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color | + format->blue.offset | (format->blue.length & 0xf) << 8); + } + return 0; +} + +static int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + hdlcd_set_scanout(hdlcd, true); + return 0; +} + + +static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, struct drm_framebuffer *oldfb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + unsigned int depth, bpp, polarities, line_length, err; + + drm_vblank_pre_modeset(crtc->dev, 0); + + polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA; + + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + polarities |= HDLCD_POLARITY_HSYNC; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + polarities |= HDLCD_POLARITY_VSYNC; + + drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp); + /* switch to the more useful 'bytes per pixel' HDLCD needs */ + bpp = (bpp + 7) / 8; + line_length = crtc->primary->fb->width * bpp; + + /* Allow max number of outstanding requests and largest burst size */ + hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS, + HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, line_length); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, + crtc->primary->fb->height - 1); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, line_length); + hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH, + mode->vtotal - mode->vsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH, + mode->vsync_start - mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_SYNC, + mode->vsync_end - mode->vsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_DATA, mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH, + mode->htotal - mode->hsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH, + mode->hsync_start - mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_SYNC, + mode->hsync_end - mode->hsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_DATA, mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities); + + err = hdlcd_crtc_colour_set(hdlcd, crtc->primary->fb->pixel_format); + if (err) + return err; + + clk_prepare(hdlcd->clk); + clk_set_rate(hdlcd->clk, mode->crtc_clock * 1000); + clk_enable(hdlcd->clk); + + hdlcd_set_scanout(hdlcd, false); + + return 0; +} + +static void hdlcd_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = { + .dpms = hdlcd_crtc_dpms, + .mode_fixup = hdlcd_crtc_mode_fixup, + .prepare = hdlcd_crtc_prepare, + .commit = hdlcd_crtc_commit, + .mode_set = hdlcd_crtc_mode_set, + .mode_set_base = hdlcd_crtc_mode_set_base, + .load_lut = hdlcd_crtc_load_lut, +}; + +int hdlcd_setup_crtc(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + int ret; + + hdlcd->crtc.port = of_graph_get_next_endpoint(dev->platformdev->dev.of_node, NULL); + if (!hdlcd->crtc.port) + return -ENXIO; + + ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs); + if (ret < 0) { + of_node_put(hdlcd->crtc.port); + return ret; + } + + drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs); + return 0; +} + diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c new file mode 100644 index 000000000000..dbdda01d69d0 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Driver + */ + +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include "hdlcd_fb_helper.h" +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +static void hdlcd_setup_mode_config(struct drm_device *dev); + +static int compare_dev(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +struct drm_encoder * +hdlcd_connector_best_encoder(struct drm_connector *connector) +{ + int enc_id = connector->encoder_ids[0]; + struct drm_mode_object *obj; + struct drm_encoder *encoder; + + if (connector->encoder) + return connector->encoder; + + if (enc_id) { + obj = drm_mode_object_find(connector->dev, enc_id, + DRM_MODE_OBJECT_ENCODER); + if (obj) { + encoder = obj_to_encoder(obj); + return encoder; + } + } + return NULL; + +} + +static int hdlcd_unload(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + drm_kms_helper_poll_fini(dev); + if (hdlcd->fbdev) + hdlcd_drm_fbdev_fini(hdlcd->fbdev); + + drm_vblank_cleanup(dev); + drm_mode_config_cleanup(dev); + + drm_irq_uninstall(dev); + + if (!IS_ERR(hdlcd->clk)) + clk_put(hdlcd->clk); + + dma_release_declared_memory(dev->dev); + platform_set_drvdata(dev->platformdev, NULL); + dev->dev_private = NULL; + + return 0; +} + +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; + struct device_node *node; + int preferred_bpp; + + hdlcd = devm_kzalloc(dev->dev, sizeof(*hdlcd), GFP_KERNEL); + if (!hdlcd) + return -ENOMEM; + +#ifdef CONFIG_DEBUG_FS + atomic_set(&hdlcd->buffer_underrun_count, 0); + atomic_set(&hdlcd->bus_error_count, 0); + atomic_set(&hdlcd->vsync_count, 0); + atomic_set(&hdlcd->dma_end_count, 0); +#endif + platform_set_drvdata(pdev, dev); + dev->dev_private = hdlcd; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdlcd->mmio = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hdlcd->mmio)) { + DRM_ERROR("failed to map control registers area\n"); + ret = PTR_ERR(hdlcd->mmio); + goto fail; + } + + version = hdlcd_read(hdlcd, HDLCD_REG_VERSION); + if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { + DRM_ERROR("unknown product id: 0x%x\n", version); + ret = -EINVAL; + goto fail; + } + DRM_INFO("found ARM HDLCD version r%dp%d\n", + (version & HDLCD_VERSION_MAJOR_MASK) >> 8, + version & HDLCD_VERSION_MINOR_MASK); + + /* Make sure hardware is in a safe reset state */ + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR,~0); + hdlcd_write(hdlcd, HDLCD_REG_INT_RAWSTAT, 0); + + /* Get the optional coherent memory resource */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + ret = dma_declare_coherent_memory(dev->dev, res->start, res->start, + resource_size(res), DMA_MEMORY_MAP); + if ((ret & DMA_MEMORY_MAP) == 0) { + DRM_ERROR("failed to declare coherent device memory\n"); + ret = -ENXIO; + goto fail; + } + } + + hdlcd_setup_mode_config(dev); + ret = hdlcd_setup_crtc(dev); + if (ret < 0) { + DRM_ERROR("failed to create crtc\n"); + goto fail; + } + + hdlcd->clk = clk_get(dev->dev, NULL); + if (IS_ERR(hdlcd->clk)) { + ret = PTR_ERR(hdlcd->clk); + goto fail; + } + + ret = component_bind_all(dev->dev, dev); + if (ret) { + DRM_ERROR("Failed to bind all components\n"); + goto fail; + } + + drm_kms_helper_poll_init(dev); + drm_mode_config_reset(dev); + + ret = drm_irq_install(dev, platform_get_irq(pdev, 0)); + if (ret < 0) { + DRM_ERROR("failed to install IRQ handler\n"); + goto fail; + } + + dev->irq_enabled = true; + dev->vblank_disable_allowed = true; + + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); + if (ret < 0) { + DRM_ERROR("failed to initialise vblank\n"); + goto fail; + } + + init_completion(&hdlcd->frame_completion); + + /* Try to pick the colour depth that Android user-side is hard-coded for */ + preferred_bpp = 16; + node = of_find_compatible_node(NULL,NULL,"arm,mali-midgard"); + if (node) { + of_node_put(node); + preferred_bpp = 32; /* If Mali present, assume 32bpp */ + } + + hdlcd->fbdev = hdlcd_drm_fbdev_init(dev, preferred_bpp, + dev->mode_config.num_crtc, + dev->mode_config.num_connector); + + if (IS_ERR(hdlcd->fbdev)) { + DRM_ERROR("failed to initialise fbdev buffer\n"); + ret = PTR_ERR(hdlcd->fbdev); + hdlcd->fbdev = NULL; + goto fail; + } + + return 0; + +fail: + hdlcd_unload(dev); + + return ret; +} + +static void hdlcd_fb_output_poll_changed(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + if (hdlcd->fbdev) { + hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev); + } +} + +static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { + .fb_create = hdlcd_fb_create, + .output_poll_changed = hdlcd_fb_output_poll_changed, +}; + +static void hdlcd_setup_mode_config(struct drm_device *dev) +{ + drm_mode_config_init(dev); + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = HDLCD_MAX_XRES; + dev->mode_config.max_height = HDLCD_MAX_YRES; + dev->mode_config.funcs = &hdlcd_mode_config_funcs; +} + +static void hdlcd_preclose(struct drm_device *dev, struct drm_file *file) +{ +} + +static void hdlcd_lastclose(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev); +} + +static irqreturn_t hdlcd_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned long irq_status; + + irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); + +#ifdef CONFIG_DEBUG_FS + if (irq_status & HDLCD_INTERRUPT_UNDERRUN) { + atomic_inc(&hdlcd->buffer_underrun_count); + } + if (irq_status & HDLCD_INTERRUPT_DMA_END) { + atomic_inc(&hdlcd->dma_end_count); + } + if (irq_status & HDLCD_INTERRUPT_BUS_ERROR) { + atomic_inc(&hdlcd->bus_error_count); + } + if (irq_status & HDLCD_INTERRUPT_VSYNC) { + atomic_inc(&hdlcd->vsync_count); + } +#endif + if (irq_status & HDLCD_INTERRUPT_VSYNC) { + struct drm_pending_vblank_event *event; + unsigned long flags; + + drm_handle_vblank(dev, 0); + + spin_lock_irqsave(&dev->event_lock, flags); + if (hdlcd->event) { + event = hdlcd->event; + hdlcd->event = NULL; + drm_send_vblank_event(dev, 0, event); + drm_vblank_put(dev, 0); + } + spin_unlock_irqrestore(&dev->event_lock, flags); + } + if (irq_status & HDLCD_INTERRUPT_DMA_END) { + // send completion when reading the frame has finished + complete_all(&hdlcd->frame_completion); + } + + /* acknowledge interrupt(s) */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status); + + return IRQ_HANDLED; +} + +static void hdlcd_irq_preinstall(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + /* Ensure interrupts are disabled */ + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, ~0); +} + +static int hdlcd_irq_postinstall(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + +#ifdef CONFIG_DEBUG_FS + /* enable debug interrupts */ + irq_mask |= HDLCD_DEBUG_INT_MASK; +#endif + + /* enable DMA completion interrupts */ + irq_mask |= HDLCD_INTERRUPT_DMA_END; + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); + + return 0; +} + +static void hdlcd_irq_uninstall(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + /* disable all the interrupts that we might have enabled */ + unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + +#ifdef CONFIG_DEBUG_FS + /* disable debug interrupts */ + irq_mask &= ~HDLCD_DEBUG_INT_MASK; +#endif + + /* disable vsync and dma interrupts */ + irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); +} + +static int hdlcd_enable_vblank(struct drm_device *dev, unsigned int pipe) +{ + 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, unsigned int pipe) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + unsigned int mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); + + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, mask & ~HDLCD_INTERRUPT_VSYNC); +} + +#ifdef CONFIG_DEBUG_FS +static int hdlcd_show_underrun_count(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + seq_printf(m, "underrun : %d\n", atomic_read(&hdlcd->buffer_underrun_count)); + seq_printf(m, "dma_end : %d\n", atomic_read(&hdlcd->dma_end_count)); + seq_printf(m, "bus_error: %d\n", atomic_read(&hdlcd->bus_error_count)); + seq_printf(m, "vsync : %d\n", atomic_read(&hdlcd->vsync_count)); + return 0; +} + +static struct drm_info_list hdlcd_debugfs_list[] = { + { "interrupt_count", hdlcd_show_underrun_count, 0 }, +}; + +static int hdlcd_debugfs_init(struct drm_minor *minor) +{ + return drm_debugfs_create_files(hdlcd_debugfs_list, + ARRAY_SIZE(hdlcd_debugfs_list), minor->debugfs_root, minor); +} + +static void hdlcd_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(hdlcd_debugfs_list, + ARRAY_SIZE(hdlcd_debugfs_list), minor); +} +#endif + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver hdlcd_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | + DRIVER_MODESET | DRIVER_PRIME, + .load = hdlcd_load, + .unload = hdlcd_unload, + .preclose = hdlcd_preclose, + .lastclose = hdlcd_lastclose, + .irq_handler = hdlcd_irq, + .irq_preinstall = hdlcd_irq_preinstall, + .irq_postinstall = hdlcd_irq_postinstall, + .irq_uninstall = hdlcd_irq_uninstall, + .set_busid = drm_platform_set_busid, + .get_vblank_counter = drm_vblank_no_hw_counter, + .enable_vblank = hdlcd_enable_vblank, + .disable_vblank = hdlcd_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = hdlcd_debugfs_init, + .debugfs_cleanup = hdlcd_debugfs_cleanup, +#endif + .fops = &fops, + .name = "hdlcd", + .desc = "ARM HDLCD Controller DRM", + .date = "20130505", + .major = 1, + .minor = 0, +}; + +static int hdlcd_add_components(struct device *dev, struct master *master) +{ + struct device_node *port, *ep = NULL; + int ret = -ENXIO; + + if (!dev->of_node) + return -ENODEV; + + do { + ep = of_graph_get_next_endpoint(dev->of_node, ep); + if (!ep) + break; + + if (!of_device_is_available(ep)) { + of_node_put(ep); + continue; + } + + port = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (!port || !of_device_is_available(port)) { + of_node_put(port); + continue; + } + + ret = component_master_add_child(master, compare_dev, port); + of_node_put(port); + } while (1); + + return ret; +} + +static int hdlcd_drm_bind(struct device *dev) +{ + return drm_platform_init(&hdlcd_driver, to_platform_device(dev)); +} + +static void hdlcd_drm_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops hdlcd_master_ops = { + .add_components = hdlcd_add_components, + .bind = hdlcd_drm_bind, + .unbind = hdlcd_drm_unbind, +}; + +static int hdlcd_probe(struct platform_device *pdev) +{ + return component_master_add(&pdev->dev, &hdlcd_master_ops); +} + +static int hdlcd_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &hdlcd_master_ops); + return 0; +} + +static struct of_device_id hdlcd_of_match[] = { + { .compatible = "arm,hdlcd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hdlcd_of_match); + +static struct platform_driver hdlcd_platform_driver = { + .probe = hdlcd_probe, + .remove = hdlcd_remove, + .driver = { + .name = "hdlcd", + .owner = THIS_MODULE, + .of_match_table = hdlcd_of_match, + }, +}; + +static int __init hdlcd_init(void) +{ + int err = platform_driver_register(&hdlcd_platform_driver); + +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + if (!err) + hdlcd_underrun_init(); +#endif + + return err; +} + +static void __exit hdlcd_exit(void) +{ +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + hdlcd_underrun_close(); +#endif + platform_driver_unregister(&hdlcd_platform_driver); +} + +/* need late_initcall() so we load after i2c driver */ +late_initcall(hdlcd_init); +module_exit(hdlcd_exit); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM HDLCD DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h new file mode 100644 index 000000000000..3980276d77bc --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -0,0 +1,69 @@ +/* + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_DRV_H__ +#define __HDLCD_DRV_H__ + +struct hdlcd_drm_private { + void __iomem *mmio; + struct clk *clk; + struct hdlcd_drm_fbdev *fbdev; + struct drm_framebuffer *fb; + struct drm_pending_vblank_event *event; + struct drm_crtc crtc; + struct completion frame_completion; +#ifdef CONFIG_DEBUG_FS + atomic_t buffer_underrun_count; + atomic_t bus_error_count; + atomic_t vsync_count; + atomic_t dma_end_count; +#endif + int dpms; +}; + +#define crtc_to_hdlcd_priv(x) container_of(x, struct hdlcd_drm_private, crtc) + +static inline void +hdlcd_write(struct hdlcd_drm_private *hdlcd, unsigned int reg, u32 value) +{ + writel(value, hdlcd->mmio + reg); +} + +static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg) +{ + return readl(hdlcd->mmio + reg); +} + +/* + * Developers using HDLCD may wish to enable these settings if + * display disruption is apparent and you suspect HDLCD + * access to RAM may be starved. + * + * Turn HDLCD default color to red instead of default black so + * that it's easier to see data underruns (compared to other + * visual disruptions) + */ +//#define HDLCD_SHOW_UNDERRUN + +/* setup the crtc subclass */ +int hdlcd_setup_crtc(struct drm_device *dev); + +/* functions for creating a suitable connector */ +extern int hdlcd_create_digital_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd); +extern int hdlcd_create_vexpress_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd); +#ifdef CONFIG_DRM_VIRTUAL_HDLCD +extern int hdlcd_create_virtual_connector(struct drm_device *dev); +#else +static inline int hdlcd_create_virtual_connector(struct drm_device *dev) +{ + return -ENXIO; +} +#endif + +/* common function used by all connectors */ +extern struct drm_encoder *hdlcd_connector_best_encoder(struct drm_connector *con); + +#endif /* __HDLCD_DRV_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.c b/drivers/gpu/drm/arm/hdlcd_fb_helper.c new file mode 100644 index 000000000000..1312e01ef784 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c @@ -0,0 +1,601 @@ +/* + * Copied from drivers/gpu/drm/drm_fb_cma_helper.c which has the following + * copyright and notices... + * + * Copyright (C) 2012 Analog Device Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Based on udl_fbdev.c + * Copyright (C) 2012 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include "hdlcd_fb_helper.h" +#include <linux/dma-buf.h> +#include <linux/module.h> +#include "hdlcd_drv.h" + +#define MAX_FRAMES 2 + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg); + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_helper.c as of Linux 3.18 + ******************************************************************************/ + +/** + * Copy of drm_fb_helper_check_var modified to allow MAX_FRAMES * height + */ +int hdlcd_fb_helper_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_framebuffer *fb = fb_helper->fb; + int depth; + + if (var->pixclock != 0 || in_dbg_master()) + return -EINVAL; + + /* Need to resize the fb object !!! */ + if (var->bits_per_pixel > fb->bits_per_pixel || + var->xres > fb->width || var->yres > fb->height || + var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) { + DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb " + "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n", + var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual, + fb->width, fb->height, fb->bits_per_pixel); + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 16: + depth = (var->green.length == 6) ? 16 : 15; + break; + case 32: + depth = (var->transp.length > 0) ? 32 : 24; + break; + default: + depth = var->bits_per_pixel; + break; + } + + switch (depth) { + case 8: + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 15: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + var->transp.length = 1; + var->transp.offset = 15; + break; + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 32: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + var->transp.offset = 24; + break; + default: + return -EINVAL; + } + return 0; +} + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_cma_helper.c as of Linux 3.18 + ******************************************************************************/ + +struct hdlcd_fb { + struct drm_framebuffer fb; + struct drm_gem_cma_object *obj[4]; +}; + +struct hdlcd_drm_fbdev { + struct drm_fb_helper fb_helper; + struct hdlcd_fb *fb; +}; + +static inline struct hdlcd_drm_fbdev *to_hdlcd_fbdev(struct drm_fb_helper *helper) +{ + return container_of(helper, struct hdlcd_drm_fbdev, fb_helper); +} + +static inline struct hdlcd_fb *to_hdlcd_fb(struct drm_framebuffer *fb) +{ + return container_of(fb, struct hdlcd_fb, fb); +} + +static void hdlcd_fb_destroy(struct drm_framebuffer *fb) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + int i; + + for (i = 0; i < 4; i++) { + if (hdlcd_fb->obj[i]) + drm_gem_object_unreference_unlocked(&hdlcd_fb->obj[i]->base); + } + + drm_framebuffer_cleanup(fb); + kfree(hdlcd_fb); +} + +static int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + return drm_gem_handle_create(file_priv, + &hdlcd_fb->obj[0]->base, handle); +} + +static struct drm_framebuffer_funcs hdlcd_fb_funcs = { + .destroy = hdlcd_fb_destroy, + .create_handle = hdlcd_fb_create_handle, +}; + +static struct hdlcd_fb *hdlcd_fb_alloc(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_cma_object **obj, + unsigned int num_planes) +{ + struct hdlcd_fb *hdlcd_fb; + int ret; + int i; + + hdlcd_fb = kzalloc(sizeof(*hdlcd_fb), GFP_KERNEL); + if (!hdlcd_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(&hdlcd_fb->fb, mode_cmd); + + for (i = 0; i < num_planes; i++) + hdlcd_fb->obj[i] = obj[i]; + + ret = drm_framebuffer_init(dev, &hdlcd_fb->fb, &hdlcd_fb_funcs); + if (ret) { + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret); + kfree(hdlcd_fb); + return ERR_PTR(ret); + } + + return hdlcd_fb; +} + +/** + * hdlcd_fb_create() - (struct drm_mode_config_funcs *)->fb_create callback function + * + * If your hardware has special alignment or pitch requirements these should be + * checked before calling this function. + */ +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct hdlcd_fb *hdlcd_fb; + struct drm_gem_cma_object *objs[4]; + struct drm_gem_object *obj; + unsigned int hsub; + unsigned int vsub; + int ret; + int i; + + hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format); + vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format); + + for (i = 0; i < drm_format_num_planes(mode_cmd->pixel_format); i++) { + unsigned int width = mode_cmd->width / (i ? hsub : 1); + unsigned int height = mode_cmd->height / (i ? vsub : 1); + unsigned int min_size; + + obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[i]); + if (!obj) { + dev_err(dev->dev, "Failed to lookup GEM object\n"); + ret = -ENXIO; + goto err_gem_object_unreference; + } + + min_size = (height - 1) * mode_cmd->pitches[i] + + width * drm_format_plane_cpp(mode_cmd->pixel_format, i) + + mode_cmd->offsets[i]; + + if (obj->size < min_size) { + drm_gem_object_unreference_unlocked(obj); + ret = -EINVAL; + goto err_gem_object_unreference; + } + objs[i] = to_drm_gem_cma_obj(obj); + } + + hdlcd_fb = hdlcd_fb_alloc(dev, mode_cmd, objs, i); + if (IS_ERR(hdlcd_fb)) { + ret = PTR_ERR(hdlcd_fb); + goto err_gem_object_unreference; + } + + return &hdlcd_fb->fb; + +err_gem_object_unreference: + for (i--; i >= 0; i--) + drm_gem_object_unreference_unlocked(&objs[i]->base); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_fb_create); + +/** + * hdlcd_fb_get_gem_obj() - Get CMA GEM object for framebuffer + * @fb: The framebuffer + * @plane: Which plane + * + * Return the CMA GEM object for given framebuffer. + * + * This function will usually be called from the CRTC callback functions. + */ +struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + if (plane >= 4) + return NULL; + + return hdlcd_fb->obj[plane]; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_get_gem_obj); + +#ifdef CONFIG_DEBUG_FS +/* + * hdlcd_fb_describe() - Helper to dump information about a single + * CMA framebuffer object + */ +static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + int i, n = drm_format_num_planes(fb->pixel_format); + + seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, + (char *)&fb->pixel_format); + + for (i = 0; i < n; i++) { + seq_printf(m, " %d: offset=%d pitch=%d, obj: ", + i, fb->offsets[i], fb->pitches[i]); + drm_gem_cma_describe(hdlcd_fb->obj[i], m); + } +} + +/** + * hdlcd_fb_debugfs_show() - Helper to list CMA framebuffer objects + * in debugfs. + */ +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct drm_framebuffer *fb; + + mutex_lock(&dev->mode_config.fb_lock); + drm_for_each_fb(fb, dev) + hdlcd_fb_describe(fb, m); + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show); +#endif + +static struct fb_ops hdlcd_drm_fbdev_ops = { + .owner = THIS_MODULE, + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, + .fb_check_var = hdlcd_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_blank = drm_fb_helper_blank, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_ioctl = hdlcd_fb_ioctl, + .fb_compat_ioctl= hdlcd_fb_ioctl, +}; + +static int hdlcd_drm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev = to_hdlcd_fbdev(helper); + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + struct drm_device *dev = helper->dev; + struct drm_gem_cma_object *obj; + struct drm_framebuffer *fb; + unsigned int bytes_per_pixel; + unsigned long offset; + struct fb_info *fbi; + size_t size; + int ret; + + DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n", + sizes->surface_width, sizes->surface_height, + sizes->surface_bpp); + + bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = mode_cmd.pitches[0] * mode_cmd.height * MAX_FRAMES; + obj = drm_gem_cma_create(dev, size); + if (IS_ERR(obj)) + return -ENOMEM; + + fbi = drm_fb_helper_alloc_fbi(helper); + if (IS_ERR(fbi)) { + ret = PTR_ERR(fbi); + goto err_drm_gem_cma_free_object; + } + + hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1); + if (IS_ERR(hdlcd_fbdev->fb)) { + dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n"); + ret = PTR_ERR(hdlcd_fbdev->fb); + goto err_fb_info_destroy; + } + + fb = &hdlcd_fbdev->fb->fb; + helper->fb = fb; + + fbi->par = helper; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->fbops = &hdlcd_drm_fbdev_ops; + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + offset = fbi->var.xoffset * bytes_per_pixel; + offset += fbi->var.yoffset * fb->pitches[0]; + + dev->mode_config.fb_base = (resource_size_t)obj->paddr; + fbi->screen_base = obj->vaddr + offset; + fbi->fix.smem_start = (unsigned long)(obj->paddr + offset); + fbi->screen_size = size; + fbi->fix.smem_len = size; + fbi->var.yres_virtual = fbi->var.yres * MAX_FRAMES; + + return 0; + +err_fb_info_destroy: + drm_fb_helper_release_fbi(helper); +err_drm_gem_cma_free_object: + drm_gem_cma_free_object(&obj->base); + return ret; +} + +static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_drm_fbdev_create, +}; + +/** + * hdlcd_drm_fbdev_init() - Allocate and initializes a hdlcd_drm_fbdev struct + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device + * @num_crtc: Number of CRTCs + * @max_conn_count: Maximum number of connectors + * + * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev; + struct drm_fb_helper *helper; + int ret; + + hdlcd_fbdev = kzalloc(sizeof(*hdlcd_fbdev), GFP_KERNEL); + if (!hdlcd_fbdev) { + dev_err(dev->dev, "Failed to allocate drm fbdev.\n"); + return ERR_PTR(-ENOMEM); + } + + helper = &hdlcd_fbdev->fb_helper; + + drm_fb_helper_prepare(dev, helper, &hdlcd_fb_helper_funcs); + + ret = drm_fb_helper_init(dev, helper, num_crtc, max_conn_count); + if (ret < 0) { + dev_err(dev->dev, "Failed to initialize drm fb helper.\n"); + goto err_free; + } + + ret = drm_fb_helper_single_add_all_connectors(helper); + if (ret < 0) { + dev_err(dev->dev, "Failed to add connectors.\n"); + goto err_drm_fb_helper_fini; + + } + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + + ret = drm_fb_helper_initial_config(helper, preferred_bpp); + if (ret < 0) { + dev_err(dev->dev, "Failed to set initial hw configuration.\n"); + goto err_drm_fb_helper_fini; + } + + return hdlcd_fbdev; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(helper); +err_free: + kfree(hdlcd_fbdev); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init); + +/** + * hdlcd_drm_fbdev_fini() - Free hdlcd_drm_fbdev struct + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct + */ +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + drm_fb_helper_unregister_fbi(&hdlcd_fbdev->fb_helper); + drm_fb_helper_release_fbi(&hdlcd_fbdev->fb_helper); + + if (hdlcd_fbdev->fb) { + drm_framebuffer_unregister_private(&hdlcd_fbdev->fb->fb); + hdlcd_fb_destroy(&hdlcd_fbdev->fb->fb); + } + + drm_fb_helper_fini(&hdlcd_fbdev->fb_helper); + kfree(hdlcd_fbdev); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_fini); + +/** + * hdlcd_drm_fbdev_restore_mode() - Restores initial framebuffer mode + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the DRM drivers lastclose callback. + */ +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_restore_fbdev_mode_unlocked(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_restore_mode); + +/** + * hdlcd_drm_fbdev_hotplug_event() - Poll for hotpulug events + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the DRM drivers output_poll_changed + * callback. + */ +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_hotplug_event(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_hotplug_event); + + +/****************************************************************************** + * IOCTL Interface + ******************************************************************************/ + +/* + * Used for sharing buffers with Mali userspace + */ +struct fb_dmabuf_export { + uint32_t fd; + uint32_t flags; +}; + +#define FBIOGET_DMABUF _IOR('F', 0x21, struct fb_dmabuf_export) + +static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct fb_dmabuf_export ebuf; + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_cma_object *obj = hdlcd->fbdev->fb->obj[0]; + struct dma_buf *dma_buf; + uint32_t fd; + + if (copy_from_user(&ebuf, argp, sizeof(ebuf))) + return -EFAULT; + + /* + * We need a reference on the gem object. This will be released by + * drm_gem_dmabuf_release when the file descriptor is closed. + */ + drm_gem_object_reference(&obj->base); + + dma_buf = drm_gem_prime_export(helper->dev, &obj->base, ebuf.flags | O_RDWR); + if (!dma_buf) { + dev_info(info->dev, "Failed to export DMA buffer\n"); + goto err_export; + } + + fd = dma_buf_fd(dma_buf, O_CLOEXEC); + if (fd < 0) { + dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n"); + goto err_export_fd; + } + ebuf.fd = fd; + + if (copy_to_user(argp, &ebuf, sizeof(ebuf))) + goto err_export_fd; + + return 0; + +err_export_fd: + dma_buf_put(dma_buf); +err_export: + drm_gem_object_unreference(&obj->base); + return -EFAULT; +} + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGET_DMABUF: + return hdlcd_get_dmabuf_ioctl(info, cmd, arg); + case FBIO_WAITFORVSYNC: + return 0; /* Nothing to do as we wait when page flipping anyway */ + default: + printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd); + } + + return -EFAULT; +} diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.h b/drivers/gpu/drm/arm/hdlcd_fb_helper.h new file mode 100644 index 000000000000..23e268db972f --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h @@ -0,0 +1,31 @@ +#ifndef __DRM_FB_CMA_HELPER_H__ +#define __DRM_FB_CMA_HELPER_H__ + +struct hdlcd_drm_fbdev; +struct drm_gem_cma_object; + +struct drm_framebuffer; +struct drm_device; +struct drm_file; +struct drm_mode_fb_cmd2; + +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count); +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev); + +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev); +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev); + +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd); + +struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane); + +#ifdef CONFIG_DEBUG_FS +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg); +#endif + +#endif + diff --git a/drivers/gpu/drm/arm/hdlcd_regs.h b/drivers/gpu/drm/arm/hdlcd_regs.h new file mode 100644 index 000000000000..66799ebef6d3 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_regs.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller register definition + */ + +#ifndef __HDLCD_REGS_H__ +#define __HDLCD_REGS_H__ + +/* register offsets */ +#define HDLCD_REG_VERSION 0x0000 /* ro */ +#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */ +#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */ +#define HDLCD_REG_INT_MASK 0x0018 /* rw */ +#define HDLCD_REG_INT_STATUS 0x001c /* ro */ +#define HDLCD_REG_FB_BASE 0x0100 /* rw */ +#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */ +#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */ +#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */ +#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */ +#define HDLCD_REG_V_SYNC 0x0200 /* rw */ +#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */ +#define HDLCD_REG_V_DATA 0x0208 /* rw */ +#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */ +#define HDLCD_REG_H_SYNC 0x0210 /* rw */ +#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */ +#define HDLCD_REG_H_DATA 0x0218 /* rw */ +#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */ +#define HDLCD_REG_POLARITIES 0x0220 /* rw */ +#define HDLCD_REG_COMMAND 0x0230 /* rw */ +#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */ +#define HDLCD_REG_RED_SELECT 0x0244 /* rw */ +#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */ +#define HDLCD_REG_BLUE_SELECT 0x024c /* rw */ + +/* version */ +#define HDLCD_PRODUCT_ID 0x1CDC0000 +#define HDLCD_PRODUCT_MASK 0xFFFF0000 +#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00 +#define HDLCD_VERSION_MINOR_MASK 0x000000FF + +/* interrupts */ +#define HDLCD_INTERRUPT_DMA_END (1 << 0) +#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1) +#define HDLCD_INTERRUPT_VSYNC (1 << 2) +#define HDLCD_INTERRUPT_UNDERRUN (1 << 3) +#define HDLCD_DEBUG_INT_MASK (HDLCD_INTERRUPT_DMA_END | \ + HDLCD_INTERRUPT_BUS_ERROR | \ + HDLCD_INTERRUPT_UNDERRUN) + +/* polarities */ +#define HDLCD_POLARITY_VSYNC (1 << 0) +#define HDLCD_POLARITY_HSYNC (1 << 1) +#define HDLCD_POLARITY_DATAEN (1 << 2) +#define HDLCD_POLARITY_DATA (1 << 3) +#define HDLCD_POLARITY_PIXELCLK (1 << 4) + +/* commands */ +#define HDLCD_COMMAND_DISABLE (0 << 0) +#define HDLCD_COMMAND_ENABLE (1 << 0) + +/* pixel format */ +#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31) +#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31) +#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3) + +/* bus options */ +#define HDLCD_BUS_BURST_MASK 0x01f +#define HDLCD_BUS_MAX_OUTSTAND 0xf00 +#define HDLCD_BUS_BURST_NONE (0 << 0) +#define HDLCD_BUS_BURST_1 (1 << 0) +#define HDLCD_BUS_BURST_2 (1 << 1) +#define HDLCD_BUS_BURST_4 (1 << 2) +#define HDLCD_BUS_BURST_8 (1 << 3) +#define HDLCD_BUS_BURST_16 (1 << 4) + +/* Max resolution supported is 4096x4096, 32bpp */ +#define HDLCD_MAX_XRES 4096 +#define HDLCD_MAX_YRES 4096 + +#define NR_PALETTE 256 + +#endif /* __HDLCD_REGS_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c new file mode 100644 index 000000000000..c03d367cdf9b --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_vexpress_encoder.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include <linux/vexpress.h> + +#include "hdlcd_drv.h" + +/* + * use the functionality of vexpress-config to set the output mode + * for HDLCD on Versatile Express boards that lack proper control + * of the DDC i2c chip. + */ + +//static struct vexpress_config_func *vconfig_func; + +/* + * Predefined modes that are available through the VExpress micro + */ +static const struct { + int hsize, vsize, vrefresh, dvimode; +} vexpress_dvimodes[] = { + { 640, 480, 60, 0 }, /* VGA */ + { 800, 600, 60, 1 }, /* SVGA */ + { 1024, 768, 60, 2 }, /* XGA */ + { 1280, 1024, 60, 3 }, /* SXGA */ + { 1600, 1200, 60, 4 }, /* UXGA */ + { 1920, 1080, 60, 5 }, /* HD1080 */ +}; + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ +} + +static enum drm_connector_status +hdlcd_connector_detect(struct drm_connector *connector, bool force) +{ + //if (vconfig_func) + return connector_status_connected; + //return connector_status_disconnected; +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static int hdlcd_vexpress_con_get_modes(struct drm_connector *connector) +{ + int i; + struct drm_display_mode *mode; + + /* Add the predefined modes */ + for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) { + mode = drm_mode_find_dmt(connector->dev, + vexpress_dvimodes[i].hsize, + vexpress_dvimodes[i].vsize, + vexpress_dvimodes[i].vrefresh, false); + if (!mode) + continue; + /* prefer the 1280x1024 mode */ + if (i == 3) + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + } + + return i; +} + +/* + * mode valid is only called for detected modes and we know that + * the restricted list is correct ;) + */ +static int hdlcd_vexpress_con_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs hdlcd_vexpress_con_helper_funcs = { + .get_modes = hdlcd_vexpress_con_get_modes, + .mode_valid = hdlcd_vexpress_con_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + + +static void hdlcd_vexpress_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); + /*if (vconfig_func) + vexpress_config_func_put(vconfig_func);*/ +} + +static const struct drm_encoder_funcs hdlcd_vexpress_encoder_funcs = { + .destroy = hdlcd_vexpress_encoder_destroy, +}; + +static void hdlcd_vexpress_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* VExpress micro has no support for DPMS */ +} + +static bool hdlcd_vexpress_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needs to be done here */ + return true; +} + +static void hdlcd_vexpress_encoder_prepare(struct drm_encoder *encoder) +{ + hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_vexpress_encoder_commit(struct drm_encoder *encoder) +{ + hdlcd_vexpress_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void hdlcd_vexpress_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /*if (vconfig_func) { + int i, vrefresh = drm_mode_vrefresh(mode); + + for (i = 0; i < ARRAY_SIZE(vexpress_dvimodes); i++) { + if (vexpress_dvimodes[i].hsize != mode->hdisplay) + continue; + if (vexpress_dvimodes[i].vsize != mode->vdisplay) + continue; + if (vexpress_dvimodes[i].vrefresh != vrefresh) + continue; + + vexpress_config_write(vconfig_func, 0, + vexpress_dvimodes[i].dvimode); + return; + } + }*/ +} + +static const struct drm_encoder_helper_funcs +hdlcd_vexpress_encoder_helper_funcs = { + .dpms = hdlcd_vexpress_encoder_dpms, + .mode_fixup = hdlcd_vexpress_encoder_mode_fixup, + .prepare = hdlcd_vexpress_encoder_prepare, + .commit = hdlcd_vexpress_encoder_commit, + .mode_set = hdlcd_vexpress_encoder_mode_set, +}; + +static const struct of_device_id vexpress_dvi_match[] = { + { .compatible = "arm,vexpress-dvimode" }, + {} +}; + +int hdlcd_create_vexpress_connector(struct drm_device *dev, + struct hdlcd_drm_private *hdlcd) +{ + int err; + struct drm_connector *connector; + struct device_node *node; + struct drm_encoder *encoder; + + node = of_find_matching_node(NULL, vexpress_dvi_match); + if (!node) + return -ENXIO; + + /* + vconfig_func = vexpress_config_func_get_by_node(node); + if (!vconfig_func) { + DRM_ERROR("failed to get an output connector\n"); + return -ENXIO; + }*/ + + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + if (!encoder) { + err = -ENOMEM; + goto encoder_alloc_fail; + } + + encoder->possible_crtcs = 1; + encoder->possible_clones = 0; + err = drm_encoder_init(dev, encoder, &hdlcd_vexpress_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (err) + goto encoder_init_fail; + + drm_encoder_helper_add(encoder, &hdlcd_vexpress_encoder_helper_funcs); + + connector = kzalloc(sizeof(*connector), GFP_KERNEL); + if (!connector) { + err = -ENOMEM; + goto connector_alloc_err; + } + + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + err = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + if (err) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_vexpress_con_helper_funcs); + + connector->encoder = encoder; + err = drm_mode_connector_attach_encoder(connector, encoder); + if (err) + goto connector_attach_err; + + return 0; + +connector_attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(connector); +connector_alloc_err: + drm_encoder_cleanup(encoder); +encoder_init_fail: + kfree(encoder); +encoder_alloc_fail: + //vexpress_config_func_put(vconfig_func); + + return err; +} diff --git a/drivers/gpu/drm/arm/hdlcd_virt_encoder.c b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c new file mode 100644 index 000000000000..2200c4a46b42 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_virt_encoder.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +#include "hdlcd_drv.h" + +struct hdlcd_connector { + struct drm_connector connector; + struct display_timings *timings; +}; + +#define conn_to_hdlcd(x) container_of(x, struct hdlcd_connector, connector) + +static void hdlcd_connector_destroy(struct drm_connector *connector) +{ + struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector); + + drm_connector_cleanup(connector); + kfree(hdlcd); +} + +static enum drm_connector_status hdlcd_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs hdlcd_connector_funcs = { + .destroy = hdlcd_connector_destroy, + .dpms = drm_helper_connector_dpms, + .detect = hdlcd_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, +}; + +static int hdlcd_connector_get_modes(struct drm_connector *connector) +{ + struct hdlcd_connector *hdlcd = conn_to_hdlcd(connector); + struct display_timings *timings = hdlcd->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct videomode vm; + + if (videomode_from_timings(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER; + if (timings->native_mode == i) + mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int hdlcd_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static const struct drm_connector_helper_funcs hdlcd_virt_con_helper_funcs = { + .get_modes = hdlcd_connector_get_modes, + .mode_valid = hdlcd_connector_mode_valid, + .best_encoder = hdlcd_connector_best_encoder, +}; + +static void hdlcd_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_funcs hdlcd_encoder_funcs = { + .destroy = hdlcd_encoder_destroy, +}; + +static void hdlcd_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool hdlcd_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void hdlcd_encoder_prepare(struct drm_encoder *encoder) +{ + hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_encoder_commit(struct drm_encoder *encoder) +{ + hdlcd_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void hdlcd_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_helper_funcs hdlcd_encoder_helper_funcs = { + .dpms = hdlcd_encoder_dpms, + .mode_fixup = hdlcd_encoder_mode_fixup, + .prepare = hdlcd_encoder_prepare, + .commit = hdlcd_encoder_commit, + .mode_set = hdlcd_encoder_mode_set, +}; + +int hdlcd_create_virtual_connector(struct drm_device *dev) +{ + struct drm_encoder *encoder; + struct hdlcd_connector *hdlcdc; + struct drm_connector *connector; + int ret; + + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return -ENOMEM; + + encoder->possible_crtcs = 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(dev, encoder, &hdlcd_encoder_funcs, + DRM_MODE_ENCODER_VIRTUAL); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &hdlcd_encoder_helper_funcs); + + hdlcdc = kzalloc(sizeof(*hdlcdc), GFP_KERNEL); + if (!hdlcdc) { + ret = -ENOMEM; + goto connector_alloc_err; + } + + hdlcdc->timings = of_get_display_timings(dev->platformdev->dev.of_node); + if (!hdlcdc->timings) { + DRM_ERROR("failed to get display panel timings\n"); + ret = -ENXIO; + goto connector_init_err; + } + + connector = &hdlcdc->connector; + + /* bogus values, pretend we're a 24" screen */ + connector->display_info.width_mm = 519; + connector->display_info.height_mm = 324; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + ret = drm_connector_init(dev, connector, &hdlcd_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &hdlcd_virt_con_helper_funcs); + + connector->encoder = encoder; + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + + return ret; + +attach_err: + drm_connector_cleanup(connector); +connector_init_err: + kfree(hdlcdc); +connector_alloc_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + kfree(encoder); + + return ret; +}; diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed63a001..338d224e1e62 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -17,6 +17,9 @@ config DRM_I2C_CH7006 This driver is currently only useful if you're also using the nouveau driver. +config DRM_I2C_DUMMY + tristate "Dummy stub driver" + config DRM_I2C_SIL164 tristate "Silicon Image sil164 TMDS transmitter" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index f897c2cfd2d1..59aea5aa1fff 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -5,6 +5,8 @@ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o ch7006-y := ch7006_drv.o ch7006_mode.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o +obj-$(CONFIG_DRM_I2C_DUMMY) += dummy_drm_i2c_drv.o + sil164-y := sil164_drv.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o diff --git a/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c new file mode 100644 index 000000000000..7817c802a6e3 --- /dev/null +++ b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c @@ -0,0 +1,454 @@ +/* + * This file was originally based on tda998x_drv.c which has the following + * copyright and licence... + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/component.h> +#include <linux/module.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> + +struct dummy_priv { + struct drm_encoder *encoder; +}; + +struct dummy_encoder_params { +}; + +#define to_dummy_priv(x) ((struct dummy_priv *)to_encoder_slave(x)->slave_priv) + +/* DRM encoder functions */ + +static void dummy_encoder_set_config(struct dummy_priv *priv, + const struct dummy_encoder_params *p) +{ +} + +static void dummy_encoder_dpms(struct dummy_priv *priv, int mode) +{ +} + +static void +dummy_encoder_save(struct drm_encoder *encoder) +{ +} + +static void +dummy_encoder_restore(struct drm_encoder *encoder) +{ +} + +static bool +dummy_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int dummy_encoder_mode_valid(struct dummy_priv *priv, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void +dummy_encoder_mode_set(struct dummy_priv *priv, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static enum drm_connector_status +dummy_encoder_detect(struct dummy_priv *priv) +{ + return connector_status_connected; +} + + +static const u8 edid_1024x768[] = { + /* + * These values are a copy of Documentation/EDID/1024x768.c + * produced by executing "make -C Documentation/EDID" + */ + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x31, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x16, 0x01, 0x03, 0x6d, 0x23, 0x1a, 0x78, + 0xea, 0x5e, 0xc0, 0xa4, 0x59, 0x4a, 0x98, 0x25, + 0x20, 0x50, 0x54, 0x00, 0x08, 0x00, 0x61, 0x40, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x64, 0x19, + 0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x08, 0x90, + 0x36, 0x00, 0x63, 0x0a, 0x11, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x4c, 0x69, 0x6e, + 0x75, 0x78, 0x20, 0x23, 0x30, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x3b, + 0x3d, 0x2f, 0x31, 0x07, 0x00, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x58, + 0x47, 0x41, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x55 +}; + +static int +dummy_encoder_get_modes(struct dummy_priv *priv, + struct drm_connector *connector) +{ + struct edid *edid = (struct edid *)edid_1024x768; + int n; + + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + + return n; +} + +static int +dummy_encoder_set_property(struct drm_encoder *encoder, + struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + return 0; +} + +static void dummy_destroy(struct dummy_priv *priv) +{ +} + +/* Slave encoder support */ + +static void +dummy_encoder_slave_set_config(struct drm_encoder *encoder, void *params) +{ + dummy_encoder_set_config(to_dummy_priv(encoder), params); +} + +static void dummy_encoder_slave_destroy(struct drm_encoder *encoder) +{ + struct dummy_priv *priv = to_dummy_priv(encoder); + + dummy_destroy(priv); + drm_i2c_encoder_destroy(encoder); + kfree(priv); +} + +static void dummy_encoder_slave_dpms(struct drm_encoder *encoder, int mode) +{ + dummy_encoder_dpms(to_dummy_priv(encoder), mode); +} + +static int dummy_encoder_slave_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + return dummy_encoder_mode_valid(to_dummy_priv(encoder), mode); +} + +static void +dummy_encoder_slave_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + dummy_encoder_mode_set(to_dummy_priv(encoder), mode, adjusted_mode); +} + +static enum drm_connector_status +dummy_encoder_slave_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + return dummy_encoder_detect(to_dummy_priv(encoder)); +} + +static int dummy_encoder_slave_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + return dummy_encoder_get_modes(to_dummy_priv(encoder), connector); +} + +static int +dummy_encoder_slave_create_resources(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + return 0; +} + +static struct drm_encoder_slave_funcs dummy_encoder_slave_funcs = { + .set_config = dummy_encoder_slave_set_config, + .destroy = dummy_encoder_slave_destroy, + .dpms = dummy_encoder_slave_dpms, + .save = dummy_encoder_save, + .restore = dummy_encoder_restore, + .mode_fixup = dummy_encoder_mode_fixup, + .mode_valid = dummy_encoder_slave_mode_valid, + .mode_set = dummy_encoder_slave_mode_set, + .detect = dummy_encoder_slave_detect, + .get_modes = dummy_encoder_slave_get_modes, + .create_resources = dummy_encoder_slave_create_resources, + .set_property = dummy_encoder_set_property, +}; + +/* I2C driver functions */ + +static int dummy_encoder_init(struct i2c_client *client, + struct drm_device *dev, + struct drm_encoder_slave *encoder_slave) +{ + struct dummy_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->encoder = &encoder_slave->base; + + encoder_slave->slave_priv = priv; + encoder_slave->slave_funcs = &dummy_encoder_slave_funcs; + + return 0; +} + +struct dummy_priv2 { + struct dummy_priv base; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +#define conn_to_dummy_priv2(x) \ + container_of(x, struct dummy_priv2, connector); + +#define enc_to_dummy_priv2(x) \ + container_of(x, struct dummy_priv2, encoder); + +static void dummy_encoder2_dpms(struct drm_encoder *encoder, int mode) +{ + struct dummy_priv2 *priv = enc_to_dummy_priv2(encoder); + + dummy_encoder_dpms(&priv->base, mode); +} + +static void dummy_encoder_prepare(struct drm_encoder *encoder) +{ + dummy_encoder2_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void dummy_encoder_commit(struct drm_encoder *encoder) +{ + dummy_encoder2_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void dummy_encoder2_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dummy_priv2 *priv = enc_to_dummy_priv2(encoder); + + dummy_encoder_mode_set(&priv->base, mode, adjusted_mode); +} + +static const struct drm_encoder_helper_funcs dummy_encoder_helper_funcs = { + .dpms = dummy_encoder2_dpms, + .save = dummy_encoder_save, + .restore = dummy_encoder_restore, + .mode_fixup = dummy_encoder_mode_fixup, + .prepare = dummy_encoder_prepare, + .commit = dummy_encoder_commit, + .mode_set = dummy_encoder2_mode_set, +}; + +static void dummy_encoder_destroy(struct drm_encoder *encoder) +{ + struct dummy_priv2 *priv = enc_to_dummy_priv2(encoder); + + dummy_destroy(&priv->base); + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs dummy_encoder_funcs = { + .destroy = dummy_encoder_destroy, +}; + +static int dummy_connector_get_modes(struct drm_connector *connector) +{ + struct dummy_priv2 *priv = conn_to_dummy_priv2(connector); + + return dummy_encoder_get_modes(&priv->base, connector); +} + +static int dummy_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct dummy_priv2 *priv = conn_to_dummy_priv2(connector); + + return dummy_encoder_mode_valid(&priv->base, mode); +} + +static struct drm_encoder * +dummy_connector_best_encoder(struct drm_connector *connector) +{ + struct dummy_priv2 *priv = conn_to_dummy_priv2(connector); + + return &priv->encoder; +} + +static +const struct drm_connector_helper_funcs dummy_connector_helper_funcs = { + .get_modes = dummy_connector_get_modes, + .mode_valid = dummy_connector_mode_valid, + .best_encoder = dummy_connector_best_encoder, +}; + +static enum drm_connector_status +dummy_connector_detect(struct drm_connector *connector, bool force) +{ + struct dummy_priv2 *priv = conn_to_dummy_priv2(connector); + + return dummy_encoder_detect(&priv->base); +} + +static void dummy_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs dummy_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dummy_connector_detect, + .destroy = dummy_connector_destroy, +}; + +static int dummy_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct dummy_priv2 *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + priv->base.encoder = &priv->encoder; + priv->encoder.possible_crtcs = 1 << 0; + + drm_encoder_helper_add(&priv->encoder, &dummy_encoder_helper_funcs); + ret = drm_encoder_init(drm, &priv->encoder, &dummy_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret) + goto err_encoder; + + drm_connector_helper_add(&priv->connector, + &dummy_connector_helper_funcs); + ret = drm_connector_init(drm, &priv->connector, + &dummy_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + goto err_connector; + + ret = drm_connector_register(&priv->connector); + if (ret) + goto err_sysfs; + + priv->connector.encoder = &priv->encoder; + drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder); + + return 0; + +err_sysfs: + drm_connector_cleanup(&priv->connector); +err_connector: + drm_encoder_cleanup(&priv->encoder); +err_encoder: + dummy_destroy(&priv->base); + return ret; +} + +static void dummy_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dummy_priv2 *priv = dev_get_drvdata(dev); + + drm_connector_cleanup(&priv->connector); + drm_encoder_cleanup(&priv->encoder); + dummy_destroy(&priv->base); +} + +static const struct component_ops dummy_ops = { + .bind = dummy_bind, + .unbind = dummy_unbind, +}; + +static int +dummy_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + return component_add(&client->dev, &dummy_ops); +} + +static int dummy_remove(struct i2c_client *client) +{ + component_del(&client->dev, &dummy_ops); + return 0; +} + +static const struct of_device_id dummy_of_ids[] = { + { .compatible = "sil,sii9022-tpi", }, + { } +}; +MODULE_DEVICE_TABLE(of, dummy_of_ids); + +static struct i2c_device_id dummy_ids[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, dummy_ids); + +static struct drm_i2c_encoder_driver dummy_driver = { + .i2c_driver = { + .probe = dummy_probe, + .remove = dummy_remove, + .driver = { + .name = "dummy_drm_i2c", + .of_match_table = dummy_of_ids + }, + .id_table = dummy_ids, + }, + .encoder_init = dummy_encoder_init, +}; + +/* Module initialization */ + +static int __init +dummy_init(void) +{ + return drm_i2c_encoder_register(THIS_MODULE, &dummy_driver); +} + +static void __exit +dummy_exit(void) +{ + drm_i2c_encoder_unregister(&dummy_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(dummy_init); +module_exit(dummy_exit); diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 266102b531a3..ba84a4001dea 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -35,7 +35,13 @@ CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_FB=y CONFIG_FB_ARMCLCD=y -CONFIG_FB_ARMHDLCD=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_CMA=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_I2C_VERSATILE=y +CONFIG_DRM_I2C_DUMMY=y CONFIG_LOGO=y # CONFIG_LOGO_LINUX_MONO is not set # CONFIG_LOGO_LINUX_VGA16 is not set diff --git a/linaro/configs/vexpress64.conf b/linaro/configs/vexpress64.conf index 769f2cc86fff..1744a88ba417 100644 --- a/linaro/configs/vexpress64.conf +++ b/linaro/configs/vexpress64.conf @@ -85,3 +85,25 @@ CONFIG_PM_RUNTIME=y CONFIG_TEE=y CONFIG_OPTEE=y CONFIG_DEVMEM=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_DRM_VIRTUAL_HDLCD=y +CONFIG_CMA=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_DRM_I2C_NXP_TDA998X=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SEQUENCER=y +CONFIG_SND_SEQ_DUMMY=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_SEQUENCER_OSS=y +# CONFIG_SND_USB is not set +CONFIG_SND_SOC=y +CONFIG_SND_DESIGNWARE_I2S=y +CONFIG_SND_SOC_HDMI_CODEC=y +CONFIG_SND_SOC_SPDIF=y +CONFIG_SND_SIMPLE_CARD=y +CONFIG_DMADEVICES=y +CONFIG_PL330_DMA=y |