diff options
author | Jon Medhurst <tixy@linaro.org> | 2016-06-13 09:31:25 +0100 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2016-06-13 09:31:25 +0100 |
commit | 166d695b439ca3bcb21aa7dde5602dd3b4e27665 (patch) | |
tree | c4b49f8ca7058846133f8890916e758992918157 | |
parent | 87e95640d5d39cabb5ad733c6403241f70fa033f (diff) | |
parent | 6efb8cd74695efeb60f52450e7a7aec5792a164d (diff) |
Merge branch 'latest-armlt-hdlcd' into latest-armlt
Conflicts:
linaro/configs/vexpress64.conf
26 files changed, 2167 insertions, 65 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/tda998x.txt b/Documentation/devicetree/bindings/display/bridge/tda998x.txt index e178e6b9f9ee..d063bac4ff36 100644 --- a/Documentation/devicetree/bindings/display/bridge/tda998x.txt +++ b/Documentation/devicetree/bindings/display/bridge/tda998x.txt @@ -21,6 +21,15 @@ Optional properties: - video-ports: 24 bits value which defines how the video controller output is wired to the TDA998x input - default: <0x230145> + - audio-ports: one or two values corresponding to entries in + the audio-port-names property. + + - audio-port-names: must contain "i2s", "spdif" entries + matching entries in the audio-ports property. + + - #sound-dai-cells: must be set to <1> for use with the simple-card. + The DAI 0 is the I2S input and the DAI 1 is the S/PDIF input. + Example: tda998x: hdmi-encoder { @@ -30,4 +39,9 @@ Example: interrupts = <27 2>; /* falling edge */ pinctrl-0 = <&pmx_camera>; pinctrl-names = "default"; + + audio-ports = <0x03>, <0x04>; + audio-port-names = "i2s", "spdif"; + #sound-dai-cells = <1>; + }; }; diff --git a/Documentation/devicetree/bindings/display/virtual-encoder.txt b/Documentation/devicetree/bindings/display/virtual-encoder.txt new file mode 100644 index 000000000000..3a9b8221c3ec --- /dev/null +++ b/Documentation/devicetree/bindings/display/virtual-encoder.txt @@ -0,0 +1,74 @@ +DRM Virtual Encoder + +The DRM Virtual Encoder is a component-based basic encoder that fetches +the display timings information from the device tree and "discovers" a +DRM output with the given data. It is helpful in a simulated environment +where there is no actual hardware to be probed and the configuration of +the display happens outside the kernel world. + +Required properties: + - compatible: should be "drm,virtual-encoder" + +Required sub-nodes: + - display-timings: node describing the virtual output timings information, + as specified in panel/display-timing.txt file. + - port: the input port connection as modelled using the OF graph bindings + specified in Documentation/devicetree/bindings/graph.txt + + +Example: + +/ { + ... + + vencoder { + compatible = "drm,virtual-encoder"; + display-timings { + native-mode = <&timing1>; + timing0: timing@0 { + /* 640x480 framebuffer */ + clock-frequency = <23750>; + hactive = <640>; + vactive = <480>; + hfront-porch = <48>; + hback-porch = <16>; + hsync-len = <96>; + vfront-porch = <33>; + vback-porch = <9>; + vsync-len = <3>; + }; + timing1: timing@1 { + /* 1280x720 framebuffer */ + clock-frequency = <74440000>; + hactive = <1280>; + vactive = <720>; + hfront-porch = <56>; + hback-porch = <192>; + hsync-len = <136>; + vfront-porch = <1>; + vback-porch = <22>; + vsync-len = <3>; + }; + }; + + port { + vencoder_in: endpoint { + remote-endpoint = <&driver_out>; + }; + }; + }; + + drm_driver: driver@f00bad { + ... + + port { + driver_out: endpoint { + remote-endpoint = <&vencoder_in>; + }; + }; + + ... + }; + + ... +}; diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index 3086efacd00e..54939131af62 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 102838fcc588..cd5780a51114 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 = <&hdlcd_clk>; 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 { @@ -294,3 +310,17 @@ <0 3 &gic 0 39 4>; }; }; + +&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 0205c97efdef..af05afbd2377 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 = <&hdlcd_clk>; 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 { @@ -652,3 +668,17 @@ <0 3 &gic 0 39 4>; }; }; + +&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 1acecaf4b13d..6fad3c315c76 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 = <&hdlcd_clk>; 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 { @@ -264,3 +280,17 @@ <0 3 &gic 0 39 4>; }; }; + +&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 5efe64db8371..0979a8f9de9d 100644 --- a/arch/arm64/boot/dts/arm/juno-base.dtsi +++ b/arch/arm64/boot/dts/arm/juno-base.dtsi @@ -127,7 +127,7 @@ /include/ "juno-clocks.dtsi" - dma@7ff00000 { + dma0: dma@7ff00000 { compatible = "arm,pl330", "arm,primecell"; reg = <0x0 0x7ff00000 0 0x1000>; #dma-cells = <1>; @@ -145,7 +145,7 @@ clocks = <&soc_faxiclk>; clock-names = "apb_pclk"; }; - +/* hdlcd@7ff50000 { compatible = "arm,hdlcd"; reg = <0 0x7ff50000 0 0x1000>; @@ -159,7 +159,7 @@ }; }; }; - +*/ hdlcd@7ff60000 { compatible = "arm,hdlcd"; reg = <0 0x7ff60000 0 0x1000>; @@ -192,16 +192,19 @@ 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>; }; }; }; - +/* hdmi-transmitter@71 { compatible = "nxp,tda998x"; reg = <0x71>; @@ -211,7 +214,7 @@ }; }; }; - }; +*/ }; ohci@7ffb0000 { compatible = "generic-ohci"; @@ -243,6 +246,37 @@ <0x00000008 0x80000000 0x1 0x80000000>; }; + 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 fc357319de35..ec70836d6019 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -228,6 +228,14 @@ config DRM_VGEM as used by Mesa's software renderer for enhanced performance. If M is selected the module will be called vgem. +config DRM_VIRT_ENCODER + tristate "Virtual OF-based encoder" + depends on DRM && OF + help + Choose this option to get a virtual encoder and its associated + connector that will use the device tree to read the display + timings information. If M is selected the module will be called + drm_vencoder. source "drivers/gpu/drm/exynos/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index be43afb08c69..bc6467d62728 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -34,6 +34,9 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o CFLAGS_drm_trace_points.o := -I$(src) +drm_vencoder-y := drm_virtual_encoder.o +obj-$(CONFIG_DRM_VIRT_ENCODER) += drm_vencoder.o + obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_ARM) += arm/ diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile index 89dcb7bab93a..ab024b523e23 100644 --- a/drivers/gpu/drm/arm/Makefile +++ b/drivers/gpu/drm/arm/Makefile @@ -1,2 +1,2 @@ -hdlcd-y := hdlcd_drv.o hdlcd_crtc.o +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_fb_helper.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 index 0813c2f06931..cdbbfb4a0f95 100644 --- a/drivers/gpu/drm/arm/hdlcd_crtc.c +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -14,7 +14,7 @@ #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 "hdlcd_fb_helper.h" #include <drm/drm_gem_cma_helper.h> #include <drm/drm_of.h> #include <drm/drm_plane_helper.h> @@ -88,16 +88,32 @@ static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc) * pixel is outside the visible frame area or when there is a * buffer underrun. */ - hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | + if(!config_enabled(CONFIG_ARM)) { + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | #ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN - 0x00ff0000 | /* show underruns in red */ + 0x00ff0000 | /* show underruns in red */ #endif - ((format->red.length & 0xf) << 8)); - hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | - ((format->green.length & 0xf) << 8)); - hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset | - ((format->blue.length & 0xf) << 8)); - + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, 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, format->red.offset | +#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN + 0x00ff0000 | /* show underruns in red */ +#endif + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->blue.offset | + ((format->blue.length & 0xf) << 8)); + } return 0; } @@ -254,7 +270,7 @@ static void hdlcd_plane_atomic_update(struct drm_plane *plane, src_h = plane->state->src_h >> 16; dest_w = plane->state->crtc_w; dest_h = plane->state->crtc_h; - gem = drm_fb_cma_get_gem_obj(plane->state->fb, 0); + gem = hdlcd_fb_get_gem_obj(plane->state->fb, 0); scanout_start = gem->paddr + plane->state->fb->offsets[0] + plane->state->crtc_y * plane->state->fb->pitches[0] + plane->state->crtc_x * bpp / 8; diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c index a6ca36f0096f..2bec09c771d4 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.c +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -23,7 +23,7 @@ #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 "hdlcd_fb_helper.h" #include <drm/drm_gem_cma_helper.h> #include <drm/drm_of.h> @@ -67,6 +67,12 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags) (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 framebuffer memory resource */ ret = of_reserved_mem_device_init(drm->dev); if (ret && ret != -ENODEV) @@ -88,6 +94,9 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags) goto irq_fail; } + spin_lock_init(&hdlcd->frame_completion_lock); + init_completion(&hdlcd->frame_completion); + return 0; irq_fail: @@ -103,7 +112,7 @@ static void hdlcd_fb_output_poll_changed(struct drm_device *drm) struct hdlcd_drm_private *hdlcd = drm->dev_private; if (hdlcd->fbdev) - drm_fbdev_cma_hotplug_event(hdlcd->fbdev); + hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev); } static int hdlcd_atomic_commit(struct drm_device *dev, @@ -113,7 +122,7 @@ static int hdlcd_atomic_commit(struct drm_device *dev, } static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { - .fb_create = drm_fb_cma_create, + .fb_create = hdlcd_fb_create, .output_poll_changed = hdlcd_fb_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = hdlcd_atomic_commit, @@ -133,7 +142,7 @@ static void hdlcd_lastclose(struct drm_device *drm) { struct hdlcd_drm_private *hdlcd = drm->dev_private; - drm_fbdev_cma_restore_mode(hdlcd->fbdev); + hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev); } static irqreturn_t hdlcd_irq(int irq, void *arg) @@ -141,6 +150,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) struct drm_device *drm = arg; struct hdlcd_drm_private *hdlcd = drm->dev_private; unsigned long irq_status; + unsigned long flags; irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); @@ -161,6 +171,16 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) if (irq_status & HDLCD_INTERRUPT_VSYNC) drm_crtc_handle_vblank(&hdlcd->crtc); + spin_lock_irqsave(&hdlcd->frame_completion_lock, flags); + if (hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS) & HDLCD_INTERRUPT_DMA_END) { + /* Clear DMA_END interrupt here, under frame_completion_lock */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + irq_status &= ~HDLCD_INTERRUPT_DMA_END; + /* Wake up everyone waiting for frame completion */ + complete_all(&hdlcd->frame_completion); + } + spin_unlock_irqrestore(&hdlcd->frame_completion_lock, flags); + /* acknowledge interrupt(s) */ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status); @@ -177,15 +197,18 @@ static void hdlcd_irq_preinstall(struct drm_device *drm) static int hdlcd_irq_postinstall(struct drm_device *drm) { -#ifdef CONFIG_DEBUG_FS struct hdlcd_drm_private *hdlcd = drm->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); -#endif + return 0; } @@ -200,12 +223,34 @@ static void hdlcd_irq_uninstall(struct drm_device *drm) irq_mask &= ~HDLCD_DEBUG_INT_MASK; #endif - /* disable vsync interrupts */ - irq_mask &= ~HDLCD_INTERRUPT_VSYNC; + /* disable vsync and dma interrupts */ + irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END); hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); } +void hdlcd_wait_for_frame_completion(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + + if (drm_vblank_get(drm, 0)) + return; /* vblank interrupts not available so don't try and wait */ + + /* + * Clear pending interrupts and completions so we won't get signalled + * for any earlier frames, + */ + spin_lock_irq(&hdlcd->frame_completion_lock); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + reinit_completion(&hdlcd->frame_completion); + spin_unlock_irq(&hdlcd->frame_completion_lock); + + /* Wait for end of current frame */ + wait_for_completion_interruptible_timeout(&hdlcd->frame_completion, HZ / 10); + + drm_vblank_put(drm, 0); +} + static int hdlcd_enable_vblank(struct drm_device *drm, unsigned int crtc) { struct hdlcd_drm_private *hdlcd = drm->dev_private; @@ -254,7 +299,7 @@ static int hdlcd_show_pxlclock(struct seq_file *m, void *arg) static struct drm_info_list hdlcd_debugfs_list[] = { { "interrupt_count", hdlcd_show_underrun_count, 0 }, { "clocks", hdlcd_show_pxlclock, 0 }, - { "fb", drm_fb_cma_debugfs_show, 0 }, + { "fb", hdlcd_fb_debugfs_show, 0 }, }; static int hdlcd_debugfs_init(struct drm_minor *minor) @@ -327,6 +372,8 @@ static int hdlcd_drm_bind(struct device *dev) struct drm_device *drm; struct hdlcd_drm_private *hdlcd; int ret; + struct device_node *node; + int preferred_bpp; hdlcd = devm_kzalloc(dev, sizeof(*hdlcd), GFP_KERNEL); if (!hdlcd) @@ -369,7 +416,15 @@ static int hdlcd_drm_bind(struct device *dev) drm_mode_config_reset(drm); drm_kms_helper_poll_init(drm); - hdlcd->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc, + /* 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"); + if (node) { + of_node_put(node); + preferred_bpp = 32; /* If Mali present, assume 32bpp */ + } + + hdlcd->fbdev = hdlcd_drm_fbdev_init(drm, preferred_bpp, drm->mode_config.num_crtc, drm->mode_config.num_connector); if (IS_ERR(hdlcd->fbdev)) { @@ -406,7 +461,7 @@ static void hdlcd_drm_unbind(struct device *dev) struct hdlcd_drm_private *hdlcd = drm->dev_private; if (hdlcd->fbdev) { - drm_fbdev_cma_fini(hdlcd->fbdev); + hdlcd_drm_fbdev_fini(hdlcd->fbdev); hdlcd->fbdev = NULL; } drm_kms_helper_poll_fini(drm); @@ -524,7 +579,19 @@ static struct platform_driver hdlcd_platform_driver = { }, }; -module_platform_driver(hdlcd_platform_driver); +static int __init hdlcd_init(void) +{ + return platform_driver_register(&hdlcd_platform_driver); +} + +static void __exit hdlcd_exit(void) +{ + platform_driver_unregister(&hdlcd_platform_driver); +} + +/* need late_initcall() so we load after i2c driver */ +late_initcall(hdlcd_init); +module_exit(hdlcd_exit); MODULE_AUTHOR("Liviu Dudau"); MODULE_DESCRIPTION("ARM HDLCD DRM driver"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h index e3950a071152..e76d99cfeada 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.h +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -8,10 +8,12 @@ struct hdlcd_drm_private { void __iomem *mmio; struct clk *clk; - struct drm_fbdev_cma *fbdev; + struct hdlcd_drm_fbdev *fbdev; struct drm_crtc crtc; struct drm_plane *plane; struct drm_atomic_state *state; + spinlock_t frame_completion_lock; + struct completion frame_completion; #ifdef CONFIG_DEBUG_FS atomic_t buffer_underrun_count; atomic_t bus_error_count; @@ -36,4 +38,6 @@ static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg) int hdlcd_setup_crtc(struct drm_device *dev); void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd); +void hdlcd_wait_for_frame_completion(struct drm_device *drm); + #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..fb05ce1bad84 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c @@ -0,0 +1,799 @@ +/* + * drm kms/fb cma (contiguous memory allocator) helper functions + * + * 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" +#include "hdlcd_regs.h" + +#define DEFAULT_FBDEFIO_DELAY_MS 50 + +#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 4.4 + ******************************************************************************/ + +/** + * 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 4.4 + ******************************************************************************/ + +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; +}; + +/** + * DOC: framebuffer cma helper functions + * + * Provides helper functions for creating a cma (contiguous memory allocator) + * backed framebuffer. + * + * hdlcd_fb_create() is used in the &drm_mode_config_funcs ->fb_create + * callback function to create a cma backed framebuffer. + * + * An fbdev framebuffer backed by cma is also available by calling + * hdlcd_drm_fbdev_init(). hdlcd_drm_fbdev_fini() tears it down. + * If the &drm_framebuffer_funcs ->dirty callback is set, fb_deferred_io + * will be set up automatically. dirty() is called by + * drm_fb_helper_deferred_io() in process context (struct delayed_work). + * + * Example fbdev deferred io code: + * + * static int driver_fbdev_fb_dirty(struct drm_framebuffer *fb, + * struct drm_file *file_priv, + * unsigned flags, unsigned color, + * struct drm_clip_rect *clips, + * unsigned num_clips) + * { + * struct drm_gem_cma_object *cma = hdlcd_fb_get_gem_obj(fb, 0); + * ... push changes ... + * return 0; + * } + * + * static struct drm_framebuffer_funcs driver_fbdev_fb_funcs = { + * .destroy = hdlcd_fb_destroy, + * .create_handle = hdlcd_fb_create_handle, + * .dirty = driver_fbdev_fb_dirty, + * }; + * + * static int driver_fbdev_create(struct drm_fb_helper *helper, + * struct drm_fb_helper_surface_size *sizes) + * { + * return hdlcd_drm_fbdev_create_with_funcs(helper, sizes, + * &driver_fbdev_fb_funcs); + * } + * + * static const struct drm_fb_helper_funcs driver_fb_helper_funcs = { + * .fb_probe = driver_fbdev_create, + * }; + * + * Initialize: + * fbdev = hdlcd_drm_fbdev_init_with_funcs(dev, 16, + * dev->mode_config.num_crtc, + * dev->mode_config.num_connector, + * &driver_fb_helper_funcs); + * + */ + +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); +} + +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); +} +EXPORT_SYMBOL(hdlcd_fb_destroy); + +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); +} +EXPORT_SYMBOL(hdlcd_fb_create_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, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_cma_object **obj, + unsigned int num_planes, const struct drm_framebuffer_funcs *funcs) +{ + 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, 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_with_funcs() - helper function for the + * &drm_mode_config_funcs ->fb_create + * callback function + * + * This can be used to set &drm_framebuffer_funcs for drivers that need the + * dirty() callback. Use hdlcd_fb_create() if you don't need to change + * &drm_framebuffer_funcs. + */ +struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_framebuffer_funcs *funcs) +{ + 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(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, funcs); + 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_with_funcs); + +/** + * hdlcd_fb_create() - &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. Use hdlcd_fb_create_with_funcs() if + * you need to set &drm_framebuffer_funcs ->dirty. + */ +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd) +{ + return hdlcd_fb_create_with_funcs(dev, file_priv, mode_cmd, + &hdlcd_fb_funcs); +} +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 int hdlcd_fb_helper_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *helper = info->par; + struct drm_framebuffer *fb = helper->fb; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + unsigned int depth, bpp; + struct drm_gem_cma_object *gem; + dma_addr_t scanout_start; + int ret; + + ret = hdlcd_fb_helper_check_var(var, info); + if (ret) + return ret; + + 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] + + (var->yoffset * fb->pitches[0]) + (var->xoffset * bpp/8); + + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + + hdlcd_wait_for_frame_completion(helper->dev); + + return 0; +} + +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 = hdlcd_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_deferred_io_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + fb_deferred_io_mmap(info, vma); + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return 0; +} + +static int hdlcd_drm_fbdev_defio_init(struct fb_info *fbi, + struct drm_gem_cma_object *cma_obj) +{ + struct fb_deferred_io *fbdefio; + struct fb_ops *fbops; + + /* + * Per device structures are needed because: + * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap + * fbdefio: individual delays + */ + fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL); + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (!fbdefio || !fbops) { + kfree(fbdefio); + return -ENOMEM; + } + + /* can't be offset from vaddr since dirty() uses cma_obj */ + fbi->screen_buffer = cma_obj->vaddr; + /* fb_deferred_io_fault() needs a physical address */ + fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer)); + + *fbops = *fbi->fbops; + fbi->fbops = fbops; + + fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS); + fbdefio->deferred_io = drm_fb_helper_deferred_io; + fbi->fbdefio = fbdefio; + fb_deferred_io_init(fbi); + fbi->fbops->fb_mmap = hdlcd_drm_fbdev_deferred_io_mmap; + + return 0; +} + +static void hdlcd_drm_fbdev_defio_fini(struct fb_info *fbi) +{ + if (!fbi->fbdefio) + return; + + fb_deferred_io_cleanup(fbi); + kfree(fbi->fbdefio); + kfree(fbi->fbops); +} + +/* + * For use in a (struct drm_fb_helper_funcs *)->fb_probe callback function that + * needs custom struct drm_framebuffer_funcs, like dirty() for deferred_io use. + */ +int hdlcd_drm_fbdev_create_with_funcs(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes, + const struct drm_framebuffer_funcs *funcs) +{ + 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_gem_free_object; + } + + hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1, funcs); + 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; + + if (funcs->dirty) { + ret = hdlcd_drm_fbdev_defio_init(fbi, obj); + if (ret) + goto err_cma_destroy; + } + + return 0; + +err_cma_destroy: + drm_framebuffer_unregister_private(&hdlcd_fbdev->fb->fb); + hdlcd_fb_destroy(&hdlcd_fbdev->fb->fb); +err_fb_info_destroy: + drm_fb_helper_release_fbi(helper); +err_gem_free_object: + drm_gem_object_unreference_unlocked(&obj->base); + return ret; +} +EXPORT_SYMBOL(hdlcd_drm_fbdev_create_with_funcs); + +static int hdlcd_drm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + return hdlcd_drm_fbdev_create_with_funcs(helper, sizes, &hdlcd_fb_funcs); +} + +static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_drm_fbdev_create, +}; + +/** + * hdlcd_drm_fbdev_init_with_funcs() - 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 + * @funcs: fb helper functions, in particular fb_probe() + * + * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs) +{ + 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, 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; + + } + + 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_with_funcs); + +/** + * 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) +{ + return hdlcd_drm_fbdev_init_with_funcs(dev, preferred_bpp, num_crtc, + max_conn_count, &hdlcd_fb_helper_funcs); +} +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); + hdlcd_drm_fbdev_defio_fini(hdlcd_fbdev->fb_helper.fbdev); + 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..a0f49bca4c10 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h @@ -0,0 +1,50 @@ +#ifndef __DRM_FB_CMA_HELPER_H__ +#define __DRM_FB_CMA_HELPER_H__ + +struct hdlcd_drm_fbdev; +struct drm_gem_cma_object; + +struct drm_fb_helper_surface_size; +struct drm_framebuffer_funcs; +struct drm_fb_helper_funcs; +struct drm_framebuffer; +struct drm_fb_helper; +struct drm_device; +struct drm_file; +struct drm_mode_fb_cmd2; + +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs); +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); +int hdlcd_drm_fbdev_create_with_funcs(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes, + const struct drm_framebuffer_funcs *funcs); + +void hdlcd_fb_destroy(struct drm_framebuffer *fb); +int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle); + +struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_framebuffer_funcs *funcs); +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, const 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 +struct seq_file; + +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg); +#endif + +#endif + diff --git a/drivers/gpu/drm/drm_virtual_encoder.c b/drivers/gpu/drm/drm_virtual_encoder.c new file mode 100644 index 000000000000..21522ed9b17a --- /dev/null +++ b/drivers/gpu/drm/drm_virtual_encoder.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2016 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * Dummy encoder and connector that use the OF to "discover" the attached + * display timings. Can be used in situations where the encoder and connector's + * functionality are emulated and no setup steps are needed, or to describe + * attached panels for which no driver exists but can be used without + * additional hardware setup. + * + * The encoder also uses the component framework so that it can be a quick + * replacement for existing drivers when testing in an emulated environment. + * + * 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 <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <linux/component.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +struct drm_virt_priv { + struct drm_connector connector; + struct drm_encoder encoder; + struct display_timings *timings; +}; + +#define connector_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, connector) + +#define encoder_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, encoder) + +static void drm_virtcon_destroy(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + + drm_connector_cleanup(connector); + display_timings_release(conn->timings); +} + +static enum drm_connector_status +drm_virtcon_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs drm_virtcon_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = drm_virtcon_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_virtcon_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int drm_virtcon_get_modes(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + struct display_timings *timings = conn->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 drm_virtcon_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *drm_virtcon_best_encoder(struct drm_connector *connector) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +struct drm_encoder * +drm_virtcon_atomic_best_encoder(struct drm_connector *connector, + struct drm_connector_state *connector_state) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +static const struct drm_connector_helper_funcs drm_virtcon_helper_funcs = { + .get_modes = drm_virtcon_get_modes, + .mode_valid = drm_virtcon_mode_valid, + .best_encoder = drm_virtcon_best_encoder, + .atomic_best_encoder = drm_virtcon_atomic_best_encoder, +}; + +static void drm_vencoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs drm_vencoder_funcs = { + .destroy = drm_vencoder_destroy, +}; + +static void drm_vencoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* nothing needed */ +} + +static bool drm_vencoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void drm_vencoder_prepare(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void drm_vencoder_commit(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void drm_vencoder_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 drm_vencoder_helper_funcs = { + .dpms = drm_vencoder_dpms, + .mode_fixup = drm_vencoder_mode_fixup, + .prepare = drm_vencoder_prepare, + .commit = drm_vencoder_commit, + .mode_set = drm_vencoder_mode_set, +}; + +static int drm_vencoder_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_encoder *encoder; + struct drm_virt_priv *con; + struct drm_connector *connector; + struct drm_device *drm = data; + u32 crtcs = 0; + int ret; + + con = devm_kzalloc(dev, sizeof(*con), GFP_KERNEL); + if (!con) + return -ENOMEM; + + dev_set_drvdata(dev, con); + connector = &con->connector; + encoder = &con->encoder; + + if (dev->of_node) { + struct drm_bridge *bridge; + crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + bridge = of_drm_find_bridge(dev->of_node); + if (bridge) { + ret = drm_bridge_attach(drm, bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge\n"); + return ret; + } + encoder->bridge = bridge; + } + con->timings = of_get_display_timings(dev->of_node); + if (!con->timings) { + dev_err(dev, "failed to get display panel timings\n"); + return ENXIO; + } + } + + /* If no CRTCs were found, fall back to the old encoder's behaviour */ + if (crtcs == 0) { + dev_warn(dev, "Falling back to first CRTC\n"); + crtcs = 1 << 0; + } + + encoder->possible_crtcs = crtcs ? crtcs : 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(drm, encoder, &drm_vencoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, "virtual-encoder"); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &drm_vencoder_helper_funcs); + + /* bogus values, pretend we're a 24" screen for DPI calculations */ + 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(drm, connector, &drm_virtcon_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &drm_virtcon_helper_funcs); + + drm_connector_register(connector); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + return ret; + +attach_err: + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +connector_init_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + display_timings_release(con->timings); + + return ret; +}; + +static void drm_vencoder_unbind(struct device *dev, struct device *master, + void *data) +{ + struct drm_virt_priv *con = dev_get_drvdata(dev); + + drm_connector_unregister(&con->connector); + drm_connector_cleanup(&con->connector); + drm_encoder_cleanup(&con->encoder); + display_timings_release(con->timings); +} + +static const struct component_ops drm_vencoder_ops = { + .bind = drm_vencoder_bind, + .unbind = drm_vencoder_unbind, +}; + +static int drm_vencoder_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &drm_vencoder_ops); +} + +static int drm_vencoder_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &drm_vencoder_ops); + return 0; +} + +static const struct of_device_id drm_vencoder_of_match[] = { + { .compatible = "drm,virtual-encoder", }, + {}, +}; +MODULE_DEVICE_TABLE(of, drm_vencoder_of_match); + +static struct platform_driver drm_vencoder_driver = { + .probe = drm_vencoder_probe, + .remove = drm_vencoder_remove, + .driver = { + .name = "drm_vencoder", + .of_match_table = drm_vencoder_of_match, + }, +}; + +module_platform_driver(drm_vencoder_driver); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("Virtual DRM Encoder"); +MODULE_LICENSE("GPL v2"); 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 2c72eb584ab7..59aea5aa1fff 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -5,8 +5,10 @@ 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 -tda998x-y := tda998x_drv.o +tda998x-y := tda998x_drv.o tda998x_codec.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o diff --git a/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c new file mode 100644 index 000000000000..fdd38a46158c --- /dev/null +++ b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c @@ -0,0 +1,272 @@ +/* + * 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_atomic_helper.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 drm_connector connector; +}; + +#define conn_to_dummy_priv(x) \ + container_of(x, struct dummy_priv, connector); + +/* DRM encoder functions */ + +static void dummy_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +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_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void +dummy_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static enum drm_connector_status +dummy_connector_detect(struct drm_connector *connector, bool force) +{ + 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_read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length) +{ + memcpy(buf, edid_1024x768, min(sizeof(edid_1024x768),length)); + return 0; +} + +static int dummy_connector_get_modes(struct drm_connector *connector) +{ + struct edid *edid; + int n; + + edid = drm_do_get_edid(connector, dummy_read_edid_block, NULL); + if (!edid) + return 0; + + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + kfree(edid); + + return n; +} + +/* I2C driver functions */ + +static void dummy_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void dummy_encoder_commit(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_helper_funcs dummy_encoder_helper_funcs = { + .dpms = dummy_encoder_dpms, + .mode_fixup = dummy_encoder_mode_fixup, + .prepare = dummy_encoder_prepare, + .commit = dummy_encoder_commit, + .mode_set = dummy_encoder_mode_set, +}; + +static void dummy_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs dummy_encoder_funcs = { + .destroy = dummy_encoder_destroy, +}; + +static struct drm_encoder * +dummy_connector_best_encoder(struct drm_connector *connector) +{ + struct dummy_priv *priv = conn_to_dummy_priv(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 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_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dummy_connector_detect, + .destroy = dummy_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int dummy_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct dummy_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + priv->connector.interlace_allowed = 1; + 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, NULL); + 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; + + 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: + return ret; +} + +static void dummy_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dummy_priv *priv = dev_get_drvdata(dev); + + drm_connector_unregister(&priv->connector); + drm_connector_cleanup(&priv->connector); + drm_encoder_cleanup(&priv->encoder); +} + +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; +} + +#ifdef CONFIG_OF +static const struct of_device_id dummy_of_ids[] = { + { .compatible = "sil,sii9022-tpi", }, + { } +}; +MODULE_DEVICE_TABLE(of, dummy_of_ids); +#endif + +static struct i2c_device_id dummy_ids[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, dummy_ids); + +static struct i2c_driver dummy_driver = { + .probe = dummy_probe, + .remove = dummy_remove, + .driver = { + .name = "dummy_drm_i2c", + .of_match_table = dummy_of_ids + }, + .id_table = dummy_ids, +}; + +module_i2c_driver(dummy_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/i2c/tda998x_codec.c b/drivers/gpu/drm/i2c/tda998x_codec.c new file mode 100644 index 000000000000..c6e275d66b2f --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_codec.c @@ -0,0 +1,248 @@ +/* + * ALSA SoC TDA998X CODEC + * + * Copyright (C) 2014 Jean-Francois Moine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/of.h> +#include <linux/i2c.h> +#include <drm/drm_encoder_slave.h> +#include <drm/i2c/tda998x.h> + +#include "tda998x_drv.h" + +#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int tda_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 *eld = priv->eld; + struct snd_pcm_runtime *runtime = substream->runtime; + u8 *sad; + int sad_count; + unsigned eld_ver, mnl, rate_mask; + unsigned max_channels, fmt; + u64 formats; + struct snd_pcm_hw_constraint_list *rate_constraints = + &priv->rate_constraints; + static const u32 hdmi_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + /* check if streaming is already active */ + if (priv->dai_id != AFMT_NO_AUDIO) + return -EBUSY; + priv->dai_id = dai->id; + + if (!eld) + return 0; + + /* adjust the hw params from the ELD (EDID) */ + eld_ver = eld[0] >> 3; + if (eld_ver != 2 && eld_ver != 31) + return 0; + + mnl = eld[4] & 0x1f; + if (mnl > 16) + return 0; + + sad_count = eld[5] >> 4; + sad = eld + 20 + mnl; + + /* Start from the basic audio settings */ + max_channels = 2; + rate_mask = 0; + fmt = 0; + while (sad_count--) { + switch (sad[0] & 0x78) { + case 0x08: /* PCM */ + max_channels = max(max_channels, (sad[0] & 7) + 1u); + rate_mask |= sad[1]; + fmt |= sad[2] & 0x07; + break; + } + sad += 3; + } + + /* set the constraints */ + rate_constraints->list = hdmi_rates; + rate_constraints->count = ARRAY_SIZE(hdmi_rates); + rate_constraints->mask = rate_mask; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + rate_constraints); + + formats = 0; + if (fmt & 1) + formats |= SNDRV_PCM_FMTBIT_S16_LE; + if (fmt & 2) + formats |= SNDRV_PCM_FMTBIT_S20_3LE; + if (fmt & 4) + formats |= SNDRV_PCM_FMTBIT_S24_LE; + snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + formats); + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, max_channels); + return 0; +} + +static int tda_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + /* Requires an attached display */ + if (!priv->encoder.crtc) + return -ENODEV; + + /* if same input and same parameters, do not do a full switch */ + if (dai->id == priv->params.audio_format && + params_format(params) == priv->audio_sample_format) { + tda998x_audio_start(priv, 0); + return 0; + } + priv->params.audio_sample_rate = params_rate(params); + priv->params.audio_format = dai->id; + priv->audio_sample_format = params_format(params); + priv->params.audio_cfg = + priv->audio_ports[dai->id == AFMT_I2S ? 0 : 1]; + tda998x_audio_start(priv, 1); + return 0; +} + +static void tda_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + tda998x_audio_stop(priv); + priv->dai_id = AFMT_NO_AUDIO; +} + +static const struct snd_soc_dai_ops tda_ops = { + .startup = tda_startup, + .hw_params = tda_hw_params, + .shutdown = tda_shutdown, +}; + +static struct snd_soc_dai_driver tda998x_dai[] = { + { + .name = "i2s-hifi", + .id = AFMT_I2S, + .playback = { + .stream_name = "HDMI I2S Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, + { + .name = "spdif-hifi", + .id = AFMT_SPDIF, + .playback = { + .stream_name = "HDMI SPDIF Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 22050, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, +}; + +static const struct snd_soc_dapm_widget tda_widgets[] = { + SND_SOC_DAPM_OUTPUT("hdmi-out"), +}; +static const struct snd_soc_dapm_route tda_routes[] = { + { "hdmi-out", NULL, "HDMI I2S Playback" }, + { "hdmi-out", NULL, "HDMI SPDIF Playback" }, +}; + +static int tda_probe(struct snd_soc_codec *codec) +{ + struct i2c_client *i2c_client = to_i2c_client(codec->dev); + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); + struct device_node *np = codec->dev->of_node; + int i, j, ret; + const char *p; + + if (!priv) + return -ENODEV; + snd_soc_codec_set_drvdata(codec, priv); + + if (!np) + return 0; + + /* get the audio input ports*/ + for (i = 0; i < 2; i++) { + u32 port; + + ret = of_property_read_u32_index(np, "audio-ports", i, &port); + if (ret) { + if (i == 0) + dev_err(codec->dev, + "bad or missing audio-ports\n"); + break; + } + ret = of_property_read_string_index(np, "audio-port-names", + i, &p); + if (ret) { + dev_err(codec->dev, + "missing audio-port-names[%d]\n", i); + break; + } + if (strcmp(p, "i2s") == 0) { + j = 0; + } else if (strcmp(p, "spdif") == 0) { + j = 1; + } else { + dev_err(codec->dev, + "bad audio-port-names '%s'\n", p); + break; + } + priv->audio_ports[j] = port; + } + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_tda998x = { + .probe = tda_probe, + .dapm_widgets = tda_widgets, + .num_dapm_widgets = ARRAY_SIZE(tda_widgets), + .dapm_routes = tda_routes, + .num_dapm_routes = ARRAY_SIZE(tda_routes), +}; + +int tda998x_codec_register(struct device *dev) +{ + return snd_soc_register_codec(dev, + &soc_codec_tda998x, + tda998x_dai, ARRAY_SIZE(tda998x_dai)); +} + +void tda998x_codec_unregister(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index f4315bc8d471..2358798038f4 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <sound/asoundef.h> +#include <sound/pcm_params.h> #include <drm/drmP.h> #include <drm/drm_atomic_helper.h> @@ -28,32 +29,9 @@ #include <drm/drm_of.h> #include <drm/i2c/tda998x.h> -#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#include "tda998x_drv.h" -struct tda998x_priv { - struct i2c_client *cec; - struct i2c_client *hdmi; - struct mutex mutex; - u16 rev; - u8 current_page; - int dpms; - bool is_hdmi_sink; - u8 vip_cntrl_0; - u8 vip_cntrl_1; - u8 vip_cntrl_2; - struct tda998x_encoder_params params; - - wait_queue_head_t wq_edid; - volatile int wq_edid_wait; - - struct work_struct detect_work; - struct timer_list edid_delay_timer; - wait_queue_head_t edid_delay_waitq; - bool edid_delay_active; - - struct drm_encoder encoder; - struct drm_connector connector; -}; +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) #define conn_to_tda998x_priv(x) \ container_of(x, struct tda998x_priv, connector) @@ -714,12 +692,11 @@ static void tda998x_configure_audio(struct tda998x_priv *priv, struct drm_display_mode *mode, struct tda998x_encoder_params *p) { - u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv; - u32 n; + u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv, aclk; + u32 n, cts; /* Enable audio ports */ reg_write(priv, REG_ENA_AP, p->audio_cfg); - reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg); /* Set audio input source */ switch (p->audio_format) { @@ -728,13 +705,28 @@ tda998x_configure_audio(struct tda998x_priv *priv, clksel_aip = AIP_CLKSEL_AIP_SPDIF; clksel_fs = AIP_CLKSEL_FS_FS64SPDIF; cts_n = CTS_N_M(3) | CTS_N_K(3); + aclk = 0; /* no clock */ break; case AFMT_I2S: reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); clksel_aip = AIP_CLKSEL_AIP_I2S; clksel_fs = AIP_CLKSEL_FS_ACLK; - cts_n = CTS_N_M(3) | CTS_N_K(3); + /* with I2S input, the CTS_N predivider depends on + * the sample width */ + switch (priv->audio_sample_format) { + case SNDRV_PCM_FORMAT_S16_LE: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case SNDRV_PCM_FORMAT_S24_LE: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case SNDRV_PCM_FORMAT_S32_LE: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } + aclk = 1; /* clock enable */ break; default: @@ -746,6 +738,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT | AIP_CNTRL_0_ACR_MAN); /* auto CTS */ reg_write(priv, REG_CTS_N, cts_n); + reg_write(priv, REG_ENA_ACLK, aclk); /* * Audio input somehow depends on HDMI line rate which is @@ -771,9 +764,25 @@ tda998x_configure_audio(struct tda998x_priv *priv, n = 128 * p->audio_sample_rate / 1000; /* Write the CTS and N values */ - buf[0] = 0x44; - buf[1] = 0x42; - buf[2] = 0x01; + if ((n > 0) && (mode->clock > 0)) { + /* + * The average CTS value is calculated as: + * + * fTMDS * n / (128 * fs) + * + * which equates to: + * + * fTMDS / 1000 + * + * for non-coherent clocks. + */ + cts = mode->clock; + } else { + cts = 82500; + } + buf[0] = cts; + buf[1] = cts >> 8; + buf[2] = cts >> 16; buf[3] = n; buf[4] = n >> 8; buf[5] = n >> 16; @@ -802,6 +811,24 @@ tda998x_configure_audio(struct tda998x_priv *priv, tda998x_write_aif(priv, p); } +/* tda998x codec interface */ +void tda998x_audio_start(struct tda998x_priv *priv, + int full) +{ + struct tda998x_encoder_params *p = &priv->params; + + if (!full) { + reg_write(priv, REG_ENA_AP, p->audio_cfg); + return; + } + tda998x_configure_audio(priv, &priv->encoder.crtc->hwmode, p); +} + +void tda998x_audio_stop(struct tda998x_priv *priv) +{ + reg_write(priv, REG_ENA_AP, 0); +} + /* DRM encoder functions */ static void tda998x_encoder_set_config(struct tda998x_priv *priv, @@ -1159,6 +1186,11 @@ static int tda998x_connector_get_modes(struct drm_connector *connector) drm_mode_connector_update_edid_property(connector, edid); n = drm_add_edid_modes(connector, edid); priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); + + /* keep the EDID as ELD for the audio subsystem */ + drm_edid_to_eld(connector, edid); + priv->eld = connector->eld; + kfree(edid); return n; @@ -1186,6 +1218,8 @@ static void tda998x_destroy(struct tda998x_priv *priv) del_timer_sync(&priv->edid_delay_timer); cancel_work_sync(&priv->detect_work); + tda998x_codec_unregister(&priv->hdmi->dev); + i2c_unregister_device(priv->cec); } @@ -1202,6 +1236,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1); priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5); + priv->params.audio_frame[1] = 1; /* channels - 1 */ + priv->params.audio_sample_rate = 48000; /* 48kHz */ + priv->current_page = 0xff; priv->hdmi = client; /* CEC I2C address bound to TDA998x I2C addr by configuration pins */ @@ -1212,6 +1249,8 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->dpms = DRM_MODE_DPMS_OFF; + i2c_set_clientdata(client, priv); + mutex_init(&priv->mutex); /* protect the page access */ init_waitqueue_head(&priv->edid_delay_waitq); setup_timer(&priv->edid_delay_timer, tda998x_edid_delay_done, @@ -1301,6 +1340,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) /* enable EDID read irq: */ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + /* register the audio CODEC */ + tda998x_codec_register(&client->dev); + if (!np) return 0; /* non-DT */ diff --git a/drivers/gpu/drm/i2c/tda998x_drv.h b/drivers/gpu/drm/i2c/tda998x_drv.h new file mode 100644 index 000000000000..2690a4e4a7e1 --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_drv.h @@ -0,0 +1,40 @@ +/* tda998x private data */ + +struct tda998x_priv { + struct i2c_client *cec; + struct i2c_client *hdmi; + struct mutex mutex; + u16 rev; + u8 current_page; + int dpms; + bool is_hdmi_sink; + u8 vip_cntrl_0; + u8 vip_cntrl_1; + u8 vip_cntrl_2; + struct tda998x_encoder_params params; + + wait_queue_head_t wq_edid; + volatile int wq_edid_wait; + + struct work_struct detect_work; + struct timer_list edid_delay_timer; + wait_queue_head_t edid_delay_waitq; + bool edid_delay_active; + + struct drm_encoder encoder; + struct drm_connector connector; + + u8 audio_ports[2]; + int audio_sample_format; + int dai_id; /* DAI ID when streaming active */ + + u8 *eld; + + struct snd_pcm_hw_constraint_list rate_constraints; +}; + +int tda998x_codec_register(struct device *dev); +void tda998x_codec_unregister(struct device *dev); + +void tda998x_audio_start(struct tda998x_priv *priv, int full); +void tda998x_audio_stop(struct tda998x_priv *priv); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index d1559cd04e3d..a3230b73d68c 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -2554,6 +2554,8 @@ extern struct drm_property *drm_mode_create_rotation_property(struct drm_device extern unsigned int drm_rotation_simplify(unsigned int rotation, unsigned int supported_rotations); +extern int drm_create_virtual_connector(struct drm_device *dev); + /* Helpers */ static inline struct drm_plane *drm_plane_find(struct drm_device *dev, diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h index 3e419d92cf5a..31757dff5e91 100644 --- a/include/drm/i2c/tda998x.h +++ b/include/drm/i2c/tda998x.h @@ -20,6 +20,7 @@ struct tda998x_encoder_params { u8 audio_frame[6]; enum { + AFMT_NO_AUDIO = 0, AFMT_SPDIF, AFMT_I2S } audio_format; diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index a5c1560aa443..71677e86b564 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -34,7 +34,15 @@ 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_DMA_CMA=y +CONFIG_CMA_SIZE_SEL_MBYTES=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 4d766c762acc..d5ae380d9b6f 100644 --- a/linaro/configs/vexpress64.conf +++ b/linaro/configs/vexpress64.conf @@ -78,3 +78,26 @@ CONFIG_SATA_SIL24=y CONFIG_SKY2=y CONFIG_ARM_TIMER_SP804=y CONFIG_ARM_CPUIDLE=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_DRM_VIRT_ENCODER=y +CONFIG_CMA=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_SEL_MBYTES=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_SPDIF=y +CONFIG_SND_SIMPLE_CARD=y +CONFIG_DMADEVICES=y +CONFIG_PL330_DMA=y |