Merge branch 'for-next' of http://git.agner.ch/git/linux-drm-fsl-dcu into drm-next
authorDave Airlie <airlied@redhat.com>
Fri, 29 Apr 2016 04:57:51 +0000 (14:57 +1000)
committerDave Airlie <airlied@redhat.com>
Fri, 29 Apr 2016 04:57:51 +0000 (14:57 +1000)
This adds very rudimentary TCON (timing controller for raw LCD displays)
support to enable the bypass mode in order to use the DCU controller on
Freescale/NXP Vybrid SoC's.

Additionally the register clock and pixel clock has been separated, but
are currently still enabled and disabled pairwise.

Other than that, fixes and cleanups accross the driver.

* 'for-next' of http://git.agner.ch/git/linux-drm-fsl-dcu:
  drm/fsl-dcu: increment version and date
  drm/fsl-dcu: implement lastclose callback
  drm/fsl-dcu: disable output polling on driver unload
  drm/fsl-dcu: deallocate fbdev CMA on unload
  drm/fsl-dcu: use variable name dev for struct drm_device
  drm/fsl-dcu: handle missing panel gracefully
  drm/fsl-dcu: detach panel on destroy
  drm/layerscape: reduce excessive stack usage
  drm/fsl-dcu: add TCON driver
  drm/fsl-dcu: use common clock framework for pixel clock divider
  drm/fsl-dcu: add extra clock for pixel clock
  drm/fsl-dcu: disable clock on initialization failure and remove

40 files changed:
Documentation/devicetree/bindings/display/snps,arcpgu.txt [new file with mode: 0644]
Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt [new file with mode: 0644]
MAINTAINERS
arch/arc/boot/dts/axs10x_mb.dtsi
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/arc/Kconfig [new file with mode: 0644]
drivers/gpu/drm/arc/Makefile [new file with mode: 0644]
drivers/gpu/drm/arc/arcpgu.h [new file with mode: 0644]
drivers/gpu/drm/arc/arcpgu_crtc.c [new file with mode: 0644]
drivers/gpu/drm/arc/arcpgu_drv.c [new file with mode: 0644]
drivers/gpu/drm/arc/arcpgu_hdmi.c [new file with mode: 0644]
drivers/gpu/drm/arc/arcpgu_regs.h [new file with mode: 0644]
drivers/gpu/drm/drm_atomic_helper.c
drivers/gpu/drm/drm_crtc.c
drivers/gpu/drm/rcar-du/Kconfig
drivers/gpu/drm/rcar-du/rcar_du_drv.c
drivers/gpu/drm/sun4i/Kconfig [new file with mode: 0644]
drivers/gpu/drm/sun4i/Makefile [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_backend.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_backend.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_crtc.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_crtc.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_dotclock.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_dotclock.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_drv.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_drv.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_framebuffer.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_framebuffer.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_layer.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_layer.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_rgb.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_rgb.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_tcon.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_tcon.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun4i_tv.c [new file with mode: 0644]
include/drm/drm_crtc.h
include/drm/drm_fb_cma_helper.h
include/uapi/drm/qxl_drm.h
include/uapi/drm/sis_drm.h

diff --git a/Documentation/devicetree/bindings/display/snps,arcpgu.txt b/Documentation/devicetree/bindings/display/snps,arcpgu.txt
new file mode 100644 (file)
index 0000000..c5c7dfd
--- /dev/null
@@ -0,0 +1,35 @@
+ARC PGU
+
+This is a display controller found on several development boards produced
+by Synopsys. The ARC PGU is an RGB streamer that reads the data from a
+framebuffer and sends it to a single digital encoder (usually HDMI).
+
+Required properties:
+  - compatible: "snps,arcpgu"
+  - reg: Physical base address and length of the controller's registers.
+  - clocks: A list of phandle + clock-specifier pairs, one for each
+    entry in 'clock-names'.
+  - clock-names: A list of clock names. For ARC PGU it should contain:
+      - "pxlclk" for the clock feeding the output PLL of the controller.
+
+Required sub-nodes:
+  - port: The PGU connection to an encoder chip.
+
+Example:
+
+/ {
+       ...
+
+       pgu@XXXXXXXX {
+               compatible = "snps,arcpgu";
+               reg = <0xXXXXXXXX 0x400>;
+               clocks = <&clock_node>;
+               clock-names = "pxlclk";
+
+               port {
+                       pgu_output: endpoint {
+                               remote-endpoint = <&hdmi_enc_input>;
+                       };
+               };
+       };
+};
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
new file mode 100644 (file)
index 0000000..df8f4ae
--- /dev/null
@@ -0,0 +1,258 @@
+Allwinner A10 Display Pipeline
+==============================
+
+The Allwinner A10 Display pipeline is composed of several components
+that are going to be documented below:
+
+TV Encoder
+----------
+
+The TV Encoder supports the composite and VGA output. It is one end of
+the pipeline.
+
+Required properties:
+ - compatible: value should be "allwinner,sun4i-a10-tv-encoder".
+ - reg: base address and size of memory-mapped region
+ - clocks: the clocks driving the TV encoder
+ - resets: phandle to the reset controller driving the encoder
+
+- ports: A ports node with endpoint definitions as defined in
+  Documentation/devicetree/bindings/media/video-interfaces.txt. The
+  first port should be the input endpoint.
+
+TCON
+----
+
+The TCON acts as a timing controller for RGB, LVDS and TV interfaces.
+
+Required properties:
+ - compatible: value should be "allwinner,sun5i-a13-tcon".
+ - reg: base address and size of memory-mapped region
+ - interrupts: interrupt associated to this IP
+ - clocks: phandles to the clocks feeding the TCON. Three are needed:
+   - 'ahb': the interface clocks
+   - 'tcon-ch0': The clock driving the TCON channel 0
+   - 'tcon-ch1': The clock driving the TCON channel 1
+ - resets: phandles to the reset controllers driving the encoder
+   - "lcd": the reset line for the TCON channel 0
+
+ - clock-names: the clock names mentioned above
+ - reset-names: the reset names mentioned above
+ - clock-output-names: Name of the pixel clock created
+
+- ports: A ports node with endpoint definitions as defined in
+  Documentation/devicetree/bindings/media/video-interfaces.txt. The
+  first port should be the input endpoint, the second one the output
+
+  The output should have two endpoints. The first is the block
+  connected to the TCON channel 0 (usually a panel or a bridge), the
+  second the block connected to the TCON channel 1 (usually the TV
+  encoder)
+
+
+Display Engine Backend
+----------------------
+
+The display engine backend exposes layers and sprites to the
+system.
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun5i-a13-display-backend
+  - reg: base address and size of the memory-mapped region.
+  - clocks: phandles to the clocks feeding the frontend and backend
+    * ahb: the backend interface clock
+    * mod: the backend module clock
+    * ram: the backend DRAM clock
+  - clock-names: the clock names mentioned above
+  - resets: phandles to the reset controllers driving the backend
+
+- ports: A ports node with endpoint definitions as defined in
+  Documentation/devicetree/bindings/media/video-interfaces.txt. The
+  first port should be the input endpoints, the second one the output
+
+Display Engine Frontend
+-----------------------
+
+The display engine frontend does formats conversion, scaling,
+deinterlacing and color space conversion.
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun5i-a13-display-frontend
+  - reg: base address and size of the memory-mapped region.
+  - interrupts: interrupt associated to this IP
+  - clocks: phandles to the clocks feeding the frontend and backend
+    * ahb: the backend interface clock
+    * mod: the backend module clock
+    * ram: the backend DRAM clock
+  - clock-names: the clock names mentioned above
+  - resets: phandles to the reset controllers driving the backend
+
+- ports: A ports node with endpoint definitions as defined in
+  Documentation/devicetree/bindings/media/video-interfaces.txt. The
+  first port should be the input endpoints, the second one the outputs
+
+
+Display Engine Pipeline
+-----------------------
+
+The display engine pipeline (and its entry point, since it can be
+either directly the backend or the frontend) is represented as an
+extra node.
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun5i-a13-display-engine
+
+  - allwinner,pipelines: list of phandle to the display engine
+    frontends available.
+
+Example:
+
+panel: panel {
+       compatible = "olimex,lcd-olinuxino-43-ts";
+       #address-cells = <1>;
+       #size-cells = <0>;
+
+       port {
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               panel_input: endpoint {
+                       remote-endpoint = <&tcon0_out_panel>;
+               };
+       };
+};
+
+tve0: tv-encoder@01c0a000 {
+       compatible = "allwinner,sun4i-a10-tv-encoder";
+       reg = <0x01c0a000 0x1000>;
+       clocks = <&ahb_gates 34>;
+       resets = <&tcon_ch0_clk 0>;
+
+       port {
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               tve0_in_tcon0: endpoint@0 {
+                       reg = <0>;
+                       remote-endpoint = <&tcon0_out_tve0>;
+               };
+       };
+};
+
+tcon0: lcd-controller@1c0c000 {
+       compatible = "allwinner,sun5i-a13-tcon";
+       reg = <0x01c0c000 0x1000>;
+       interrupts = <44>;
+       resets = <&tcon_ch0_clk 1>;
+       reset-names = "lcd";
+       clocks = <&ahb_gates 36>,
+                <&tcon_ch0_clk>,
+                <&tcon_ch1_clk>;
+       clock-names = "ahb",
+                     "tcon-ch0",
+                     "tcon-ch1";
+       clock-output-names = "tcon-pixel-clock";
+
+       ports {
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               tcon0_in: port@0 {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       reg = <0>;
+
+                       tcon0_in_be0: endpoint@0 {
+                               reg = <0>;
+                               remote-endpoint = <&be0_out_tcon0>;
+                       };
+               };
+
+               tcon0_out: port@1 {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       reg = <1>;
+
+                       tcon0_out_panel: endpoint@0 {
+                               reg = <0>;
+                               remote-endpoint = <&panel_input>;
+                       };
+
+                       tcon0_out_tve0: endpoint@1 {
+                               reg = <1>;
+                               remote-endpoint = <&tve0_in_tcon0>;
+                       };
+               };
+       };
+};
+
+fe0: display-frontend@1e00000 {
+       compatible = "allwinner,sun5i-a13-display-frontend";
+       reg = <0x01e00000 0x20000>;
+       interrupts = <47>;
+       clocks = <&ahb_gates 46>, <&de_fe_clk>,
+                <&dram_gates 25>;
+       clock-names = "ahb", "mod",
+                     "ram";
+       resets = <&de_fe_clk>;
+
+       ports {
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               fe0_out: port@1 {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       reg = <1>;
+
+                       fe0_out_be0: endpoint {
+                               remote-endpoint = <&be0_in_fe0>;
+                       };
+               };
+       };
+};
+
+be0: display-backend@1e60000 {
+       compatible = "allwinner,sun5i-a13-display-backend";
+       reg = <0x01e60000 0x10000>;
+       clocks = <&ahb_gates 44>, <&de_be_clk>,
+                <&dram_gates 26>;
+       clock-names = "ahb", "mod",
+                     "ram";
+       resets = <&de_be_clk>;
+
+       ports {
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               be0_in: port@0 {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       reg = <0>;
+
+                       be0_in_fe0: endpoint@0 {
+                               reg = <0>;
+                               remote-endpoint = <&fe0_out_be0>;
+                       };
+               };
+
+               be0_out: port@1 {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       reg = <1>;
+
+                       be0_out_tcon0: endpoint@0 {
+                               reg = <0>;
+                               remote-endpoint = <&tcon0_in_be0>;
+                       };
+               };
+       };
+};
+
+display-engine {
+       compatible = "allwinner,sun5i-a13-display-engine";
+       allwinner,pipelines = <&fe0>;
+};
index 84ff4a0..9ef7b40 100644 (file)
@@ -847,6 +847,12 @@ S: Maintained
 F:     drivers/net/arcnet/
 F:     include/uapi/linux/if_arcnet.h
 
+ARC PGU DRM DRIVER
+M:     Alexey Brodkin <abrodkin@synopsys.com>
+S:     Supported
+F:     drivers/gpu/drm/arc/
+F:     Documentation/devicetree/bindings/display/snps,arcpgu.txt
+
 ARM HDLCD DRM DRIVER
 M:     Liviu Dudau <liviu.dudau@arm.com>
 S:     Supported
@@ -3803,6 +3809,13 @@ S:       Supported
 F:     drivers/gpu/drm/atmel-hlcdc/
 F:     Documentation/devicetree/bindings/drm/atmel/
 
+DRM DRIVERS FOR ALLWINNER A10
+M:     Maxime Ripard  <maxime.ripard@free-electrons.com>
+L:     dri-devel@lists.freedesktop.org
+S:     Supported
+F:     drivers/gpu/drm/sun4i/
+F:     Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+
 DRM DRIVERS FOR EXYNOS
 M:     Inki Dae <inki.dae@samsung.com>
 M:     Joonyoung Shim <jy0922.shim@samsung.com>
index ab5d570..823f15c 100644 (file)
                                clock-frequency = <50000000>;
                                #clock-cells = <0>;
                        };
+
+                       pguclk: pguclk {
+                               #clock-cells = <0>;
+                               compatible = "fixed-clock";
+                               clock-frequency = <74440000>;
+                       };
                };
 
                ethernet@0x18000 {
                        clocks = <&i2cclk>;
                        interrupts = <16>;
 
+                       adv7511:adv7511@39{
+                               compatible="adi,adv7511";
+                               reg = <0x39>;
+                               interrupts = <23>;
+                               adi,input-depth = <8>;
+                               adi,input-colorspace = "rgb";
+                               adi,input-clock = "1x";
+                               adi,clock-delay = <0x03>;
+
+                               ports {
+                                       #address-cells = <1>;
+                                       #size-cells = <0>;
+
+                                       /* RGB/YUV input */
+                                       port@0 {
+                                               reg = <0>;
+                                               adv7511_input:endpoint {
+                                               remote-endpoint = <&pgu_output>;
+                                               };
+                                       };
+
+                                       /* HDMI output */
+                                       port@1 {
+                                               reg = <1>;
+                                               adv7511_output: endpoint {
+                                                       remote-endpoint = <&hdmi_connector_in>;
+                                               };
+                                       };
+                               };
+                       };
+
                        eeprom@0x54{
                                compatible = "24c01";
                                reg = <0x54>;
                        };
                };
 
+               hdmi0: connector {
+                       compatible = "hdmi-connector";
+                       type = "a";
+                       port {
+                               hdmi_connector_in: endpoint {
+                                       remote-endpoint = <&adv7511_output>;
+                               };
+                       };
+               };
+
                gpio0:gpio@13000 {
                        compatible = "snps,dw-apb-gpio";
                        reg = <0x13000 0x1000>;
                                reg = <2>;
                        };
                };
+
+               pgu@17000 {
+                       compatible = "snps,arcpgu";
+                       reg = <0x17000 0x400>;
+                       encoder-slave = <&adv7511>;
+                       clocks = <&pguclk>;
+                       clock-names = "pxlclk";
+
+                       port {
+                               pgu_output: endpoint {
+                                       remote-endpoint = <&adv7511_input>;
+                               };
+                       };
+               };
        };
 };
index f2a74d0..cd51502 100644 (file)
@@ -252,6 +252,8 @@ source "drivers/gpu/drm/rcar-du/Kconfig"
 
 source "drivers/gpu/drm/shmobile/Kconfig"
 
+source "drivers/gpu/drm/sun4i/Kconfig"
+
 source "drivers/gpu/drm/omapdrm/Kconfig"
 
 source "drivers/gpu/drm/tilcdc/Kconfig"
@@ -281,3 +283,5 @@ source "drivers/gpu/drm/imx/Kconfig"
 source "drivers/gpu/drm/vc4/Kconfig"
 
 source "drivers/gpu/drm/etnaviv/Kconfig"
+
+source "drivers/gpu/drm/arc/Kconfig"
index 6eb94fc..1a26b4e 100644 (file)
@@ -1,4 +1,4 @@
-#
+
 # Makefile for the drm device driver.  This driver provides support for the
 # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
 
@@ -65,6 +65,7 @@ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/
 obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-y                  += omapdrm/
+obj-$(CONFIG_DRM_SUN4I) += sun4i/
 obj-y                  += tilcdc/
 obj-$(CONFIG_DRM_QXL) += qxl/
 obj-$(CONFIG_DRM_BOCHS) += bochs/
@@ -78,3 +79,4 @@ obj-y                 += panel/
 obj-y                  += bridge/
 obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
+obj-$(CONFIG_DRM_ARCPGU)+= arc/
diff --git a/drivers/gpu/drm/arc/Kconfig b/drivers/gpu/drm/arc/Kconfig
new file mode 100644 (file)
index 0000000..f9a13b6
--- /dev/null
@@ -0,0 +1,10 @@
+config DRM_ARCPGU
+       tristate "ARC PGU"
+       depends on DRM && OF
+       select DRM_KMS_CMA_HELPER
+       select DRM_KMS_FB_HELPER
+       select DRM_KMS_HELPER
+       help
+         Choose this option if you have an ARC PGU controller.
+
+         If M is selected the module will be called arcpgu.
diff --git a/drivers/gpu/drm/arc/Makefile b/drivers/gpu/drm/arc/Makefile
new file mode 100644 (file)
index 0000000..d48fda7
--- /dev/null
@@ -0,0 +1,2 @@
+arcpgu-y := arcpgu_crtc.o arcpgu_hdmi.o arcpgu_drv.o
+obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o
diff --git a/drivers/gpu/drm/arc/arcpgu.h b/drivers/gpu/drm/arc/arcpgu.h
new file mode 100644 (file)
index 0000000..86574b6
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ARC PGU DRM driver.
+ *
+ * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.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.
+ *
+ */
+
+#ifndef _ARCPGU_H_
+#define _ARCPGU_H_
+
+struct arcpgu_drm_private {
+       void __iomem            *regs;
+       struct clk              *clk;
+       struct drm_fbdev_cma    *fbdev;
+       struct drm_framebuffer  *fb;
+       struct list_head        event_list;
+       struct drm_crtc         crtc;
+       struct drm_plane        *plane;
+};
+
+#define crtc_to_arcpgu_priv(x) container_of(x, struct arcpgu_drm_private, crtc)
+
+static inline void arc_pgu_write(struct arcpgu_drm_private *arcpgu,
+                                unsigned int reg, u32 value)
+{
+       iowrite32(value, arcpgu->regs + reg);
+}
+
+static inline u32 arc_pgu_read(struct arcpgu_drm_private *arcpgu,
+                              unsigned int reg)
+{
+       return ioread32(arcpgu->regs + reg);
+}
+
+int arc_pgu_setup_crtc(struct drm_device *dev);
+int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np);
+struct drm_fbdev_cma *arcpgu_fbdev_cma_init(struct drm_device *dev,
+       unsigned int preferred_bpp, unsigned int num_crtc,
+       unsigned int max_conn_count);
+
+#endif
diff --git a/drivers/gpu/drm/arc/arcpgu_crtc.c b/drivers/gpu/drm/arc/arcpgu_crtc.c
new file mode 100644 (file)
index 0000000..92f8bef
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * ARC PGU DRM driver.
+ *
+ * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.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.
+ *
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <linux/clk.h>
+#include <linux/platform_data/simplefb.h>
+
+#include "arcpgu.h"
+#include "arcpgu_regs.h"
+
+#define ENCODE_PGU_XY(x, y)    ((((x) - 1) << 16) | ((y) - 1))
+
+static struct simplefb_format supported_formats[] = {
+       { "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565 },
+       { "r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888 },
+};
+
+static void arc_pgu_set_pxl_fmt(struct drm_crtc *crtc)
+{
+       struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc);
+       uint32_t pixel_format = crtc->primary->state->fb->pixel_format;
+       struct simplefb_format *format = NULL;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(supported_formats); i++) {
+               if (supported_formats[i].fourcc == pixel_format)
+                       format = &supported_formats[i];
+       }
+
+       if (WARN_ON(!format))
+               return;
+
+       if (format->fourcc == DRM_FORMAT_RGB888)
+               arc_pgu_write(arcpgu, ARCPGU_REG_CTRL,
+                             arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) |
+                                          ARCPGU_MODE_RGB888_MASK);
+
+}
+
+static const struct drm_crtc_funcs arc_pgu_crtc_funcs = {
+       .destroy = drm_crtc_cleanup,
+       .set_config = drm_atomic_helper_set_config,
+       .page_flip = drm_atomic_helper_page_flip,
+       .reset = drm_atomic_helper_crtc_reset,
+       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static void arc_pgu_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+       struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc);
+       struct drm_display_mode *m = &crtc->state->adjusted_mode;
+       u32 val;
+
+       arc_pgu_write(arcpgu, ARCPGU_REG_FMT,
+                     ENCODE_PGU_XY(m->crtc_htotal, m->crtc_vtotal));
+
+       arc_pgu_write(arcpgu, ARCPGU_REG_HSYNC,
+                     ENCODE_PGU_XY(m->crtc_hsync_start - m->crtc_hdisplay,
+                                   m->crtc_hsync_end - m->crtc_hdisplay));
+
+       arc_pgu_write(arcpgu, ARCPGU_REG_VSYNC,
+                     ENCODE_PGU_XY(m->crtc_vsync_start - m->crtc_vdisplay,
+                                   m->crtc_vsync_end - m->crtc_vdisplay));
+
+       arc_pgu_write(arcpgu, ARCPGU_REG_ACTIVE,
+                     ENCODE_PGU_XY(m->crtc_hblank_end - m->crtc_hblank_start,
+                                   m->crtc_vblank_end - m->crtc_vblank_start));
+
+       val = arc_pgu_read(arcpgu, ARCPGU_REG_CTRL);
+
+       if (m->flags & DRM_MODE_FLAG_PVSYNC)
+               val |= ARCPGU_CTRL_VS_POL_MASK << ARCPGU_CTRL_VS_POL_OFST;
+       else
+               val &= ~(ARCPGU_CTRL_VS_POL_MASK << ARCPGU_CTRL_VS_POL_OFST);
+
+       if (m->flags & DRM_MODE_FLAG_PHSYNC)
+               val |= ARCPGU_CTRL_HS_POL_MASK << ARCPGU_CTRL_HS_POL_OFST;
+       else
+               val &= ~(ARCPGU_CTRL_HS_POL_MASK << ARCPGU_CTRL_HS_POL_OFST);
+
+       arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, val);
+       arc_pgu_write(arcpgu, ARCPGU_REG_STRIDE, 0);
+       arc_pgu_write(arcpgu, ARCPGU_REG_START_SET, 1);
+
+       arc_pgu_set_pxl_fmt(crtc);
+
+       clk_set_rate(arcpgu->clk, m->crtc_clock * 1000);
+}
+
+static void arc_pgu_crtc_enable(struct drm_crtc *crtc)
+{
+       struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc);
+
+       clk_prepare_enable(arcpgu->clk);
+       arc_pgu_write(arcpgu, ARCPGU_REG_CTRL,
+                     arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) |
+                     ARCPGU_CTRL_ENABLE_MASK);
+}
+
+static void arc_pgu_crtc_disable(struct drm_crtc *crtc)
+{
+       struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc);
+
+       if (!crtc->primary->fb)
+               return;
+
+       clk_disable_unprepare(arcpgu->clk);
+       arc_pgu_write(arcpgu, ARCPGU_REG_CTRL,
+                             arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) &
+                             ~ARCPGU_CTRL_ENABLE_MASK);
+}
+
+static int arc_pgu_crtc_atomic_check(struct drm_crtc *crtc,
+                                    struct drm_crtc_state *state)
+{
+       struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc);
+       struct drm_display_mode *mode = &state->adjusted_mode;
+       long rate, clk_rate = mode->clock * 1000;
+
+       rate = clk_round_rate(arcpgu->clk, clk_rate);
+       if (rate != clk_rate)
+               return -EINVAL;
+
+       return 0;
+}
+
+static void arc_pgu_crtc_atomic_begin(struct drm_crtc *crtc,
+                                     struct drm_crtc_state *state)
+{
+       struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc);
+       unsigned long flags;
+
+       if (crtc->state->event) {
+               struct drm_pending_vblank_event *event = crtc->state->event;
+
+               crtc->state->event = NULL;
+               event->pipe = drm_crtc_index(crtc);
+
+               WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+               spin_lock_irqsave(&crtc->dev->event_lock, flags);
+               list_add_tail(&event->base.link, &arcpgu->event_list);
+               spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+       }
+}
+
+static const struct drm_crtc_helper_funcs arc_pgu_crtc_helper_funcs = {
+       .mode_set       = drm_helper_crtc_mode_set,
+       .mode_set_base  = drm_helper_crtc_mode_set_base,
+       .mode_set_nofb  = arc_pgu_crtc_mode_set_nofb,
+       .enable         = arc_pgu_crtc_enable,
+       .disable        = arc_pgu_crtc_disable,
+       .prepare        = arc_pgu_crtc_disable,
+       .commit         = arc_pgu_crtc_enable,
+       .atomic_check   = arc_pgu_crtc_atomic_check,
+       .atomic_begin   = arc_pgu_crtc_atomic_begin,
+};
+
+static void arc_pgu_plane_atomic_update(struct drm_plane *plane,
+                                       struct drm_plane_state *state)
+{
+       struct arcpgu_drm_private *arcpgu;
+       struct drm_gem_cma_object *gem;
+
+       if (!plane->state->crtc || !plane->state->fb)
+               return;
+
+       arcpgu = crtc_to_arcpgu_priv(plane->state->crtc);
+       gem = drm_fb_cma_get_gem_obj(plane->state->fb, 0);
+       arc_pgu_write(arcpgu, ARCPGU_REG_BUF0_ADDR, gem->paddr);
+}
+
+static const struct drm_plane_helper_funcs arc_pgu_plane_helper_funcs = {
+       .prepare_fb = NULL,
+       .cleanup_fb = NULL,
+       .atomic_update = arc_pgu_plane_atomic_update,
+};
+
+static void arc_pgu_plane_destroy(struct drm_plane *plane)
+{
+       drm_plane_helper_disable(plane);
+       drm_plane_cleanup(plane);
+}
+
+static const struct drm_plane_funcs arc_pgu_plane_funcs = {
+       .update_plane           = drm_atomic_helper_update_plane,
+       .disable_plane          = drm_atomic_helper_disable_plane,
+       .destroy                = arc_pgu_plane_destroy,
+       .reset                  = drm_atomic_helper_plane_reset,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_plane_destroy_state,
+};
+
+static struct drm_plane *arc_pgu_plane_init(struct drm_device *drm)
+{
+       struct arcpgu_drm_private *arcpgu = drm->dev_private;
+       struct drm_plane *plane = NULL;
+       u32 formats[ARRAY_SIZE(supported_formats)], i;
+       int ret;
+
+       plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL);
+       if (!plane)
+               return ERR_PTR(-ENOMEM);
+
+       for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
+               formats[i] = supported_formats[i].fourcc;
+
+       ret = drm_universal_plane_init(drm, plane, 0xff, &arc_pgu_plane_funcs,
+                                      formats, ARRAY_SIZE(formats),
+                                      DRM_PLANE_TYPE_PRIMARY, NULL);
+       if (ret)
+               return ERR_PTR(ret);
+
+       drm_plane_helper_add(plane, &arc_pgu_plane_helper_funcs);
+       arcpgu->plane = plane;
+
+       return plane;
+}
+
+int arc_pgu_setup_crtc(struct drm_device *drm)
+{
+       struct arcpgu_drm_private *arcpgu = drm->dev_private;
+       struct drm_plane *primary;
+       int ret;
+
+       primary = arc_pgu_plane_init(drm);
+       if (IS_ERR(primary))
+               return PTR_ERR(primary);
+
+       ret = drm_crtc_init_with_planes(drm, &arcpgu->crtc, primary, NULL,
+                                       &arc_pgu_crtc_funcs, NULL);
+       if (ret) {
+               arc_pgu_plane_destroy(primary);
+               return ret;
+       }
+
+       drm_crtc_helper_add(&arcpgu->crtc, &arc_pgu_crtc_helper_funcs);
+       return 0;
+}
diff --git a/drivers/gpu/drm/arc/arcpgu_drv.c b/drivers/gpu/drm/arc/arcpgu_drv.c
new file mode 100644 (file)
index 0000000..5b35e5d
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * ARC PGU DRM driver.
+ *
+ * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.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.
+ *
+ */
+
+#include <linux/clk.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_atomic_helper.h>
+
+#include "arcpgu.h"
+#include "arcpgu_regs.h"
+
+static void arcpgu_fb_output_poll_changed(struct drm_device *dev)
+{
+       struct arcpgu_drm_private *arcpgu = dev->dev_private;
+
+       if (arcpgu->fbdev)
+               drm_fbdev_cma_hotplug_event(arcpgu->fbdev);
+}
+
+static int arcpgu_atomic_commit(struct drm_device *dev,
+                                   struct drm_atomic_state *state, bool async)
+{
+       return drm_atomic_helper_commit(dev, state, false);
+}
+
+static struct drm_mode_config_funcs arcpgu_drm_modecfg_funcs = {
+       .fb_create  = drm_fb_cma_create,
+       .output_poll_changed = arcpgu_fb_output_poll_changed,
+       .atomic_check = drm_atomic_helper_check,
+       .atomic_commit = arcpgu_atomic_commit,
+};
+
+static void arcpgu_setup_mode_config(struct drm_device *drm)
+{
+       drm_mode_config_init(drm);
+       drm->mode_config.min_width = 0;
+       drm->mode_config.min_height = 0;
+       drm->mode_config.max_width = 1920;
+       drm->mode_config.max_height = 1080;
+       drm->mode_config.funcs = &arcpgu_drm_modecfg_funcs;
+}
+
+int arcpgu_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+       int ret;
+
+       ret = drm_gem_mmap(filp, vma);
+       if (ret)
+               return ret;
+
+       vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags));
+       return 0;
+}
+
+static const struct file_operations arcpgu_drm_ops = {
+       .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 = arcpgu_gem_mmap,
+};
+
+static void arcpgu_preclose(struct drm_device *drm, struct drm_file *file)
+{
+       struct arcpgu_drm_private *arcpgu = drm->dev_private;
+       struct drm_pending_vblank_event *e, *t;
+       unsigned long flags;
+
+       spin_lock_irqsave(&drm->event_lock, flags);
+       list_for_each_entry_safe(e, t, &arcpgu->event_list, base.link) {
+               if (e->base.file_priv != file)
+                       continue;
+               list_del(&e->base.link);
+               e->base.destroy(&e->base);
+       }
+       spin_unlock_irqrestore(&drm->event_lock, flags);
+}
+
+static void arcpgu_lastclose(struct drm_device *drm)
+{
+       struct arcpgu_drm_private *arcpgu = drm->dev_private;
+
+       drm_fbdev_cma_restore_mode(arcpgu->fbdev);
+}
+
+static int arcpgu_load(struct drm_device *drm)
+{
+       struct platform_device *pdev = to_platform_device(drm->dev);
+       struct arcpgu_drm_private *arcpgu;
+       struct device_node *encoder_node;
+       struct resource *res;
+       int ret;
+
+       arcpgu = devm_kzalloc(&pdev->dev, sizeof(*arcpgu), GFP_KERNEL);
+       if (arcpgu == NULL)
+               return -ENOMEM;
+
+       drm->dev_private = arcpgu;
+
+       arcpgu->clk = devm_clk_get(drm->dev, "pxlclk");
+       if (IS_ERR(arcpgu->clk))
+               return PTR_ERR(arcpgu->clk);
+
+       INIT_LIST_HEAD(&arcpgu->event_list);
+
+       arcpgu_setup_mode_config(drm);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       arcpgu->regs = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(arcpgu->regs)) {
+               dev_err(drm->dev, "Could not remap IO mem\n");
+               return PTR_ERR(arcpgu->regs);
+       }
+
+       dev_info(drm->dev, "arc_pgu ID: 0x%x\n",
+                arc_pgu_read(arcpgu, ARCPGU_REG_ID));
+
+       if (dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)))
+               return -ENODEV;
+
+       if (arc_pgu_setup_crtc(drm) < 0)
+               return -ENODEV;
+
+       /* find the encoder node and initialize it */
+       encoder_node = of_parse_phandle(drm->dev->of_node, "encoder-slave", 0);
+       if (!encoder_node) {
+               dev_err(drm->dev, "failed to get an encoder slave node\n");
+               return -ENODEV;
+       }
+
+       ret = arcpgu_drm_hdmi_init(drm, encoder_node);
+       if (ret < 0)
+               return ret;
+
+       drm_mode_config_reset(drm);
+       drm_kms_helper_poll_init(drm);
+
+       arcpgu->fbdev = drm_fbdev_cma_init(drm, 16,
+                                             drm->mode_config.num_crtc,
+                                             drm->mode_config.num_connector);
+       if (IS_ERR(arcpgu->fbdev)) {
+               ret = PTR_ERR(arcpgu->fbdev);
+               arcpgu->fbdev = NULL;
+               return -ENODEV;
+       }
+
+       platform_set_drvdata(pdev, arcpgu);
+       return 0;
+}
+
+int arcpgu_unload(struct drm_device *drm)
+{
+       struct arcpgu_drm_private *arcpgu = drm->dev_private;
+
+       if (arcpgu->fbdev) {
+               drm_fbdev_cma_fini(arcpgu->fbdev);
+               arcpgu->fbdev = NULL;
+       }
+       drm_kms_helper_poll_fini(drm);
+       drm_vblank_cleanup(drm);
+       drm_mode_config_cleanup(drm);
+
+       return 0;
+}
+
+static struct drm_driver arcpgu_drm_driver = {
+       .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME |
+                          DRIVER_ATOMIC,
+       .preclose = arcpgu_preclose,
+       .lastclose = arcpgu_lastclose,
+       .name = "drm-arcpgu",
+       .desc = "ARC PGU Controller",
+       .date = "20160219",
+       .major = 1,
+       .minor = 0,
+       .patchlevel = 0,
+       .fops = &arcpgu_drm_ops,
+       .dumb_create = drm_gem_cma_dumb_create,
+       .dumb_map_offset = drm_gem_cma_dumb_map_offset,
+       .dumb_destroy = drm_gem_dumb_destroy,
+       .get_vblank_counter = drm_vblank_no_hw_counter,
+       .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+       .gem_free_object = drm_gem_cma_free_object,
+       .gem_vm_ops = &drm_gem_cma_vm_ops,
+       .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_import_sg_table = drm_gem_cma_prime_import_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,
+};
+
+static int arcpgu_probe(struct platform_device *pdev)
+{
+       struct drm_device *drm;
+       int ret;
+
+       drm = drm_dev_alloc(&arcpgu_drm_driver, &pdev->dev);
+       if (!drm)
+               return -ENOMEM;
+
+       ret = arcpgu_load(drm);
+       if (ret)
+               goto err_unref;
+
+       ret = drm_dev_register(drm, 0);
+       if (ret)
+               goto err_unload;
+
+       ret = drm_connector_register_all(drm);
+       if (ret)
+               goto err_unregister;
+
+       return 0;
+
+err_unregister:
+       drm_dev_unregister(drm);
+
+err_unload:
+       arcpgu_unload(drm);
+
+err_unref:
+       drm_dev_unref(drm);
+
+       return ret;
+}
+
+static int arcpgu_remove(struct platform_device *pdev)
+{
+       struct drm_device *drm = platform_get_drvdata(pdev);
+
+       drm_connector_unregister_all(drm);
+       drm_dev_unregister(drm);
+       arcpgu_unload(drm);
+       drm_dev_unref(drm);
+
+       return 0;
+}
+
+static const struct of_device_id arcpgu_of_table[] = {
+       {.compatible = "snps,arcpgu"},
+       {}
+};
+
+MODULE_DEVICE_TABLE(of, arcpgu_of_table);
+
+static struct platform_driver arcpgu_platform_driver = {
+       .probe = arcpgu_probe,
+       .remove = arcpgu_remove,
+       .driver = {
+                  .name = "arcpgu",
+                  .of_match_table = arcpgu_of_table,
+                  },
+};
+
+module_platform_driver(arcpgu_platform_driver);
+
+MODULE_AUTHOR("Carlos Palminha <palminha@synopsys.com>");
+MODULE_DESCRIPTION("ARC PGU DRM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
new file mode 100644 (file)
index 0000000..08b6bae
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * ARC PGU DRM driver.
+ *
+ * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.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.
+ *
+ */
+
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_atomic_helper.h>
+
+#include "arcpgu.h"
+
+struct arcpgu_drm_connector {
+       struct drm_connector connector;
+       struct drm_encoder_slave *encoder_slave;
+};
+
+static int arcpgu_drm_connector_get_modes(struct drm_connector *connector)
+{
+       const struct drm_encoder_slave_funcs *sfuncs;
+       struct drm_encoder_slave *slave;
+       struct arcpgu_drm_connector *con =
+               container_of(connector, struct arcpgu_drm_connector, connector);
+
+       slave = con->encoder_slave;
+       if (slave == NULL) {
+               dev_err(connector->dev->dev,
+                       "connector_get_modes: cannot find slave encoder for connector\n");
+               return 0;
+       }
+
+       sfuncs = slave->slave_funcs;
+       if (sfuncs->get_modes == NULL)
+               return 0;
+
+       return sfuncs->get_modes(&slave->base, connector);
+}
+
+struct drm_encoder *
+arcpgu_drm_connector_best_encoder(struct drm_connector *connector)
+{
+       struct drm_encoder_slave *slave;
+       struct arcpgu_drm_connector *con =
+               container_of(connector, struct arcpgu_drm_connector, connector);
+
+       slave = con->encoder_slave;
+       if (slave == NULL) {
+               dev_err(connector->dev->dev,
+                       "connector_best_encoder: cannot find slave encoder for connector\n");
+               return NULL;
+       }
+
+       return &slave->base;
+}
+
+static enum drm_connector_status
+arcpgu_drm_connector_detect(struct drm_connector *connector, bool force)
+{
+       enum drm_connector_status status = connector_status_unknown;
+       const struct drm_encoder_slave_funcs *sfuncs;
+       struct drm_encoder_slave *slave;
+
+       struct arcpgu_drm_connector *con =
+               container_of(connector, struct arcpgu_drm_connector, connector);
+
+       slave = con->encoder_slave;
+       if (slave == NULL) {
+               dev_err(connector->dev->dev,
+                       "connector_detect: cannot find slave encoder for connector\n");
+               return status;
+       }
+
+       sfuncs = slave->slave_funcs;
+       if (sfuncs && sfuncs->detect)
+               return sfuncs->detect(&slave->base, connector);
+
+       dev_err(connector->dev->dev, "connector_detect: could not detect slave funcs\n");
+       return status;
+}
+
+static void arcpgu_drm_connector_destroy(struct drm_connector *connector)
+{
+       drm_connector_unregister(connector);
+       drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_helper_funcs
+arcpgu_drm_connector_helper_funcs = {
+       .get_modes = arcpgu_drm_connector_get_modes,
+       .best_encoder = arcpgu_drm_connector_best_encoder,
+};
+
+static const struct drm_connector_funcs arcpgu_drm_connector_funcs = {
+       .dpms = drm_helper_connector_dpms,
+       .reset = drm_atomic_helper_connector_reset,
+       .detect = arcpgu_drm_connector_detect,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = arcpgu_drm_connector_destroy,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static struct drm_encoder_helper_funcs arcpgu_drm_encoder_helper_funcs = {
+       .dpms = drm_i2c_encoder_dpms,
+       .mode_fixup = drm_i2c_encoder_mode_fixup,
+       .mode_set = drm_i2c_encoder_mode_set,
+       .prepare = drm_i2c_encoder_prepare,
+       .commit = drm_i2c_encoder_commit,
+       .detect = drm_i2c_encoder_detect,
+};
+
+static struct drm_encoder_funcs arcpgu_drm_encoder_funcs = {
+       .destroy = drm_encoder_cleanup,
+};
+
+int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
+{
+       struct arcpgu_drm_connector *arcpgu_connector;
+       struct drm_i2c_encoder_driver *driver;
+       struct drm_encoder_slave *encoder;
+       struct drm_connector *connector;
+       struct i2c_client *i2c_slave;
+       int ret;
+
+       encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL);
+       if (encoder == NULL)
+               return -ENOMEM;
+
+       i2c_slave = of_find_i2c_device_by_node(np);
+       if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) {
+               dev_err(drm->dev, "failed to find i2c slave encoder\n");
+               return -EPROBE_DEFER;
+       }
+
+       if (i2c_slave->dev.driver == NULL) {
+               dev_err(drm->dev, "failed to find i2c slave driver\n");
+               return -EPROBE_DEFER;
+       }
+
+       driver =
+           to_drm_i2c_encoder_driver(to_i2c_driver(i2c_slave->dev.driver));
+       ret = driver->encoder_init(i2c_slave, drm, encoder);
+       if (ret) {
+               dev_err(drm->dev, "failed to initialize i2c encoder slave\n");
+               return ret;
+       }
+
+       encoder->base.possible_crtcs = 1;
+       encoder->base.possible_clones = 0;
+       ret = drm_encoder_init(drm, &encoder->base, &arcpgu_drm_encoder_funcs,
+                              DRM_MODE_ENCODER_TMDS, NULL);
+       if (ret)
+               return ret;
+
+       drm_encoder_helper_add(&encoder->base,
+                              &arcpgu_drm_encoder_helper_funcs);
+
+       arcpgu_connector = devm_kzalloc(drm->dev, sizeof(*arcpgu_connector),
+                                       GFP_KERNEL);
+       if (!arcpgu_connector) {
+               ret = -ENOMEM;
+               goto error_encoder_cleanup;
+       }
+
+       connector = &arcpgu_connector->connector;
+       drm_connector_helper_add(connector, &arcpgu_drm_connector_helper_funcs);
+       ret = drm_connector_init(drm, connector, &arcpgu_drm_connector_funcs,
+                       DRM_MODE_CONNECTOR_HDMIA);
+       if (ret < 0) {
+               dev_err(drm->dev, "failed to initialize drm connector\n");
+               goto error_encoder_cleanup;
+       }
+
+       ret = drm_mode_connector_attach_encoder(connector, &encoder->base);
+       if (ret < 0) {
+               dev_err(drm->dev, "could not attach connector to encoder\n");
+               drm_connector_unregister(connector);
+               goto error_connector_cleanup;
+       }
+
+       arcpgu_connector->encoder_slave = encoder;
+
+       return 0;
+
+error_connector_cleanup:
+       drm_connector_cleanup(connector);
+
+error_encoder_cleanup:
+       drm_encoder_cleanup(&encoder->base);
+       return ret;
+}
diff --git a/drivers/gpu/drm/arc/arcpgu_regs.h b/drivers/gpu/drm/arc/arcpgu_regs.h
new file mode 100644 (file)
index 0000000..95a13a8
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ARC PGU DRM driver.
+ *
+ * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.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.
+ *
+ */
+
+#ifndef _ARC_PGU_REGS_H_
+#define _ARC_PGU_REGS_H_
+
+#define ARCPGU_REG_CTRL                0x00
+#define ARCPGU_REG_STAT                0x04
+#define ARCPGU_REG_FMT         0x10
+#define ARCPGU_REG_HSYNC       0x14
+#define ARCPGU_REG_VSYNC       0x18
+#define ARCPGU_REG_ACTIVE      0x1c
+#define ARCPGU_REG_BUF0_ADDR   0x40
+#define ARCPGU_REG_STRIDE      0x50
+#define ARCPGU_REG_START_SET   0x84
+
+#define ARCPGU_REG_ID          0x3FC
+
+#define ARCPGU_CTRL_ENABLE_MASK        0x02
+#define ARCPGU_CTRL_VS_POL_MASK        0x1
+#define ARCPGU_CTRL_VS_POL_OFST        0x3
+#define ARCPGU_CTRL_HS_POL_MASK        0x1
+#define ARCPGU_CTRL_HS_POL_OFST        0x4
+#define ARCPGU_MODE_RGB888_MASK        0x04
+#define ARCPGU_STAT_BUSY_MASK  0x02
+
+#endif
index 40c7b26..d25abce 100644 (file)
@@ -2510,12 +2510,9 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_dpms);
  */
 void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc)
 {
-       if (crtc->state) {
-               drm_property_unreference_blob(crtc->state->mode_blob);
-               drm_property_unreference_blob(crtc->state->degamma_lut);
-               drm_property_unreference_blob(crtc->state->ctm);
-               drm_property_unreference_blob(crtc->state->gamma_lut);
-       }
+       if (crtc->state)
+               __drm_atomic_helper_crtc_destroy_state(crtc, crtc->state);
+
        kfree(crtc->state);
        crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
 
@@ -2621,8 +2618,8 @@ EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state);
  */
 void drm_atomic_helper_plane_reset(struct drm_plane *plane)
 {
-       if (plane->state && plane->state->fb)
-               drm_framebuffer_unreference(plane->state->fb);
+       if (plane->state)
+               __drm_atomic_helper_plane_destroy_state(plane, plane->state);
 
        kfree(plane->state);
        plane->state = kzalloc(sizeof(*plane->state), GFP_KERNEL);
@@ -2743,6 +2740,10 @@ void drm_atomic_helper_connector_reset(struct drm_connector *connector)
        struct drm_connector_state *conn_state =
                kzalloc(sizeof(*conn_state), GFP_KERNEL);
 
+       if (connector->state)
+               __drm_atomic_helper_connector_destroy_state(connector,
+                                                           connector->state);
+
        kfree(connector->state);
        __drm_atomic_helper_connector_reset(connector, conn_state);
 }
index d078a5c..4e5b015 100644 (file)
@@ -360,10 +360,6 @@ static struct drm_mode_object *_object_find(struct drm_device *dev,
                obj = NULL;
        if (obj && obj->id != id)
                obj = NULL;
-       /* don't leak out unref'd fb's */
-       if (obj &&
-           obj->type == DRM_MODE_OBJECT_BLOB)
-               obj = NULL;
 
        if (obj && obj->free_cb) {
                if (!kref_get_unless_zero(&obj->refcount))
@@ -380,24 +376,28 @@ static struct drm_mode_object *_object_find(struct drm_device *dev,
  * @id: id of the mode object
  * @type: type of the mode object
  *
- * Note that framebuffers cannot be looked up with this functions - since those
- * are reference counted, they need special treatment.  Even with
- * DRM_MODE_OBJECT_ANY (although that will simply return NULL
- * rather than WARN_ON()).
+ * This function is used to look up a modeset object. It will acquire a
+ * reference for reference counted objects. This reference must be dropped again
+ * by callind drm_mode_object_unreference().
  */
 struct drm_mode_object *drm_mode_object_find(struct drm_device *dev,
                uint32_t id, uint32_t type)
 {
        struct drm_mode_object *obj = NULL;
 
-       /* Framebuffers are reference counted and need their own lookup
-        * function.*/
-       WARN_ON(type == DRM_MODE_OBJECT_FB || type == DRM_MODE_OBJECT_BLOB);
        obj = _object_find(dev, id, type);
        return obj;
 }
 EXPORT_SYMBOL(drm_mode_object_find);
 
+/**
+ * drm_mode_object_unreference - decr the object refcnt
+ * @obj: mode_object
+ *
+ * This functions decrements the object's refcount if it is a refcounted modeset
+ * object. It is a no-op on any other object. This is used to drop references
+ * acquired with drm_mode_object_reference().
+ */
 void drm_mode_object_unreference(struct drm_mode_object *obj)
 {
        if (obj->free_cb) {
@@ -408,11 +408,12 @@ void drm_mode_object_unreference(struct drm_mode_object *obj)
 EXPORT_SYMBOL(drm_mode_object_unreference);
 
 /**
- * drm_mode_object_reference - incr the fb refcnt
+ * drm_mode_object_reference - incr the object refcnt
  * @obj: mode_object
  *
- * This function operates only on refcounted objects.
- * This functions increments the object's refcount.
+ * This functions increments the object's refcount if it is a refcounted modeset
+ * object. It is a no-op on any other object. References should be dropped again
+ * by calling drm_mode_object_unreference().
  */
 void drm_mode_object_reference(struct drm_mode_object *obj)
 {
@@ -4253,6 +4254,20 @@ done:
        return ret;
 }
 
+static void drm_property_free_blob(struct kref *kref)
+{
+       struct drm_property_blob *blob =
+               container_of(kref, struct drm_property_blob, base.refcount);
+
+       mutex_lock(&blob->dev->mode_config.blob_lock);
+       list_del(&blob->head_global);
+       mutex_unlock(&blob->dev->mode_config.blob_lock);
+
+       drm_mode_object_unregister(blob->dev, &blob->base);
+
+       kfree(blob);
+}
+
 /**
  * drm_property_create_blob - Create new blob property
  *
@@ -4290,47 +4305,22 @@ drm_property_create_blob(struct drm_device *dev, size_t length,
        if (data)
                memcpy(blob->data, data, length);
 
-       mutex_lock(&dev->mode_config.blob_lock);
-
-       ret = drm_mode_object_get(dev, &blob->base, DRM_MODE_OBJECT_BLOB);
+       ret = drm_mode_object_get_reg(dev, &blob->base, DRM_MODE_OBJECT_BLOB,
+                                     true, drm_property_free_blob);
        if (ret) {
                kfree(blob);
-               mutex_unlock(&dev->mode_config.blob_lock);
                return ERR_PTR(-EINVAL);
        }
 
-       kref_init(&blob->refcount);
-
+       mutex_lock(&dev->mode_config.blob_lock);
        list_add_tail(&blob->head_global,
                      &dev->mode_config.property_blob_list);
-
        mutex_unlock(&dev->mode_config.blob_lock);
 
        return blob;
 }
 EXPORT_SYMBOL(drm_property_create_blob);
 
-/**
- * drm_property_free_blob - Blob property destructor
- *
- * Internal free function for blob properties; must not be used directly.
- *
- * @kref: Reference
- */
-static void drm_property_free_blob(struct kref *kref)
-{
-       struct drm_property_blob *blob =
-               container_of(kref, struct drm_property_blob, refcount);
-
-       WARN_ON(!mutex_is_locked(&blob->dev->mode_config.blob_lock));
-
-       list_del(&blob->head_global);
-       list_del(&blob->head_file);
-       drm_mode_object_unregister(blob->dev, &blob->base);
-
-       kfree(blob);
-}
-
 /**
  * drm_property_unreference_blob - Unreference a blob property
  *
@@ -4340,41 +4330,13 @@ static void drm_property_free_blob(struct kref *kref)
  */
 void drm_property_unreference_blob(struct drm_property_blob *blob)
 {
-       struct drm_device *dev;
-
        if (!blob)
                return;
 
-       dev = blob->dev;
-
-       DRM_DEBUG("%p: blob ID: %d (%d)\n", blob, blob->base.id, atomic_read(&blob->refcount.refcount));
-
-       if (kref_put_mutex(&blob->refcount, drm_property_free_blob,
-                          &dev->mode_config.blob_lock))
-               mutex_unlock(&dev->mode_config.blob_lock);
-       else
-               might_lock(&dev->mode_config.blob_lock);
+       drm_mode_object_unreference(&blob->base);
 }
 EXPORT_SYMBOL(drm_property_unreference_blob);
 
-/**
- * drm_property_unreference_blob_locked - Unreference a blob property with blob_lock held
- *
- * Drop a reference on a blob property. May free the object. This must be
- * called with blob_lock held.
- *
- * @blob: Pointer to blob property
- */
-static void drm_property_unreference_blob_locked(struct drm_property_blob *blob)
-{
-       if (!blob)
-               return;
-
-       DRM_DEBUG("%p: blob ID: %d (%d)\n", blob, blob->base.id, atomic_read(&blob->refcount.refcount));
-
-       kref_put(&blob->refcount, drm_property_free_blob);
-}
-
 /**
  * drm_property_destroy_user_blobs - destroy all blobs created by this client
  * @dev:       DRM device
@@ -4385,14 +4347,14 @@ void drm_property_destroy_user_blobs(struct drm_device *dev,
 {
        struct drm_property_blob *blob, *bt;
 
-       mutex_lock(&dev->mode_config.blob_lock);
-
+       /*
+        * When the file gets released that means no one else can access the
+        * blob list any more, so no need to grab dev->blob_lock.
+        */
        list_for_each_entry_safe(blob, bt, &file_priv->blobs, head_file) {
                list_del_init(&blob->head_file);
-               drm_property_unreference_blob_locked(blob);
+               drm_property_unreference_blob(blob);
        }
-
-       mutex_unlock(&dev->mode_config.blob_lock);
 }
 
 /**
@@ -4404,35 +4366,11 @@ void drm_property_destroy_user_blobs(struct drm_device *dev,
  */
 struct drm_property_blob *drm_property_reference_blob(struct drm_property_blob *blob)
 {
-       DRM_DEBUG("%p: blob ID: %d (%d)\n", blob, blob->base.id, atomic_read(&blob->refcount.refcount));
-       kref_get(&blob->refcount);
+       drm_mode_object_reference(&blob->base);
        return blob;
 }
 EXPORT_SYMBOL(drm_property_reference_blob);
 
-/*
- * Like drm_property_lookup_blob, but does not return an additional reference.
- * Must be called with blob_lock held.
- */
-static struct drm_property_blob *__drm_property_lookup_blob(struct drm_device *dev,
-                                                           uint32_t id)
-{
-       struct drm_mode_object *obj = NULL;
-       struct drm_property_blob *blob;
-
-       WARN_ON(!mutex_is_locked(&dev->mode_config.blob_lock));
-
-       mutex_lock(&dev->mode_config.idr_mutex);
-       obj = idr_find(&dev->mode_config.crtc_idr, id);
-       if (!obj || (obj->type != DRM_MODE_OBJECT_BLOB) || (obj->id != id))
-               blob = NULL;
-       else
-               blob = obj_to_blob(obj);
-       mutex_unlock(&dev->mode_config.idr_mutex);
-
-       return blob;
-}
-
 /**
  * drm_property_lookup_blob - look up a blob property and take a reference
  * @dev: drm device
@@ -4445,16 +4383,12 @@ static struct drm_property_blob *__drm_property_lookup_blob(struct drm_device *d
 struct drm_property_blob *drm_property_lookup_blob(struct drm_device *dev,
                                                   uint32_t id)
 {
-       struct drm_property_blob *blob;
-
-       mutex_lock(&dev->mode_config.blob_lock);
-       blob = __drm_property_lookup_blob(dev, id);
-       if (blob) {
-               if (!kref_get_unless_zero(&blob->refcount))
-                       blob = NULL;
-       }
-       mutex_unlock(&dev->mode_config.blob_lock);
+       struct drm_mode_object *obj;
+       struct drm_property_blob *blob = NULL;
 
+       obj = _object_find(dev, id, DRM_MODE_OBJECT_BLOB);
+       if (obj)
+               blob = obj_to_blob(obj);
        return blob;
 }
 EXPORT_SYMBOL(drm_property_lookup_blob);
@@ -4559,26 +4493,21 @@ int drm_mode_getblob_ioctl(struct drm_device *dev,
        if (!drm_core_check_feature(dev, DRIVER_MODESET))
                return -EINVAL;
 
-       drm_modeset_lock_all(dev);
-       mutex_lock(&dev->mode_config.blob_lock);
-       blob = __drm_property_lookup_blob(dev, out_resp->blob_id);
-       if (!blob) {
-               ret = -ENOENT;
-               goto done;
-       }
+       blob = drm_property_lookup_blob(dev, out_resp->blob_id);
+       if (!blob)
+               return -ENOENT;
 
        if (out_resp->length == blob->length) {
                blob_ptr = (void __user *)(unsigned long)out_resp->data;
                if (copy_to_user(blob_ptr, blob->data, blob->length)) {
                        ret = -EFAULT;
-                       goto done;
+                       goto unref;
                }
        }
        out_resp->length = blob->length;
+unref:
+       drm_property_unreference_blob(blob);
 
-done:
-       mutex_unlock(&dev->mode_config.blob_lock);
-       drm_modeset_unlock_all(dev);
        return ret;
 }
 
@@ -4657,13 +4586,11 @@ int drm_mode_destroyblob_ioctl(struct drm_device *dev,
        if (!drm_core_check_feature(dev, DRIVER_MODESET))
                return -EINVAL;
 
-       mutex_lock(&dev->mode_config.blob_lock);
-       blob = __drm_property_lookup_blob(dev, out_resp->blob_id);
-       if (!blob) {
-               ret = -ENOENT;
-               goto err;
-       }
+       blob = drm_property_lookup_blob(dev, out_resp->blob_id);
+       if (!blob)
+               return -ENOENT;
 
+       mutex_lock(&dev->mode_config.blob_lock);
        /* Ensure the property was actually created by this user. */
        list_for_each_entry(bt, &file_priv->blobs, head_file) {
                if (bt == blob) {
@@ -4680,13 +4607,18 @@ int drm_mode_destroyblob_ioctl(struct drm_device *dev,
        /* We must drop head_file here, because we may not be the last
         * reference on the blob. */
        list_del_init(&blob->head_file);
-       drm_property_unreference_blob_locked(blob);
        mutex_unlock(&dev->mode_config.blob_lock);
 
+       /* One reference from lookup, and one from the filp. */
+       drm_property_unreference_blob(blob);
+       drm_property_unreference_blob(blob);
+
        return 0;
 
 err:
        mutex_unlock(&dev->mode_config.blob_lock);
+       drm_property_unreference_blob(blob);
+
        return ret;
 }
 
@@ -4997,7 +4929,7 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data,
        }
        if (!obj->properties) {
                ret = -EINVAL;
-               goto out;
+               goto out_unref;
        }
 
        ret = get_properties(obj, file_priv->atomic,
@@ -5005,6 +4937,8 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data,
                        (uint64_t __user *)(unsigned long)(arg->prop_values_ptr),
                        &arg->count_props);
 
+out_unref:
+       drm_mode_object_unreference(obj);
 out:
        drm_modeset_unlock_all(dev);
        return ret;
@@ -5047,20 +4981,20 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
                goto out;
        }
        if (!arg_obj->properties)
-               goto out;
+               goto out_unref;
 
        for (i = 0; i < arg_obj->properties->count; i++)
                if (arg_obj->properties->properties[i]->base.id == arg->prop_id)
                        break;
 
        if (i == arg_obj->properties->count)
-               goto out;
+               goto out_unref;
 
        prop_obj = drm_mode_object_find(dev, arg->prop_id,
                                        DRM_MODE_OBJECT_PROPERTY);
        if (!prop_obj) {
                ret = -ENOENT;
-               goto out;
+               goto out_unref;
        }
        property = obj_to_property(prop_obj);
 
@@ -5083,6 +5017,8 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data,
 
        drm_property_change_valid_put(property, ref);
 
+out_unref:
+       drm_mode_object_unreference(arg_obj);
 out:
        drm_modeset_unlock_all(dev);
        return ret;
index 1f10fa0..7fc3ca5 100644 (file)
@@ -2,7 +2,7 @@ config DRM_RCAR_DU
        tristate "DRM Support for R-Car Display Unit"
        depends on DRM && OF
        depends on ARM || ARM64
-       depends on ARCH_SHMOBILE || COMPILE_TEST
+       depends on ARCH_RENESAS || COMPILE_TEST
        select DRM_KMS_HELPER
        select DRM_KMS_CMA_HELPER
        select DRM_GEM_CMA_HELPER
@@ -27,6 +27,6 @@ config DRM_RCAR_LVDS
 config DRM_RCAR_VSP
        bool "R-Car DU VSP Compositor Support"
        depends on DRM_RCAR_DU
-       depends on VIDEO_RENESAS_VSP1
+       depends on VIDEO_RENESAS_VSP1=y || (VIDEO_RENESAS_VSP1 && DRM_RCAR_DU=m)
        help
          Enable support to expose the R-Car VSP Compositor as KMS planes.
index 0f251dc..fb9242d 100644 (file)
@@ -297,7 +297,6 @@ static int rcar_du_probe(struct platform_device *pdev)
 {
        struct device_node *np = pdev->dev.of_node;
        struct rcar_du_device *rcdu;
-       struct drm_connector *connector;
        struct drm_device *ddev;
        struct resource *mem;
        int ret;
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
new file mode 100644 (file)
index 0000000..99510e6
--- /dev/null
@@ -0,0 +1,14 @@
+config DRM_SUN4I
+       tristate "DRM Support for Allwinner A10 Display Engine"
+       depends on DRM && ARM
+       depends on ARCH_SUNXI || COMPILE_TEST
+       select DRM_GEM_CMA_HELPER
+       select DRM_KMS_HELPER
+       select DRM_KMS_CMA_HELPER
+       select DRM_PANEL
+       select REGMAP_MMIO
+       select VIDEOMODE_HELPERS
+       help
+         Choose this option if you have an Allwinner SoC with a
+         Display Engine. If M is selected the module will be called
+         sun4i-drm.
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
new file mode 100644 (file)
index 0000000..58cd551
--- /dev/null
@@ -0,0 +1,13 @@
+sun4i-drm-y += sun4i_crtc.o
+sun4i-drm-y += sun4i_drv.o
+sun4i-drm-y += sun4i_framebuffer.o
+sun4i-drm-y += sun4i_layer.o
+
+sun4i-tcon-y += sun4i_tcon.o
+sun4i-tcon-y += sun4i_rgb.o
+sun4i-tcon-y += sun4i_dotclock.o
+
+obj-$(CONFIG_DRM_SUN4I)                += sun4i-drm.o sun4i-tcon.o
+obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
+
+obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c
new file mode 100644 (file)
index 0000000..f7a15c1
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include <linux/component.h>
+#include <linux/reset.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+
+static u32 sunxi_rgb2yuv_coef[12] = {
+       0x00000107, 0x00000204, 0x00000064, 0x00000108,
+       0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808,
+       0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808
+};
+
+void sun4i_backend_apply_color_correction(struct sun4i_backend *backend)
+{
+       int i;
+
+       DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n");
+
+       /* Set color correction */
+       regmap_write(backend->regs, SUN4I_BACKEND_OCCTL_REG,
+                    SUN4I_BACKEND_OCCTL_ENABLE);
+
+       for (i = 0; i < 12; i++)
+               regmap_write(backend->regs, SUN4I_BACKEND_OCRCOEF_REG(i),
+                            sunxi_rgb2yuv_coef[i]);
+}
+EXPORT_SYMBOL(sun4i_backend_apply_color_correction);
+
+void sun4i_backend_disable_color_correction(struct sun4i_backend *backend)
+{
+       DRM_DEBUG_DRIVER("Disabling color correction\n");
+
+       /* Disable color correction */
+       regmap_update_bits(backend->regs, SUN4I_BACKEND_OCCTL_REG,
+                          SUN4I_BACKEND_OCCTL_ENABLE, 0);
+}
+EXPORT_SYMBOL(sun4i_backend_disable_color_correction);
+
+void sun4i_backend_commit(struct sun4i_backend *backend)
+{
+       DRM_DEBUG_DRIVER("Committing changes\n");
+
+       regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG,
+                    SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS |
+                    SUN4I_BACKEND_REGBUFFCTL_LOADCTL);
+}
+EXPORT_SYMBOL(sun4i_backend_commit);
+
+void sun4i_backend_layer_enable(struct sun4i_backend *backend,
+                               int layer, bool enable)
+{
+       u32 val;
+
+       DRM_DEBUG_DRIVER("Enabling layer %d\n", layer);
+
+       if (enable)
+               val = SUN4I_BACKEND_MODCTL_LAY_EN(layer);
+       else
+               val = 0;
+
+       regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG,
+                          SUN4I_BACKEND_MODCTL_LAY_EN(layer), val);
+}
+EXPORT_SYMBOL(sun4i_backend_layer_enable);
+
+static int sun4i_backend_drm_format_to_layer(u32 format, u32 *mode)
+{
+       switch (format) {
+       case DRM_FORMAT_ARGB8888:
+               *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888;
+               break;
+
+       case DRM_FORMAT_XRGB8888:
+               *mode = SUN4I_BACKEND_LAY_FBFMT_XRGB8888;
+               break;
+
+       case DRM_FORMAT_RGB888:
+               *mode = SUN4I_BACKEND_LAY_FBFMT_RGB888;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+int sun4i_backend_update_layer_coord(struct sun4i_backend *backend,
+                                    int layer, struct drm_plane *plane)
+{
+       struct drm_plane_state *state = plane->state;
+       struct drm_framebuffer *fb = state->fb;
+
+       DRM_DEBUG_DRIVER("Updating layer %d\n", layer);
+
+       if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
+               DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n",
+                                state->crtc_w, state->crtc_h);
+               regmap_write(backend->regs, SUN4I_BACKEND_DISSIZE_REG,
+                            SUN4I_BACKEND_DISSIZE(state->crtc_w,
+                                                  state->crtc_h));
+       }
+
+       /* Set the line width */
+       DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8);
+       regmap_write(backend->regs, SUN4I_BACKEND_LAYLINEWIDTH_REG(layer),
+                    fb->pitches[0] * 8);
+
+       /* Set height and width */
+       DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n",
+                        state->crtc_w, state->crtc_h);
+       regmap_write(backend->regs, SUN4I_BACKEND_LAYSIZE_REG(layer),
+                    SUN4I_BACKEND_LAYSIZE(state->crtc_w,
+                                          state->crtc_h));
+
+       /* Set base coordinates */
+       DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n",
+                        state->crtc_x, state->crtc_y);
+       regmap_write(backend->regs, SUN4I_BACKEND_LAYCOOR_REG(layer),
+                    SUN4I_BACKEND_LAYCOOR(state->crtc_x,
+                                          state->crtc_y));
+
+       return 0;
+}
+EXPORT_SYMBOL(sun4i_backend_update_layer_coord);
+
+int sun4i_backend_update_layer_formats(struct sun4i_backend *backend,
+                                      int layer, struct drm_plane *plane)
+{
+       struct drm_plane_state *state = plane->state;
+       struct drm_framebuffer *fb = state->fb;
+       bool interlaced = false;
+       u32 val;
+       int ret;
+
+       if (plane->state->crtc)
+               interlaced = plane->state->crtc->state->adjusted_mode.flags
+                       & DRM_MODE_FLAG_INTERLACE;
+
+       regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG,
+                          SUN4I_BACKEND_MODCTL_ITLMOD_EN,
+                          interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0);
+
+       DRM_DEBUG_DRIVER("Switching display backend interlaced mode %s\n",
+                        interlaced ? "on" : "off");
+
+       ret = sun4i_backend_drm_format_to_layer(fb->pixel_format, &val);
+       if (ret) {
+               DRM_DEBUG_DRIVER("Invalid format\n");
+               return val;
+       }
+
+       regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG1(layer),
+                          SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val);
+
+       return 0;
+}
+EXPORT_SYMBOL(sun4i_backend_update_layer_formats);
+
+int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
+                                     int layer, struct drm_plane *plane)
+{
+       struct drm_plane_state *state = plane->state;
+       struct drm_framebuffer *fb = state->fb;
+       struct drm_gem_cma_object *gem;
+       u32 lo_paddr, hi_paddr;
+       dma_addr_t paddr;
+       int bpp;
+
+       /* Get the physical address of the buffer in memory */
+       gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+       DRM_DEBUG_DRIVER("Using GEM @ 0x%x\n", gem->paddr);
+
+       /* Compute the start of the displayed memory */
+       bpp = drm_format_plane_cpp(fb->pixel_format, 0);
+       paddr = gem->paddr + fb->offsets[0];
+       paddr += (state->src_x >> 16) * bpp;
+       paddr += (state->src_y >> 16) * fb->pitches[0];
+
+       DRM_DEBUG_DRIVER("Setting buffer address to 0x%x\n", paddr);
+
+       /* Write the 32 lower bits of the address (in bits) */
+       lo_paddr = paddr << 3;
+       DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr);
+       regmap_write(backend->regs, SUN4I_BACKEND_LAYFB_L32ADD_REG(layer),
+                    lo_paddr);
+
+       /* And the upper bits */
+       hi_paddr = paddr >> 29;
+       DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr);
+       regmap_update_bits(backend->regs, SUN4I_BACKEND_LAYFB_H4ADD_REG,
+                          SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer),
+                          SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr));
+
+       return 0;
+}
+EXPORT_SYMBOL(sun4i_backend_update_layer_buffer);
+
+static struct regmap_config sun4i_backend_regmap_config = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = 0x5800,
+};
+
+static int sun4i_backend_bind(struct device *dev, struct device *master,
+                             void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct drm_device *drm = data;
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_backend *backend;
+       struct resource *res;
+       void __iomem *regs;
+       int i, ret;
+
+       backend = devm_kzalloc(dev, sizeof(*backend), GFP_KERNEL);
+       if (!backend)
+               return -ENOMEM;
+       dev_set_drvdata(dev, backend);
+       drv->backend = backend;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       regs = devm_ioremap_resource(dev, res);
+       if (IS_ERR(regs)) {
+               dev_err(dev, "Couldn't map the backend registers\n");
+               return PTR_ERR(regs);
+       }
+
+       backend->regs = devm_regmap_init_mmio(dev, regs,
+                                             &sun4i_backend_regmap_config);
+       if (IS_ERR(backend->regs)) {
+               dev_err(dev, "Couldn't create the backend0 regmap\n");
+               return PTR_ERR(backend->regs);
+       }
+
+       backend->reset = devm_reset_control_get(dev, NULL);
+       if (IS_ERR(backend->reset)) {
+               dev_err(dev, "Couldn't get our reset line\n");
+               return PTR_ERR(backend->reset);
+       }
+
+       ret = reset_control_deassert(backend->reset);
+       if (ret) {
+               dev_err(dev, "Couldn't deassert our reset line\n");
+               return ret;
+       }
+
+       backend->bus_clk = devm_clk_get(dev, "ahb");
+       if (IS_ERR(backend->bus_clk)) {
+               dev_err(dev, "Couldn't get the backend bus clock\n");
+               ret = PTR_ERR(backend->bus_clk);
+               goto err_assert_reset;
+       }
+       clk_prepare_enable(backend->bus_clk);
+
+       backend->mod_clk = devm_clk_get(dev, "mod");
+       if (IS_ERR(backend->mod_clk)) {
+               dev_err(dev, "Couldn't get the backend module clock\n");
+               ret = PTR_ERR(backend->mod_clk);
+               goto err_disable_bus_clk;
+       }
+       clk_prepare_enable(backend->mod_clk);
+
+       backend->ram_clk = devm_clk_get(dev, "ram");
+       if (IS_ERR(backend->ram_clk)) {
+               dev_err(dev, "Couldn't get the backend RAM clock\n");
+               ret = PTR_ERR(backend->ram_clk);
+               goto err_disable_mod_clk;
+       }
+       clk_prepare_enable(backend->ram_clk);
+
+       /* Reset the registers */
+       for (i = 0x800; i < 0x1000; i += 4)
+               regmap_write(backend->regs, i, 0);
+
+       /* Disable registers autoloading */
+       regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG,
+                    SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS);
+
+       /* Enable the backend */
+       regmap_write(backend->regs, SUN4I_BACKEND_MODCTL_REG,
+                    SUN4I_BACKEND_MODCTL_DEBE_EN |
+                    SUN4I_BACKEND_MODCTL_START_CTL);
+
+       return 0;
+
+err_disable_mod_clk:
+       clk_disable_unprepare(backend->mod_clk);
+err_disable_bus_clk:
+       clk_disable_unprepare(backend->bus_clk);
+err_assert_reset:
+       reset_control_assert(backend->reset);
+       return ret;
+}
+
+static void sun4i_backend_unbind(struct device *dev, struct device *master,
+                                void *data)
+{
+       struct sun4i_backend *backend = dev_get_drvdata(dev);
+
+       clk_disable_unprepare(backend->ram_clk);
+       clk_disable_unprepare(backend->mod_clk);
+       clk_disable_unprepare(backend->bus_clk);
+       reset_control_assert(backend->reset);
+}
+
+static struct component_ops sun4i_backend_ops = {
+       .bind   = sun4i_backend_bind,
+       .unbind = sun4i_backend_unbind,
+};
+
+static int sun4i_backend_probe(struct platform_device *pdev)
+{
+       return component_add(&pdev->dev, &sun4i_backend_ops);
+}
+
+static int sun4i_backend_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &sun4i_backend_ops);
+
+       return 0;
+}
+
+static const struct of_device_id sun4i_backend_of_table[] = {
+       { .compatible = "allwinner,sun5i-a13-display-backend" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sun4i_backend_of_table);
+
+static struct platform_driver sun4i_backend_platform_driver = {
+       .probe          = sun4i_backend_probe,
+       .remove         = sun4i_backend_remove,
+       .driver         = {
+               .name           = "sun4i-backend",
+               .of_match_table = sun4i_backend_of_table,
+       },
+};
+module_platform_driver(sun4i_backend_platform_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 Display Backend Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h
new file mode 100644 (file)
index 0000000..7070bb3
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_BACKEND_H_
+#define _SUN4I_BACKEND_H_
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define SUN4I_BACKEND_MODCTL_REG               0x800
+#define SUN4I_BACKEND_MODCTL_LINE_SEL                  BIT(29)
+#define SUN4I_BACKEND_MODCTL_ITLMOD_EN                 BIT(28)
+#define SUN4I_BACKEND_MODCTL_OUT_SEL                   GENMASK(22, 20)
+#define SUN4I_BACKEND_MODCTL_OUT_LCD                           (0 << 20)
+#define SUN4I_BACKEND_MODCTL_OUT_FE0                           (6 << 20)
+#define SUN4I_BACKEND_MODCTL_OUT_FE1                           (7 << 20)
+#define SUN4I_BACKEND_MODCTL_HWC_EN                    BIT(16)
+#define SUN4I_BACKEND_MODCTL_LAY_EN(l)                 BIT(8 + l)
+#define SUN4I_BACKEND_MODCTL_OCSC_EN                   BIT(5)
+#define SUN4I_BACKEND_MODCTL_DFLK_EN                   BIT(4)
+#define SUN4I_BACKEND_MODCTL_DLP_START_CTL             BIT(2)
+#define SUN4I_BACKEND_MODCTL_START_CTL                 BIT(1)
+#define SUN4I_BACKEND_MODCTL_DEBE_EN                   BIT(0)
+
+#define SUN4I_BACKEND_BACKCOLOR_REG            0x804
+#define SUN4I_BACKEND_BACKCOLOR(r, g, b)               (((r) << 16) | ((g) << 8) | (b))
+
+#define SUN4I_BACKEND_DISSIZE_REG              0x808
+#define SUN4I_BACKEND_DISSIZE(w, h)                    (((((h) - 1) & 0xffff) << 16) | \
+                                                        (((w) - 1) & 0xffff))
+
+#define SUN4I_BACKEND_LAYSIZE_REG(l)           (0x810 + (0x4 * (l)))
+#define SUN4I_BACKEND_LAYSIZE(w, h)                    (((((h) - 1) & 0x1fff) << 16) | \
+                                                        (((w) - 1) & 0x1fff))
+
+#define SUN4I_BACKEND_LAYCOOR_REG(l)           (0x820 + (0x4 * (l)))
+#define SUN4I_BACKEND_LAYCOOR(x, y)                    ((((u32)(y) & 0xffff) << 16) | \
+                                                        ((u32)(x) & 0xffff))
+
+#define SUN4I_BACKEND_LAYLINEWIDTH_REG(l)      (0x840 + (0x4 * (l)))
+
+#define SUN4I_BACKEND_LAYFB_L32ADD_REG(l)      (0x850 + (0x4 * (l)))
+
+#define SUN4I_BACKEND_LAYFB_H4ADD_REG          0x860
+#define SUN4I_BACKEND_LAYFB_H4ADD_MSK(l)               GENMASK(3 + ((l) * 8), 0)
+#define SUN4I_BACKEND_LAYFB_H4ADD(l, val)                      ((val) << ((l) * 8))
+
+#define SUN4I_BACKEND_REGBUFFCTL_REG           0x870
+#define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS          BIT(1)
+#define SUN4I_BACKEND_REGBUFFCTL_LOADCTL               BIT(0)
+
+#define SUN4I_BACKEND_CKMAX_REG                        0x880
+#define SUN4I_BACKEND_CKMIN_REG                        0x884
+#define SUN4I_BACKEND_CKCFG_REG                        0x888
+#define SUN4I_BACKEND_ATTCTL_REG0(l)           (0x890 + (0x4 * (l)))
+#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK     BIT(15)
+#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x)               ((x) << 15)
+#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK      GENMASK(11, 10)
+#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x)                        ((x) << 10)
+
+#define SUN4I_BACKEND_ATTCTL_REG1(l)           (0x8a0 + (0x4 * (l)))
+#define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT          GENMASK(15, 14)
+#define SUN4I_BACKEND_ATTCTL_REG1_LAY_WSCAFCT          GENMASK(13, 12)
+#define SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT            GENMASK(11, 8)
+#define SUN4I_BACKEND_LAY_FBFMT_1BPP                           (0 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_2BPP                           (1 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_4BPP                           (2 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_8BPP                           (3 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_RGB655                         (4 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_RGB565                         (5 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_RGB556                         (6 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_ARGB1555                       (7 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_RGBA5551                       (8 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_XRGB8888                       (9 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_ARGB8888                       (10 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_RGB888                         (11 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_ARGB4444                       (12 << 8)
+#define SUN4I_BACKEND_LAY_FBFMT_RGBA4444                       (13 << 8)
+
+#define SUN4I_BACKEND_DLCDPCTL_REG             0x8b0
+#define SUN4I_BACKEND_DLCDPFRMBUF_ADDRCTL_REG  0x8b4
+#define SUN4I_BACKEND_DLCDPCOOR_REG0           0x8b8
+#define SUN4I_BACKEND_DLCDPCOOR_REG1           0x8bc
+
+#define SUN4I_BACKEND_INT_EN_REG               0x8c0
+#define SUN4I_BACKEND_INT_FLAG_REG             0x8c4
+#define SUN4I_BACKEND_REG_LOAD_FINISHED                        BIT(1)
+
+#define SUN4I_BACKEND_HWCCTL_REG               0x8d8
+#define SUN4I_BACKEND_HWCFBCTL_REG             0x8e0
+#define SUN4I_BACKEND_WBCTL_REG                        0x8f0
+#define SUN4I_BACKEND_WBADD_REG                        0x8f4
+#define SUN4I_BACKEND_WBLINEWIDTH_REG          0x8f8
+#define SUN4I_BACKEND_SPREN_REG                        0x900
+#define SUN4I_BACKEND_SPRFMTCTL_REG            0x908
+#define SUN4I_BACKEND_SPRALPHACTL_REG          0x90c
+#define SUN4I_BACKEND_IYUVCTL_REG              0x920
+#define SUN4I_BACKEND_IYUVADD_REG(c)           (0x930 + (0x4 * (c)))
+#define SUN4I_BACKEND_IYUVLINEWITDTH_REG(c)    (0x940 + (0x4 * (c)))
+#define SUN4I_BACKEND_YGCOEF_REG(c)            (0x950 + (0x4 * (c)))
+#define SUN4I_BACKEND_YGCONS_REG               0x95c
+#define SUN4I_BACKEND_URCOEF_REG(c)            (0x960 + (0x4 * (c)))
+#define SUN4I_BACKEND_URCONS_REG               0x96c
+#define SUN4I_BACKEND_VBCOEF_REG(c)            (0x970 + (0x4 * (c)))
+#define SUN4I_BACKEND_VBCONS_REG               0x97c
+#define SUN4I_BACKEND_KSCTL_REG                        0x980
+#define SUN4I_BACKEND_KSBKCOLOR_REG            0x984
+#define SUN4I_BACKEND_KSFSTLINEWIDTH_REG       0x988
+#define SUN4I_BACKEND_KSVSCAFCT_REG            0x98c
+#define SUN4I_BACKEND_KSHSCACOEF_REG(x)                (0x9a0 + (0x4 * (x)))
+#define SUN4I_BACKEND_OCCTL_REG                        0x9c0
+#define SUN4I_BACKEND_OCCTL_ENABLE                     BIT(0)
+
+#define SUN4I_BACKEND_OCRCOEF_REG(x)           (0x9d0 + (0x4 * (x)))
+#define SUN4I_BACKEND_OCRCONS_REG              0x9dc
+#define SUN4I_BACKEND_OCGCOEF_REG(x)           (0x9e0 + (0x4 * (x)))
+#define SUN4I_BACKEND_OCGCONS_REG              0x9ec
+#define SUN4I_BACKEND_OCBCOEF_REG(x)           (0x9f0 + (0x4 * (x)))
+#define SUN4I_BACKEND_OCBCONS_REG              0x9fc
+#define SUN4I_BACKEND_SPRCOORCTL_REG(s)                (0xa00 + (0x4 * (s)))
+#define SUN4I_BACKEND_SPRATTCTL_REG(s)         (0xb00 + (0x4 * (s)))
+#define SUN4I_BACKEND_SPRADD_REG(s)            (0xc00 + (0x4 * (s)))
+#define SUN4I_BACKEND_SPRLINEWIDTH_REG(s)      (0xd00 + (0x4 * (s)))
+
+#define SUN4I_BACKEND_SPRPALTAB_OFF            0x4000
+#define SUN4I_BACKEND_GAMMATAB_OFF             0x4400
+#define SUN4I_BACKEND_HWCPATTERN_OFF           0x4800
+#define SUN4I_BACKEND_HWCCOLORTAB_OFF          0x4c00
+#define SUN4I_BACKEND_PIPE_OFF(p)              (0x5000 + (0x400 * (p)))
+
+struct sun4i_backend {
+       struct regmap           *regs;
+
+       struct reset_control    *reset;
+
+       struct clk              *bus_clk;
+       struct clk              *mod_clk;
+       struct clk              *ram_clk;
+};
+
+void sun4i_backend_apply_color_correction(struct sun4i_backend *backend);
+void sun4i_backend_disable_color_correction(struct sun4i_backend *backend);
+
+void sun4i_backend_commit(struct sun4i_backend *backend);
+
+void sun4i_backend_layer_enable(struct sun4i_backend *backend,
+                               int layer, bool enable);
+int sun4i_backend_update_layer_coord(struct sun4i_backend *backend,
+                                    int layer, struct drm_plane *plane);
+int sun4i_backend_update_layer_formats(struct sun4i_backend *backend,
+                                      int layer, struct drm_plane *plane);
+int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
+                                     int layer, struct drm_plane *plane);
+
+#endif /* _SUN4I_BACKEND_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c
new file mode 100644 (file)
index 0000000..4182a21
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modes.h>
+
+#include <linux/clk-provider.h>
+#include <linux/ioport.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#include <video/videomode.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_crtc.h"
+#include "sun4i_drv.h"
+#include "sun4i_tcon.h"
+
+static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc,
+                                   struct drm_crtc_state *old_state)
+{
+       struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
+       struct drm_device *dev = crtc->dev;
+       unsigned long flags;
+
+       if (crtc->state->event) {
+               WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+               spin_lock_irqsave(&dev->event_lock, flags);
+               scrtc->event = crtc->state->event;
+               spin_unlock_irqrestore(&dev->event_lock, flags);
+               crtc->state->event = NULL;
+        }
+}
+
+static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc,
+                                   struct drm_crtc_state *old_state)
+{
+       struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
+       struct sun4i_drv *drv = scrtc->drv;
+
+       DRM_DEBUG_DRIVER("Committing plane changes\n");
+
+       sun4i_backend_commit(drv->backend);
+}
+
+static void sun4i_crtc_disable(struct drm_crtc *crtc)
+{
+       struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
+       struct sun4i_drv *drv = scrtc->drv;
+
+       DRM_DEBUG_DRIVER("Disabling the CRTC\n");
+
+       sun4i_tcon_disable(drv->tcon);
+}
+
+static void sun4i_crtc_enable(struct drm_crtc *crtc)
+{
+       struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
+       struct sun4i_drv *drv = scrtc->drv;
+
+       DRM_DEBUG_DRIVER("Enabling the CRTC\n");
+
+       sun4i_tcon_enable(drv->tcon);
+}
+
+static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = {
+       .atomic_begin   = sun4i_crtc_atomic_begin,
+       .atomic_flush   = sun4i_crtc_atomic_flush,
+       .disable        = sun4i_crtc_disable,
+       .enable         = sun4i_crtc_enable,
+};
+
+static const struct drm_crtc_funcs sun4i_crtc_funcs = {
+       .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
+       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .destroy                = drm_crtc_cleanup,
+       .page_flip              = drm_atomic_helper_page_flip,
+       .reset                  = drm_atomic_helper_crtc_reset,
+       .set_config             = drm_atomic_helper_set_config,
+};
+
+struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_crtc *scrtc;
+       int ret;
+
+       scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL);
+       if (!scrtc)
+               return NULL;
+       scrtc->drv = drv;
+
+       ret = drm_crtc_init_with_planes(drm, &scrtc->crtc,
+                                       drv->primary,
+                                       NULL,
+                                       &sun4i_crtc_funcs,
+                                       NULL);
+       if (ret) {
+               dev_err(drm->dev, "Couldn't init DRM CRTC\n");
+               return NULL;
+       }
+
+       drm_crtc_helper_add(&scrtc->crtc, &sun4i_crtc_helper_funcs);
+
+       return scrtc;
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.h b/drivers/gpu/drm/sun4i/sun4i_crtc.h
new file mode 100644 (file)
index 0000000..dec8ce4
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_CRTC_H_
+#define _SUN4I_CRTC_H_
+
+struct sun4i_crtc {
+       struct drm_crtc                 crtc;
+       struct drm_pending_vblank_event *event;
+
+       struct sun4i_drv                *drv;
+};
+
+static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc)
+{
+       return container_of(crtc, struct sun4i_crtc, crtc);
+}
+
+struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm);
+
+#endif /* _SUN4I_CRTC_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.c b/drivers/gpu/drm/sun4i/sun4i_dotclock.c
new file mode 100644 (file)
index 0000000..3ff668c
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include "sun4i_tcon.h"
+
+struct sun4i_dclk {
+       struct clk_hw   hw;
+       struct regmap   *regmap;
+};
+
+static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
+{
+       return container_of(hw, struct sun4i_dclk, hw);
+}
+
+static void sun4i_dclk_disable(struct clk_hw *hw)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+
+       regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
+                          BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
+}
+
+static int sun4i_dclk_enable(struct clk_hw *hw)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+
+       return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
+                                 BIT(SUN4I_TCON0_DCLK_GATE_BIT),
+                                 BIT(SUN4I_TCON0_DCLK_GATE_BIT));
+}
+
+static int sun4i_dclk_is_enabled(struct clk_hw *hw)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+       u32 val;
+
+       regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
+
+       return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
+}
+
+static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
+                                           unsigned long parent_rate)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+       u32 val;
+
+       regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
+
+       val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
+       val &= SUN4I_TCON0_DCLK_DIV_WIDTH;
+
+       if (!val)
+               val = 1;
+
+       return parent_rate / val;
+}
+
+static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
+                                 unsigned long *parent_rate)
+{
+       return *parent_rate / DIV_ROUND_CLOSEST(*parent_rate, rate);
+}
+
+static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
+                              unsigned long parent_rate)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+       int div = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+       return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
+                                 GENMASK(6, 0), div);
+}
+
+static int sun4i_dclk_get_phase(struct clk_hw *hw)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+       u32 val;
+
+       regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
+
+       val >>= 28;
+       val &= 3;
+
+       return val * 120;
+}
+
+static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
+{
+       struct sun4i_dclk *dclk = hw_to_dclk(hw);
+
+       regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
+                          GENMASK(29, 28),
+                          degrees / 120);
+
+       return 0;
+}
+
+static const struct clk_ops sun4i_dclk_ops = {
+       .disable        = sun4i_dclk_disable,
+       .enable         = sun4i_dclk_enable,
+       .is_enabled     = sun4i_dclk_is_enabled,
+
+       .recalc_rate    = sun4i_dclk_recalc_rate,
+       .round_rate     = sun4i_dclk_round_rate,
+       .set_rate       = sun4i_dclk_set_rate,
+
+       .get_phase      = sun4i_dclk_get_phase,
+       .set_phase      = sun4i_dclk_set_phase,
+};
+
+int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
+{
+       const char *clk_name, *parent_name;
+       struct clk_init_data init;
+       struct sun4i_dclk *dclk;
+
+       parent_name = __clk_get_name(tcon->sclk0);
+       of_property_read_string_index(dev->of_node, "clock-output-names", 0,
+                                     &clk_name);
+
+       dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
+       if (!dclk)
+               return -ENOMEM;
+
+       init.name = clk_name;
+       init.ops = &sun4i_dclk_ops;
+       init.parent_names = &parent_name;
+       init.num_parents = 1;
+
+       dclk->regmap = tcon->regs;
+       dclk->hw.init = &init;
+
+       tcon->dclk = clk_register(dev, &dclk->hw);
+       if (IS_ERR(tcon->dclk))
+               return PTR_ERR(tcon->dclk);
+
+       return 0;
+}
+EXPORT_SYMBOL(sun4i_dclk_create);
+
+int sun4i_dclk_free(struct sun4i_tcon *tcon)
+{
+       clk_unregister(tcon->dclk);
+       return 0;
+}
+EXPORT_SYMBOL(sun4i_dclk_free);
diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.h b/drivers/gpu/drm/sun4i/sun4i_dotclock.h
new file mode 100644 (file)
index 0000000..d5e25fa
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_DOTCLOCK_H_
+#define _SUN4I_DOTCLOCK_H_
+
+struct sun4i_tcon;
+
+int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon);
+int sun4i_dclk_free(struct sun4i_tcon *tcon);
+
+#endif /* _SUN4I_DOTCLOCK_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
new file mode 100644 (file)
index 0000000..76e922b
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <linux/component.h>
+#include <linux/of_graph.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_drv.h"
+#include "sun4i_framebuffer.h"
+#include "sun4i_layer.h"
+#include "sun4i_tcon.h"
+
+static int sun4i_drv_connector_plug_all(struct drm_device *drm)
+{
+       struct drm_connector *connector, *failed;
+       int ret;
+
+       mutex_lock(&drm->mode_config.mutex);
+       list_for_each_entry(connector, &drm->mode_config.connector_list, head) {
+               ret = drm_connector_register(connector);
+               if (ret) {
+                       failed = connector;
+                       goto err;
+               }
+       }
+       mutex_unlock(&drm->mode_config.mutex);
+       return 0;
+
+err:
+       list_for_each_entry(connector, &drm->mode_config.connector_list, head) {
+               if (failed == connector)
+                       break;
+
+               drm_connector_unregister(connector);
+       }
+       mutex_unlock(&drm->mode_config.mutex);
+
+       return ret;
+}
+
+static int sun4i_drv_enable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       DRM_DEBUG_DRIVER("Enabling VBLANK on pipe %d\n", pipe);
+
+       sun4i_tcon_enable_vblank(tcon, true);
+
+       return 0;
+}
+
+static void sun4i_drv_disable_vblank(struct drm_device *drm, unsigned int pipe)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       DRM_DEBUG_DRIVER("Disabling VBLANK on pipe %d\n", pipe);
+
+       sun4i_tcon_enable_vblank(tcon, false);
+}
+
+static const struct file_operations sun4i_drv_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 sun4i_drv_driver = {
+       .driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC,
+
+       /* Generic Operations */
+       .fops                   = &sun4i_drv_fops,
+       .name                   = "sun4i-drm",
+       .desc                   = "Allwinner sun4i Display Engine",
+       .date                   = "20150629",
+       .major                  = 1,
+       .minor                  = 0,
+
+       /* GEM Operations */
+       .dumb_create            = drm_gem_cma_dumb_create,
+       .dumb_destroy           = drm_gem_dumb_destroy,
+       .dumb_map_offset        = drm_gem_cma_dumb_map_offset,
+       .gem_free_object        = drm_gem_cma_free_object,
+       .gem_vm_ops             = &drm_gem_cma_vm_ops,
+
+       /* PRIME Operations */
+       .prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
+       .gem_prime_import       = drm_gem_prime_import,
+       .gem_prime_export       = drm_gem_prime_export,
+       .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+       .gem_prime_import_sg_table = drm_gem_cma_prime_import_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,
+
+       /* Frame Buffer Operations */
+
+       /* VBlank Operations */
+       .get_vblank_counter     = drm_vblank_count,
+       .enable_vblank          = sun4i_drv_enable_vblank,
+       .disable_vblank         = sun4i_drv_disable_vblank,
+};
+
+static int sun4i_drv_bind(struct device *dev)
+{
+       struct drm_device *drm;
+       struct sun4i_drv *drv;
+       int ret;
+
+       drm = drm_dev_alloc(&sun4i_drv_driver, dev);
+       if (!drm)
+               return -ENOMEM;
+
+       ret = drm_dev_set_unique(drm, dev_name(drm->dev));
+       if (ret)
+               goto free_drm;
+
+       drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+       if (!drv) {
+               ret = -ENOMEM;
+               goto free_drm;
+       }
+       drm->dev_private = drv;
+
+       drm_vblank_init(drm, 1);
+       drm_mode_config_init(drm);
+
+       ret = component_bind_all(drm->dev, drm);
+       if (ret) {
+               dev_err(drm->dev, "Couldn't bind all pipelines components\n");
+               goto free_drm;
+       }
+
+       /* Create our layers */
+       drv->layers = sun4i_layers_init(drm);
+       if (!drv->layers) {
+               dev_err(drm->dev, "Couldn't create the planes\n");
+               ret = -EINVAL;
+               goto free_drm;
+       }
+
+       /* Create our CRTC */
+       drv->crtc = sun4i_crtc_init(drm);
+       if (!drv->crtc) {
+               dev_err(drm->dev, "Couldn't create the CRTC\n");
+               ret = -EINVAL;
+               goto free_drm;
+       }
+       drm->irq_enabled = true;
+
+       /* Create our framebuffer */
+       drv->fbdev = sun4i_framebuffer_init(drm);
+       if (IS_ERR(drv->fbdev)) {
+               dev_err(drm->dev, "Couldn't create our framebuffer\n");
+               ret = PTR_ERR(drv->fbdev);
+               goto free_drm;
+       }
+
+       /* Enable connectors polling */
+       drm_kms_helper_poll_init(drm);
+
+       ret = drm_dev_register(drm, 0);
+       if (ret)
+               goto free_drm;
+
+       ret = sun4i_drv_connector_plug_all(drm);
+       if (ret)
+               goto unregister_drm;
+
+       return 0;
+
+unregister_drm:
+       drm_dev_unregister(drm);
+free_drm:
+       drm_dev_unref(drm);
+       return ret;
+}
+
+static void sun4i_drv_unbind(struct device *dev)
+{
+       struct drm_device *drm = dev_get_drvdata(dev);
+
+       drm_dev_unregister(drm);
+       drm_kms_helper_poll_fini(drm);
+       sun4i_framebuffer_free(drm);
+       drm_vblank_cleanup(drm);
+       drm_dev_unref(drm);
+}
+
+static const struct component_master_ops sun4i_drv_master_ops = {
+       .bind   = sun4i_drv_bind,
+       .unbind = sun4i_drv_unbind,
+};
+
+static bool sun4i_drv_node_is_frontend(struct device_node *node)
+{
+       return of_device_is_compatible(node,
+                                      "allwinner,sun5i-a13-display-frontend");
+}
+
+static bool sun4i_drv_node_is_tcon(struct device_node *node)
+{
+       return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon");
+}
+
+static int compare_of(struct device *dev, void *data)
+{
+       DRM_DEBUG_DRIVER("Comparing of node %s with %s\n",
+                        of_node_full_name(dev->of_node),
+                        of_node_full_name(data));
+
+       return dev->of_node == data;
+}
+
+static int sun4i_drv_add_endpoints(struct device *dev,
+                                  struct component_match **match,
+                                  struct device_node *node)
+{
+       struct device_node *port, *ep, *remote;
+       int count = 0;
+
+       /*
+        * We don't support the frontend for now, so we will never
+        * have a device bound. Just skip over it, but we still want
+        * the rest our pipeline to be added.
+        */
+       if (!sun4i_drv_node_is_frontend(node) &&
+           !of_device_is_available(node))
+               return 0;
+
+       if (!sun4i_drv_node_is_frontend(node)) {
+               /* Add current component */
+               DRM_DEBUG_DRIVER("Adding component %s\n",
+                                of_node_full_name(node));
+               component_match_add(dev, match, compare_of, node);
+               count++;
+       }
+
+       /* Inputs are listed first, then outputs */
+       port = of_graph_get_port_by_id(node, 1);
+       if (!port) {
+               DRM_DEBUG_DRIVER("No output to bind\n");
+               return count;
+       }
+
+       for_each_available_child_of_node(port, ep) {
+               remote = of_graph_get_remote_port_parent(ep);
+               if (!remote) {
+                       DRM_DEBUG_DRIVER("Error retrieving the output node\n");
+                       of_node_put(remote);
+                       continue;
+               }
+
+               /*
+                * If the node is our TCON, the first port is used for our
+                * panel, and will not be part of the
+                * component framework.
+                */
+               if (sun4i_drv_node_is_tcon(node)) {
+                       struct of_endpoint endpoint;
+
+                       if (of_graph_parse_endpoint(ep, &endpoint)) {
+                               DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
+                               continue;
+                       }
+
+                       if (!endpoint.id) {
+                               DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
+                               continue;
+                       }
+               }
+
+               /* Walk down our tree */
+               count += sun4i_drv_add_endpoints(dev, match, remote);
+
+               of_node_put(remote);
+       }
+
+       return count;
+}
+
+static int sun4i_drv_probe(struct platform_device *pdev)
+{
+       struct component_match *match = NULL;
+       struct device_node *np = pdev->dev.of_node;
+       int i, count = 0;
+
+       for (i = 0;; i++) {
+               struct device_node *pipeline = of_parse_phandle(np,
+                                                               "allwinner,pipelines",
+                                                               i);
+               if (!pipeline)
+                       break;
+
+               count += sun4i_drv_add_endpoints(&pdev->dev, &match,
+                                               pipeline);
+
+               DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n",
+                                count, i);
+       }
+
+       if (count)
+               return component_master_add_with_match(&pdev->dev,
+                                                      &sun4i_drv_master_ops,
+                                                      match);
+       else
+               return 0;
+}
+
+static int sun4i_drv_remove(struct platform_device *pdev)
+{
+       return 0;
+}
+
+static const struct of_device_id sun4i_drv_of_table[] = {
+       { .compatible = "allwinner,sun5i-a13-display-engine" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);
+
+static struct platform_driver sun4i_drv_platform_driver = {
+       .probe          = sun4i_drv_probe,
+       .remove         = sun4i_drv_remove,
+       .driver         = {
+               .name           = "sun4i-drm",
+               .of_match_table = sun4i_drv_of_table,
+       },
+};
+module_platform_driver(sun4i_drv_platform_driver);
+
+MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h
new file mode 100644 (file)
index 0000000..597353e
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_DRV_H_
+#define _SUN4I_DRV_H_
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+struct sun4i_drv {
+       struct sun4i_backend    *backend;
+       struct sun4i_crtc       *crtc;
+       struct sun4i_tcon       *tcon;
+
+       struct drm_plane        *primary;
+       struct drm_fbdev_cma    *fbdev;
+
+       struct sun4i_layer      **layers;
+};
+
+#endif /* _SUN4I_DRV_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c
new file mode 100644 (file)
index 0000000..a0b30c2
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drmP.h>
+
+#include "sun4i_drv.h"
+
+static void sun4i_de_output_poll_changed(struct drm_device *drm)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+
+       if (drv->fbdev)
+               drm_fbdev_cma_hotplug_event(drv->fbdev);
+}
+
+static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = {
+       .output_poll_changed    = sun4i_de_output_poll_changed,
+       .atomic_check           = drm_atomic_helper_check,
+       .atomic_commit          = drm_atomic_helper_commit,
+       .fb_create              = drm_fb_cma_create,
+};
+
+struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm)
+{
+       drm_mode_config_reset(drm);
+
+       drm->mode_config.max_width = 8192;
+       drm->mode_config.max_height = 8192;
+
+       drm->mode_config.funcs = &sun4i_de_mode_config_funcs;
+
+       return drm_fbdev_cma_init(drm, 32,
+                                 drm->mode_config.num_crtc,
+                                 drm->mode_config.num_connector);
+}
+
+void sun4i_framebuffer_free(struct drm_device *drm)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+
+       drm_fbdev_cma_fini(drv->fbdev);
+       drm_mode_config_cleanup(drm);
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.h b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h
new file mode 100644 (file)
index 0000000..3afd652
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_FRAMEBUFFER_H_
+#define _SUN4I_FRAMEBUFFER_H_
+
+struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm);
+void sun4i_framebuffer_free(struct drm_device *drm);
+
+#endif /* _SUN4I_FRAMEBUFFER_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c
new file mode 100644 (file)
index 0000000..068ab80
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drmP.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+#include "sun4i_layer.h"
+
+#define SUN4I_NUM_LAYERS       2
+
+static int sun4i_backend_layer_atomic_check(struct drm_plane *plane,
+                                           struct drm_plane_state *state)
+{
+       return 0;
+}
+
+static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane,
+                                              struct drm_plane_state *old_state)
+{
+       struct sun4i_layer *layer = plane_to_sun4i_layer(plane);
+       struct sun4i_drv *drv = layer->drv;
+       struct sun4i_backend *backend = drv->backend;
+
+       sun4i_backend_layer_enable(backend, layer->id, false);
+}
+
+static void sun4i_backend_layer_atomic_update(struct drm_plane *plane,
+                                             struct drm_plane_state *old_state)
+{
+       struct sun4i_layer *layer = plane_to_sun4i_layer(plane);
+       struct sun4i_drv *drv = layer->drv;
+       struct sun4i_backend *backend = drv->backend;
+
+       sun4i_backend_update_layer_coord(backend, layer->id, plane);
+       sun4i_backend_update_layer_formats(backend, layer->id, plane);
+       sun4i_backend_update_layer_buffer(backend, layer->id, plane);
+       sun4i_backend_layer_enable(backend, layer->id, true);
+}
+
+static struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = {
+       .atomic_check   = sun4i_backend_layer_atomic_check,
+       .atomic_disable = sun4i_backend_layer_atomic_disable,
+       .atomic_update  = sun4i_backend_layer_atomic_update,
+};
+
+static const struct drm_plane_funcs sun4i_backend_layer_funcs = {
+       .atomic_destroy_state   = drm_atomic_helper_plane_destroy_state,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .destroy                = drm_plane_cleanup,
+       .disable_plane          = drm_atomic_helper_disable_plane,
+       .reset                  = drm_atomic_helper_plane_reset,
+       .update_plane           = drm_atomic_helper_update_plane,
+};
+
+static const uint32_t sun4i_backend_layer_formats[] = {
+       DRM_FORMAT_ARGB8888,
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_RGB888,
+};
+
+static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm,
+                                               enum drm_plane_type type)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_layer *layer;
+       int ret;
+
+       layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL);
+       if (!layer)
+               return ERR_PTR(-ENOMEM);
+
+       ret = drm_universal_plane_init(drm, &layer->plane, BIT(0),
+                                      &sun4i_backend_layer_funcs,
+                                      sun4i_backend_layer_formats,
+                                      ARRAY_SIZE(sun4i_backend_layer_formats),
+                                      type,
+                                      NULL);
+       if (ret) {
+               dev_err(drm->dev, "Couldn't initialize layer\n");
+               return ERR_PTR(ret);
+       }
+
+       drm_plane_helper_add(&layer->plane,
+                            &sun4i_backend_layer_helper_funcs);
+       layer->drv = drv;
+
+       if (type == DRM_PLANE_TYPE_PRIMARY)
+               drv->primary = &layer->plane;
+
+       return layer;
+}
+
+struct sun4i_layer **sun4i_layers_init(struct drm_device *drm)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_layer **layers;
+       int i;
+
+       layers = devm_kcalloc(drm->dev, SUN4I_NUM_LAYERS, sizeof(**layers),
+                             GFP_KERNEL);
+       if (!layers)
+               return ERR_PTR(-ENOMEM);
+
+       /*
+        * The hardware is a bit unusual here.
+        *
+        * Even though it supports 4 layers, it does the composition
+        * in two separate steps.
+        *
+        * The first one is assigning a layer to one of its two
+        * pipes. If more that 1 layer is assigned to the same pipe,
+        * and if pixels overlaps, the pipe will take the pixel from
+        * the layer with the highest priority.
+        *
+        * The second step is the actual alpha blending, that takes
+        * the two pipes as input, and uses the eventual alpha
+        * component to do the transparency between the two.
+        *
+        * This two steps scenario makes us unable to guarantee a
+        * robust alpha blending between the 4 layers in all
+        * situations. So we just expose two layers, one per pipe. On
+        * SoCs that support it, sprites could fill the need for more
+        * layers.
+        */
+       for (i = 0; i < SUN4I_NUM_LAYERS; i++) {
+               enum drm_plane_type type = (i == 0)
+                                        ? DRM_PLANE_TYPE_PRIMARY
+                                        : DRM_PLANE_TYPE_OVERLAY;
+               struct sun4i_layer *layer = layers[i];
+
+               layer = sun4i_layer_init_one(drm, type);
+               if (IS_ERR(layer)) {
+                       dev_err(drm->dev, "Couldn't initialize %s plane\n",
+                               i ? "overlay" : "primary");
+                       return ERR_CAST(layer);
+               };
+
+               DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n",
+                                i ? "overlay" : "primary", i);
+               regmap_update_bits(drv->backend->regs, SUN4I_BACKEND_ATTCTL_REG0(i),
+                                  SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK,
+                                  SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(i));
+
+               layer->id = i;
+       };
+
+       return layers;
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h
new file mode 100644 (file)
index 0000000..a2f65d7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_LAYER_H_
+#define _SUN4I_LAYER_H_
+
+struct sun4i_layer {
+       struct drm_plane        plane;
+       struct sun4i_drv        *drv;
+       int                     id;
+};
+
+static inline struct sun4i_layer *
+plane_to_sun4i_layer(struct drm_plane *plane)
+{
+       return container_of(plane, struct sun4i_layer, plane);
+}
+
+struct sun4i_layer **sun4i_layers_init(struct drm_device *drm);
+
+#endif /* _SUN4I_LAYER_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
new file mode 100644 (file)
index 0000000..ab64948
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+
+#include "sun4i_drv.h"
+#include "sun4i_tcon.h"
+
+struct sun4i_rgb {
+       struct drm_connector    connector;
+       struct drm_encoder      encoder;
+
+       struct sun4i_drv        *drv;
+};
+
+static inline struct sun4i_rgb *
+drm_connector_to_sun4i_rgb(struct drm_connector *connector)
+{
+       return container_of(connector, struct sun4i_rgb,
+                           connector);
+}
+
+static inline struct sun4i_rgb *
+drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder)
+{
+       return container_of(encoder, struct sun4i_rgb,
+                           encoder);
+}
+
+static int sun4i_rgb_get_modes(struct drm_connector *connector)
+{
+       struct sun4i_rgb *rgb =
+               drm_connector_to_sun4i_rgb(connector);
+       struct sun4i_drv *drv = rgb->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       return drm_panel_get_modes(tcon->panel);
+}
+
+static int sun4i_rgb_mode_valid(struct drm_connector *connector,
+                               struct drm_display_mode *mode)
+{
+       u32 hsync = mode->hsync_end - mode->hsync_start;
+       u32 vsync = mode->vsync_end - mode->vsync_start;
+
+       DRM_DEBUG_DRIVER("Validating modes...\n");
+
+       if (hsync < 1)
+               return MODE_HSYNC_NARROW;
+
+       if (hsync > 0x3ff)
+               return MODE_HSYNC_WIDE;
+
+       if ((mode->hdisplay < 1) || (mode->htotal < 1))
+               return MODE_H_ILLEGAL;
+
+       if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff))
+               return MODE_BAD_HVALUE;
+
+       DRM_DEBUG_DRIVER("Horizontal parameters OK\n");
+
+       if (vsync < 1)
+               return MODE_VSYNC_NARROW;
+
+       if (vsync > 0x3ff)
+               return MODE_VSYNC_WIDE;
+
+       if ((mode->vdisplay < 1) || (mode->vtotal < 1))
+               return MODE_V_ILLEGAL;
+
+       if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff))
+               return MODE_BAD_VVALUE;
+
+       DRM_DEBUG_DRIVER("Vertical parameters OK\n");
+
+       return MODE_OK;
+}
+
+static struct drm_encoder *
+sun4i_rgb_best_encoder(struct drm_connector *connector)
+{
+       struct sun4i_rgb *rgb =
+               drm_connector_to_sun4i_rgb(connector);
+
+       return &rgb->encoder;
+}
+
+static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = {
+       .get_modes      = sun4i_rgb_get_modes,
+       .mode_valid     = sun4i_rgb_mode_valid,
+       .best_encoder   = sun4i_rgb_best_encoder,
+};
+
+static enum drm_connector_status
+sun4i_rgb_connector_detect(struct drm_connector *connector, bool force)
+{
+       return connector_status_connected;
+}
+
+static void
+sun4i_rgb_connector_destroy(struct drm_connector *connector)
+{
+       struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector);
+       struct sun4i_drv *drv = rgb->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       drm_panel_detach(tcon->panel);
+       drm_connector_cleanup(connector);
+}
+
+static struct drm_connector_funcs sun4i_rgb_con_funcs = {
+       .dpms                   = drm_atomic_helper_connector_dpms,
+       .detect                 = sun4i_rgb_connector_detect,
+       .fill_modes             = drm_helper_probe_single_connector_modes,
+       .destroy                = sun4i_rgb_connector_destroy,
+       .reset                  = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
+};
+
+static int sun4i_rgb_atomic_check(struct drm_encoder *encoder,
+                                 struct drm_crtc_state *crtc_state,
+                                 struct drm_connector_state *conn_state)
+{
+       return 0;
+}
+
+static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
+{
+       struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
+       struct sun4i_drv *drv = rgb->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       DRM_DEBUG_DRIVER("Enabling RGB output\n");
+
+       drm_panel_enable(tcon->panel);
+       sun4i_tcon_channel_enable(tcon, 0);
+}
+
+static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
+{
+       struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
+       struct sun4i_drv *drv = rgb->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       DRM_DEBUG_DRIVER("Disabling RGB output\n");
+
+       sun4i_tcon_channel_disable(tcon, 0);
+       drm_panel_disable(tcon->panel);
+}
+
+static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
+                                      struct drm_display_mode *mode,
+                                      struct drm_display_mode *adjusted_mode)
+{
+       struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
+       struct sun4i_drv *drv = rgb->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       sun4i_tcon0_mode_set(tcon, mode);
+
+       clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
+
+       /* FIXME: This seems to be board specific */
+       clk_set_phase(tcon->dclk, 120);
+}
+
+static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = {
+       .atomic_check   = sun4i_rgb_atomic_check,
+       .mode_set       = sun4i_rgb_encoder_mode_set,
+       .disable        = sun4i_rgb_encoder_disable,
+       .enable         = sun4i_rgb_encoder_enable,
+};
+
+static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder)
+{
+       drm_encoder_cleanup(encoder);
+}
+
+static struct drm_encoder_funcs sun4i_rgb_enc_funcs = {
+       .destroy        = sun4i_rgb_enc_destroy,
+};
+
+int sun4i_rgb_init(struct drm_device *drm)
+{
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_tcon *tcon = drv->tcon;
+       struct sun4i_rgb *rgb;
+       int ret;
+
+       /* If we don't have a panel, there's no point in going on */
+       if (!tcon->panel)
+               return -ENODEV;
+
+       rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL);
+       if (!rgb)
+               return -ENOMEM;
+       rgb->drv = drv;
+
+       drm_encoder_helper_add(&rgb->encoder,
+                              &sun4i_rgb_enc_helper_funcs);
+       ret = drm_encoder_init(drm,
+                              &rgb->encoder,
+                              &sun4i_rgb_enc_funcs,
+                              DRM_MODE_ENCODER_NONE,
+                              NULL);
+       if (ret) {
+               dev_err(drm->dev, "Couldn't initialise the rgb encoder\n");
+               goto err_out;
+       }
+
+       /* The RGB encoder can only work with the TCON channel 0 */
+       rgb->encoder.possible_crtcs = BIT(0);
+
+       drm_connector_helper_add(&rgb->connector,
+                                &sun4i_rgb_con_helper_funcs);
+       ret = drm_connector_init(drm, &rgb->connector,
+                                &sun4i_rgb_con_funcs,
+                                DRM_MODE_CONNECTOR_Unknown);
+       if (ret) {
+               dev_err(drm->dev, "Couldn't initialise the rgb connector\n");
+               goto err_cleanup_connector;
+       }
+
+       drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder);
+
+       drm_panel_attach(tcon->panel, &rgb->connector);
+
+       return 0;
+
+err_cleanup_connector:
+       drm_encoder_cleanup(&rgb->encoder);
+err_out:
+       return ret;
+}
+EXPORT_SYMBOL(sun4i_rgb_init);
diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.h b/drivers/gpu/drm/sun4i/sun4i_rgb.h
new file mode 100644 (file)
index 0000000..7c4da4c
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef _SUN4I_RGB_H_
+#define _SUN4I_RGB_H_
+
+int sun4i_rgb_init(struct drm_device *drm);
+
+#endif /* _SUN4I_RGB_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
new file mode 100644 (file)
index 0000000..9f19b0e
--- /dev/null
@@ -0,0 +1,561 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <linux/component.h>
+#include <linux/ioport.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include "sun4i_crtc.h"
+#include "sun4i_dotclock.h"
+#include "sun4i_drv.h"
+#include "sun4i_rgb.h"
+#include "sun4i_tcon.h"
+
+void sun4i_tcon_disable(struct sun4i_tcon *tcon)
+{
+       DRM_DEBUG_DRIVER("Disabling TCON\n");
+
+       /* Disable the TCON */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+                          SUN4I_TCON_GCTL_TCON_ENABLE, 0);
+}
+EXPORT_SYMBOL(sun4i_tcon_disable);
+
+void sun4i_tcon_enable(struct sun4i_tcon *tcon)
+{
+       DRM_DEBUG_DRIVER("Enabling TCON\n");
+
+       /* Enable the TCON */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+                          SUN4I_TCON_GCTL_TCON_ENABLE,
+                          SUN4I_TCON_GCTL_TCON_ENABLE);
+}
+EXPORT_SYMBOL(sun4i_tcon_enable);
+
+void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
+{
+       /* Disable the TCON's channel */
+       if (channel == 0) {
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
+                                  SUN4I_TCON0_CTL_TCON_ENABLE, 0);
+               clk_disable_unprepare(tcon->dclk);
+       } else if (channel == 1) {
+               regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
+                                  SUN4I_TCON1_CTL_TCON_ENABLE, 0);
+               clk_disable_unprepare(tcon->sclk1);
+       }
+}
+EXPORT_SYMBOL(sun4i_tcon_channel_disable);
+
+void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
+{
+       /* Enable the TCON's channel */
+       if (channel == 0) {
+               regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
+                                  SUN4I_TCON0_CTL_TCON_ENABLE,
+                                  SUN4I_TCON0_CTL_TCON_ENABLE);
+               clk_prepare_enable(tcon->dclk);
+       } else if (channel == 1) {
+               regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
+                                  SUN4I_TCON1_CTL_TCON_ENABLE,
+                                  SUN4I_TCON1_CTL_TCON_ENABLE);
+               clk_prepare_enable(tcon->sclk1);
+       }
+}
+EXPORT_SYMBOL(sun4i_tcon_channel_enable);
+
+void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
+{
+       u32 mask, val = 0;
+
+       DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis");
+
+       mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) |
+              SUN4I_TCON_GINT0_VBLANK_ENABLE(1);
+
+       if (enable)
+               val = mask;
+
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val);
+}
+EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
+
+static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
+                                   int channel)
+{
+       int delay = mode->vtotal - mode->vdisplay;
+
+       if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+               delay /= 2;
+
+       if (channel == 1)
+               delay -= 2;
+
+       delay = min(delay, 30);
+
+       DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay);
+
+       return delay;
+}
+
+void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
+                         struct drm_display_mode *mode)
+{
+       unsigned int bp, hsync, vsync;
+       u8 clk_delay;
+       u32 val = 0;
+
+       /* Adjust clock delay */
+       clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
+       regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
+                          SUN4I_TCON0_CTL_CLK_DELAY_MASK,
+                          SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
+
+       /* Set the resolution */
+       regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG,
+                    SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) |
+                    SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
+
+       /*
+        * This is called a backporch in the register documentation,
+        * but it really is the front porch + hsync
+        */
+       bp = mode->crtc_htotal - mode->crtc_hsync_start;
+       DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
+                        mode->crtc_htotal, bp);
+
+       /* Set horizontal display timings */
+       regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG,
+                    SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) |
+                    SUN4I_TCON0_BASIC1_H_BACKPORCH(bp));
+
+       /*
+        * This is called a backporch in the register documentation,
+        * but it really is the front porch + hsync
+        */
+       bp = mode->crtc_vtotal - mode->crtc_vsync_start;
+       DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
+                        mode->crtc_vtotal, bp);
+
+       /* Set vertical display timings */
+       regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
+                    SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) |
+                    SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
+
+       /* Set Hsync and Vsync length */
+       hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
+       vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
+       DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync);
+       regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG,
+                    SUN4I_TCON0_BASIC3_V_SYNC(vsync) |
+                    SUN4I_TCON0_BASIC3_H_SYNC(hsync));
+
+       /* Setup the polarity of the various signals */
+       if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+               val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE;
+
+       if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+               val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
+
+       regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG,
+                          SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE,
+                          val);
+
+       /* Map output pins to channel 0 */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+                          SUN4I_TCON_GCTL_IOMAP_MASK,
+                          SUN4I_TCON_GCTL_IOMAP_TCON0);
+
+       /* Enable the output on the pins */
+       regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0);
+}
+EXPORT_SYMBOL(sun4i_tcon0_mode_set);
+
+void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
+                         struct drm_display_mode *mode)
+{
+       unsigned int bp, hsync, vsync;
+       u8 clk_delay;
+       u32 val;
+
+       /* Adjust clock delay */
+       clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
+       regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
+                          SUN4I_TCON1_CTL_CLK_DELAY_MASK,
+                          SUN4I_TCON1_CTL_CLK_DELAY(clk_delay));
+
+       /* Set interlaced mode */
+       if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+               val = SUN4I_TCON1_CTL_INTERLACE_ENABLE;
+       else
+               val = 0;
+       regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
+                          SUN4I_TCON1_CTL_INTERLACE_ENABLE,
+                          val);
+
+       /* Set the input resolution */
+       regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG,
+                    SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) |
+                    SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay));
+
+       /* Set the upscaling resolution */
+       regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG,
+                    SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) |
+                    SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay));
+
+       /* Set the output resolution */
+       regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG,
+                    SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) |
+                    SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay));
+
+       /* Set horizontal display timings */
+       bp = mode->crtc_htotal - mode->crtc_hsync_end;
+       DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
+                        mode->htotal, bp);
+       regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG,
+                    SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) |
+                    SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
+
+       /* Set vertical display timings */
+       bp = mode->crtc_vtotal - mode->crtc_vsync_end;
+       DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
+                        mode->vtotal, bp);
+       regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
+                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
+                    SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
+
+       /* Set Hsync and Vsync length */
+       hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
+       vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
+       DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync);
+       regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG,
+                    SUN4I_TCON1_BASIC5_V_SYNC(vsync) |
+                    SUN4I_TCON1_BASIC5_H_SYNC(hsync));
+
+       /* Map output pins to channel 1 */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
+                          SUN4I_TCON_GCTL_IOMAP_MASK,
+                          SUN4I_TCON_GCTL_IOMAP_TCON1);
+
+       /*
+        * FIXME: Undocumented bits
+        */
+       if (tcon->has_mux)
+               regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+}
+EXPORT_SYMBOL(sun4i_tcon1_mode_set);
+
+static void sun4i_tcon_finish_page_flip(struct drm_device *dev,
+                                       struct sun4i_crtc *scrtc)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->event_lock, flags);
+       if (scrtc->event) {
+               drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event);
+               drm_crtc_vblank_put(&scrtc->crtc);
+               scrtc->event = NULL;
+       }
+       spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static irqreturn_t sun4i_tcon_handler(int irq, void *private)
+{
+       struct sun4i_tcon *tcon = private;
+       struct drm_device *drm = tcon->drm;
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_crtc *scrtc = drv->crtc;
+       unsigned int status;
+
+       regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status);
+
+       if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) |
+                       SUN4I_TCON_GINT0_VBLANK_INT(1))))
+               return IRQ_NONE;
+
+       drm_crtc_handle_vblank(&scrtc->crtc);
+       sun4i_tcon_finish_page_flip(drm, scrtc);
+
+       /* Acknowledge the interrupt */
+       regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG,
+                          SUN4I_TCON_GINT0_VBLANK_INT(0) |
+                          SUN4I_TCON_GINT0_VBLANK_INT(1),
+                          0);
+
+       return IRQ_HANDLED;
+}
+
+static int sun4i_tcon_init_clocks(struct device *dev,
+                                 struct sun4i_tcon *tcon)
+{
+       tcon->clk = devm_clk_get(dev, "ahb");
+       if (IS_ERR(tcon->clk)) {
+               dev_err(dev, "Couldn't get the TCON bus clock\n");
+               return PTR_ERR(tcon->clk);
+       }
+       clk_prepare_enable(tcon->clk);
+
+       tcon->sclk0 = devm_clk_get(dev, "tcon-ch0");
+       if (IS_ERR(tcon->sclk0)) {
+               dev_err(dev, "Couldn't get the TCON channel 0 clock\n");
+               return PTR_ERR(tcon->sclk0);
+       }
+
+       tcon->sclk1 = devm_clk_get(dev, "tcon-ch1");
+       if (IS_ERR(tcon->sclk1)) {
+               dev_err(dev, "Couldn't get the TCON channel 1 clock\n");
+               return PTR_ERR(tcon->sclk1);
+       }
+
+       return sun4i_dclk_create(dev, tcon);
+}
+
+static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon)
+{
+       sun4i_dclk_free(tcon);
+       clk_disable_unprepare(tcon->clk);
+}
+
+static int sun4i_tcon_init_irq(struct device *dev,
+                              struct sun4i_tcon *tcon)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       int irq, ret;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(dev, "Couldn't retrieve the TCON interrupt\n");
+               return irq;
+       }
+
+       ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0,
+                              dev_name(dev), tcon);
+       if (ret) {
+               dev_err(dev, "Couldn't request the IRQ\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static struct regmap_config sun4i_tcon_regmap_config = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = 0x800,
+};
+
+static int sun4i_tcon_init_regmap(struct device *dev,
+                                 struct sun4i_tcon *tcon)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct resource *res;
+       void __iomem *regs;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       regs = devm_ioremap_resource(dev, res);
+       if (IS_ERR(regs)) {
+               dev_err(dev, "Couldn't map the TCON registers\n");
+               return PTR_ERR(regs);
+       }
+
+       tcon->regs = devm_regmap_init_mmio(dev, regs,
+                                          &sun4i_tcon_regmap_config);
+       if (IS_ERR(tcon->regs)) {
+               dev_err(dev, "Couldn't create the TCON regmap\n");
+               return PTR_ERR(tcon->regs);
+       }
+
+       /* Make sure the TCON is disabled and all IRQs are off */
+       regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0);
+       regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0);
+       regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0);
+
+       /* Disable IO lines and set them to tristate */
+       regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0);
+       regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0);
+
+       return 0;
+}
+
+static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node)
+{
+       struct device_node *port, *remote, *child;
+       struct device_node *end_node = NULL;
+
+       /* Inputs are listed first, then outputs */
+       port = of_graph_get_port_by_id(node, 1);
+
+       /*
+        * Our first output is the RGB interface where the panel will
+        * be connected.
+        */
+       for_each_child_of_node(port, child) {
+               u32 reg;
+
+               of_property_read_u32(child, "reg", &reg);
+               if (reg == 0)
+                       end_node = child;
+       }
+
+       if (!end_node) {
+               DRM_DEBUG_DRIVER("Missing panel endpoint\n");
+               return ERR_PTR(-ENODEV);
+       }
+
+       remote = of_graph_get_remote_port_parent(end_node);
+       if (!remote) {
+               DRM_DEBUG_DRIVER("Enable to parse remote node\n");
+               return ERR_PTR(-EINVAL);
+       }
+
+       return of_drm_find_panel(remote);
+}
+
+static int sun4i_tcon_bind(struct device *dev, struct device *master,
+                          void *data)
+{
+       struct drm_device *drm = data;
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_tcon *tcon;
+       int ret;
+
+       tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL);
+       if (!tcon)
+               return -ENOMEM;
+       dev_set_drvdata(dev, tcon);
+       drv->tcon = tcon;
+       tcon->drm = drm;
+
+       if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon"))
+               tcon->has_mux = true;
+
+       tcon->lcd_rst = devm_reset_control_get(dev, "lcd");
+       if (IS_ERR(tcon->lcd_rst)) {
+               dev_err(dev, "Couldn't get our reset line\n");
+               return PTR_ERR(tcon->lcd_rst);
+       }
+
+       /* Make sure our TCON is reset */
+       if (!reset_control_status(tcon->lcd_rst))
+               reset_control_assert(tcon->lcd_rst);
+
+       ret = reset_control_deassert(tcon->lcd_rst);
+       if (ret) {
+               dev_err(dev, "Couldn't deassert our reset line\n");
+               return ret;
+       }
+
+       ret = sun4i_tcon_init_regmap(dev, tcon);
+       if (ret) {
+               dev_err(dev, "Couldn't init our TCON regmap\n");
+               goto err_assert_reset;
+       }
+
+       ret = sun4i_tcon_init_clocks(dev, tcon);
+       if (ret) {
+               dev_err(dev, "Couldn't init our TCON clocks\n");
+               goto err_assert_reset;
+       }
+
+       ret = sun4i_tcon_init_irq(dev, tcon);
+       if (ret) {
+               dev_err(dev, "Couldn't init our TCON interrupts\n");
+               goto err_free_clocks;
+       }
+
+       tcon->panel = sun4i_tcon_find_panel(dev->of_node);
+       if (IS_ERR(tcon->panel)) {
+               dev_info(dev, "No panel found... RGB output disabled\n");
+               return 0;
+       }
+
+       return sun4i_rgb_init(drm);
+
+err_free_clocks:
+       sun4i_tcon_free_clocks(tcon);
+err_assert_reset:
+       reset_control_assert(tcon->lcd_rst);
+       return ret;
+}
+
+static void sun4i_tcon_unbind(struct device *dev, struct device *master,
+                             void *data)
+{
+       struct sun4i_tcon *tcon = dev_get_drvdata(dev);
+
+       sun4i_tcon_free_clocks(tcon);
+}
+
+static struct component_ops sun4i_tcon_ops = {
+       .bind   = sun4i_tcon_bind,
+       .unbind = sun4i_tcon_unbind,
+};
+
+static int sun4i_tcon_probe(struct platform_device *pdev)
+{
+       struct device_node *node = pdev->dev.of_node;
+       struct drm_panel *panel;
+
+       /*
+        * The panel is not ready.
+        * Defer the probe.
+        */
+       panel = sun4i_tcon_find_panel(node);
+       if (IS_ERR(panel)) {
+               /*
+                * If we don't have a panel endpoint, just go on
+                */
+               if (PTR_ERR(panel) != -ENODEV)
+                       return -EPROBE_DEFER;
+       }
+
+       return component_add(&pdev->dev, &sun4i_tcon_ops);
+}
+
+static int sun4i_tcon_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &sun4i_tcon_ops);
+
+       return 0;
+}
+
+static const struct of_device_id sun4i_tcon_of_table[] = {
+       { .compatible = "allwinner,sun5i-a13-tcon" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table);
+
+static struct platform_driver sun4i_tcon_platform_driver = {
+       .probe          = sun4i_tcon_probe,
+       .remove         = sun4i_tcon_remove,
+       .driver         = {
+               .name           = "sun4i-tcon",
+               .of_match_table = sun4i_tcon_of_table,
+       },
+};
+module_platform_driver(sun4i_tcon_platform_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
new file mode 100644 (file)
index 0000000..0e0b11d
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Boris Brezillon <boris.brezillon@free-electrons.com>
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#ifndef __SUN4I_TCON_H__
+#define __SUN4I_TCON_H__
+
+#include <drm/drm_crtc.h>
+
+#include <linux/kernel.h>
+#include <linux/reset.h>
+
+#define SUN4I_TCON_GCTL_REG                    0x0
+#define SUN4I_TCON_GCTL_TCON_ENABLE                    BIT(31)
+#define SUN4I_TCON_GCTL_IOMAP_MASK                     BIT(0)
+#define SUN4I_TCON_GCTL_IOMAP_TCON1                    (1 << 0)
+#define SUN4I_TCON_GCTL_IOMAP_TCON0                    (0 << 0)
+
+#define SUN4I_TCON_GINT0_REG                   0x4
+#define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe)           BIT(31 - (pipe))
+#define SUN4I_TCON_GINT0_VBLANK_INT(pipe)              BIT(15 - (pipe))
+
+#define SUN4I_TCON_GINT1_REG                   0x8
+#define SUN4I_TCON_FRM_CTL_REG                 0x10
+
+#define SUN4I_TCON0_CTL_REG                    0x40
+#define SUN4I_TCON0_CTL_TCON_ENABLE                    BIT(31)
+#define SUN4I_TCON0_CTL_CLK_DELAY_MASK                 GENMASK(8, 4)
+#define SUN4I_TCON0_CTL_CLK_DELAY(delay)               ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK)
+
+#define SUN4I_TCON0_DCLK_REG                   0x44
+#define SUN4I_TCON0_DCLK_GATE_BIT                      (31)
+#define SUN4I_TCON0_DCLK_DIV_SHIFT                     (0)
+#define SUN4I_TCON0_DCLK_DIV_WIDTH                     (7)
+
+#define SUN4I_TCON0_BASIC0_REG                 0x48
+#define SUN4I_TCON0_BASIC0_X(width)                    ((((width) - 1) & 0xfff) << 16)
+#define SUN4I_TCON0_BASIC0_Y(height)                   (((height) - 1) & 0xfff)
+
+#define SUN4I_TCON0_BASIC1_REG                 0x4c
+#define SUN4I_TCON0_BASIC1_H_TOTAL(total)              ((((total) - 1) & 0x1fff) << 16)
+#define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)             (((bp) - 1) & 0xfff)
+
+#define SUN4I_TCON0_BASIC2_REG                 0x50
+#define SUN4I_TCON0_BASIC2_V_TOTAL(total)              ((((total) * 2) & 0x1fff) << 16)
+#define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)             (((bp) - 1) & 0xfff)
+
+#define SUN4I_TCON0_BASIC3_REG                 0x54
+#define SUN4I_TCON0_BASIC3_H_SYNC(width)               ((((width) - 1) & 0x7ff) << 16)
+#define SUN4I_TCON0_BASIC3_V_SYNC(height)              (((height) - 1) & 0x7ff)
+
+#define SUN4I_TCON0_HV_IF_REG                  0x58
+#define SUN4I_TCON0_CPU_IF_REG                 0x60
+#define SUN4I_TCON0_CPU_WR_REG                 0x64
+#define SUN4I_TCON0_CPU_RD0_REG                        0x68
+#define SUN4I_TCON0_CPU_RDA_REG                        0x6c
+#define SUN4I_TCON0_TTL0_REG                   0x70
+#define SUN4I_TCON0_TTL1_REG                   0x74
+#define SUN4I_TCON0_TTL2_REG                   0x78
+#define SUN4I_TCON0_TTL3_REG                   0x7c
+#define SUN4I_TCON0_TTL4_REG                   0x80
+#define SUN4I_TCON0_LVDS_IF_REG                        0x84
+#define SUN4I_TCON0_IO_POL_REG                 0x88
+#define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase)           ((phase & 3) << 28)
+#define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE              BIT(25)
+#define SUN4I_TCON0_IO_POL_VSYNC_POSITIVE              BIT(24)
+
+#define SUN4I_TCON0_IO_TRI_REG                 0x8c
+#define SUN4I_TCON0_IO_TRI_HSYNC_DISABLE               BIT(25)
+#define SUN4I_TCON0_IO_TRI_VSYNC_DISABLE               BIT(24)
+#define SUN4I_TCON0_IO_TRI_DATA_PINS_DISABLE(pins)     GENMASK(pins, 0)
+
+#define SUN4I_TCON1_CTL_REG                    0x90
+#define SUN4I_TCON1_CTL_TCON_ENABLE                    BIT(31)
+#define SUN4I_TCON1_CTL_INTERLACE_ENABLE               BIT(20)
+#define SUN4I_TCON1_CTL_CLK_DELAY_MASK                 GENMASK(8, 4)
+#define SUN4I_TCON1_CTL_CLK_DELAY(delay)               ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK)
+
+#define SUN4I_TCON1_BASIC0_REG                 0x94
+#define SUN4I_TCON1_BASIC0_X(width)                    ((((width) - 1) & 0xfff) << 16)
+#define SUN4I_TCON1_BASIC0_Y(height)                   (((height) - 1) & 0xfff)
+
+#define SUN4I_TCON1_BASIC1_REG                 0x98
+#define SUN4I_TCON1_BASIC1_X(width)                    ((((width) - 1) & 0xfff) << 16)
+#define SUN4I_TCON1_BASIC1_Y(height)                   (((height) - 1) & 0xfff)
+
+#define SUN4I_TCON1_BASIC2_REG                 0x9c
+#define SUN4I_TCON1_BASIC2_X(width)                    ((((width) - 1) & 0xfff) << 16)
+#define SUN4I_TCON1_BASIC2_Y(height)                   (((height) - 1) & 0xfff)
+
+#define SUN4I_TCON1_BASIC3_REG                 0xa0
+#define SUN4I_TCON1_BASIC3_H_TOTAL(total)              ((((total) - 1) & 0x1fff) << 16)
+#define SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)             (((bp) - 1) & 0xfff)
+
+#define SUN4I_TCON1_BASIC4_REG                 0xa4
+#define SUN4I_TCON1_BASIC4_V_TOTAL(total)              (((total) & 0x1fff) << 16)
+#define SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)             (((bp) - 1) & 0xfff)
+
+#define SUN4I_TCON1_BASIC5_REG                 0xa8
+#define SUN4I_TCON1_BASIC5_H_SYNC(width)               ((((width) - 1) & 0x3ff) << 16)
+#define SUN4I_TCON1_BASIC5_V_SYNC(height)              (((height) - 1) & 0x3ff)
+
+#define SUN4I_TCON1_IO_POL_REG                 0xf0
+#define SUN4I_TCON1_IO_TRI_REG                 0xf4
+#define SUN4I_TCON_CEU_CTL_REG                 0x100
+#define SUN4I_TCON_CEU_MUL_RR_REG              0x110
+#define SUN4I_TCON_CEU_MUL_RG_REG              0x114
+#define SUN4I_TCON_CEU_MUL_RB_REG              0x118
+#define SUN4I_TCON_CEU_ADD_RC_REG              0x11c
+#define SUN4I_TCON_CEU_MUL_GR_REG              0x120
+#define SUN4I_TCON_CEU_MUL_GG_REG              0x124
+#define SUN4I_TCON_CEU_MUL_GB_REG              0x128
+#define SUN4I_TCON_CEU_ADD_GC_REG              0x12c
+#define SUN4I_TCON_CEU_MUL_BR_REG              0x130
+#define SUN4I_TCON_CEU_MUL_BG_REG              0x134
+#define SUN4I_TCON_CEU_MUL_BB_REG              0x138
+#define SUN4I_TCON_CEU_ADD_BC_REG              0x13c
+#define SUN4I_TCON_CEU_RANGE_R_REG             0x140
+#define SUN4I_TCON_CEU_RANGE_G_REG             0x144
+#define SUN4I_TCON_CEU_RANGE_B_REG             0x148
+#define SUN4I_TCON_MUX_CTRL_REG                        0x200
+#define SUN4I_TCON1_FILL_CTL_REG               0x300
+#define SUN4I_TCON1_FILL_BEG0_REG              0x304
+#define SUN4I_TCON1_FILL_END0_REG              0x308
+#define SUN4I_TCON1_FILL_DATA0_REG             0x30c
+#define SUN4I_TCON1_FILL_BEG1_REG              0x310
+#define SUN4I_TCON1_FILL_END1_REG              0x314
+#define SUN4I_TCON1_FILL_DATA1_REG             0x318
+#define SUN4I_TCON1_FILL_BEG2_REG              0x31c
+#define SUN4I_TCON1_FILL_END2_REG              0x320
+#define SUN4I_TCON1_FILL_DATA2_REG             0x324
+#define SUN4I_TCON1_GAMMA_TABLE_REG            0x400
+
+#define SUN4I_TCON_MAX_CHANNELS                2
+
+struct sun4i_tcon {
+       struct drm_device               *drm;
+       struct regmap                   *regs;
+
+       /* Main bus clock */
+       struct clk                      *clk;
+
+       /* Clocks for the TCON channels */
+       struct clk                      *sclk0;
+       struct clk                      *sclk1;
+
+       /* Pixel clock */
+       struct clk                      *dclk;
+
+       /* Reset control */
+       struct reset_control            *lcd_rst;
+
+       /* Platform adjustments */
+       bool                            has_mux;
+
+       struct drm_panel                *panel;
+};
+
+/* Global Control */
+void sun4i_tcon_disable(struct sun4i_tcon *tcon);
+void sun4i_tcon_enable(struct sun4i_tcon *tcon);
+
+/* Channel Control */
+void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel);
+void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel);
+
+void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
+
+/* Mode Related Controls */
+void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
+                                bool enable);
+void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
+                         struct drm_display_mode *mode);
+void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
+                         struct drm_display_mode *mode);
+
+#endif /* __SUN4I_TCON_H__ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
new file mode 100644 (file)
index 0000000..bc047f9
--- /dev/null
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2015 Free Electrons
+ * Copyright (C) 2015 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+#include "sun4i_tcon.h"
+
+#define SUN4I_TVE_EN_REG               0x000
+#define SUN4I_TVE_EN_DAC_MAP_MASK              GENMASK(19, 4)
+#define SUN4I_TVE_EN_DAC_MAP(dac, out)         (((out) & 0xf) << (dac + 1) * 4)
+#define SUN4I_TVE_EN_ENABLE                    BIT(0)
+
+#define SUN4I_TVE_CFG0_REG             0x004
+#define SUN4I_TVE_CFG0_DAC_CONTROL_54M         BIT(26)
+#define SUN4I_TVE_CFG0_CORE_DATAPATH_54M       BIT(25)
+#define SUN4I_TVE_CFG0_CORE_CONTROL_54M                BIT(24)
+#define SUN4I_TVE_CFG0_YC_EN                   BIT(17)
+#define SUN4I_TVE_CFG0_COMP_EN                 BIT(16)
+#define SUN4I_TVE_CFG0_RES(x)                  ((x) & 0xf)
+#define SUN4I_TVE_CFG0_RES_480i                        SUN4I_TVE_CFG0_RES(0)
+#define SUN4I_TVE_CFG0_RES_576i                        SUN4I_TVE_CFG0_RES(1)
+
+#define SUN4I_TVE_DAC0_REG             0x008
+#define SUN4I_TVE_DAC0_CLOCK_INVERT            BIT(24)
+#define SUN4I_TVE_DAC0_LUMA(x)                 (((x) & 3) << 20)
+#define SUN4I_TVE_DAC0_LUMA_0_4                        SUN4I_TVE_DAC0_LUMA(3)
+#define SUN4I_TVE_DAC0_CHROMA(x)               (((x) & 3) << 18)
+#define SUN4I_TVE_DAC0_CHROMA_0_75             SUN4I_TVE_DAC0_CHROMA(3)
+#define SUN4I_TVE_DAC0_INTERNAL_DAC(x)         (((x) & 3) << 16)
+#define SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS  SUN4I_TVE_DAC0_INTERNAL_DAC(3)
+#define SUN4I_TVE_DAC0_DAC_EN(dac)             BIT(dac)
+
+#define SUN4I_TVE_NOTCH_REG            0x00c
+#define SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(dac, x)        ((4 - (x)) << (dac * 3))
+
+#define SUN4I_TVE_CHROMA_FREQ_REG      0x010
+
+#define SUN4I_TVE_PORCH_REG            0x014
+#define SUN4I_TVE_PORCH_BACK(x)                        ((x) << 16)
+#define SUN4I_TVE_PORCH_FRONT(x)               (x)
+
+#define SUN4I_TVE_LINE_REG             0x01c
+#define SUN4I_TVE_LINE_FIRST(x)                        ((x) << 16)
+#define SUN4I_TVE_LINE_NUMBER(x)               (x)
+
+#define SUN4I_TVE_LEVEL_REG            0x020
+#define SUN4I_TVE_LEVEL_BLANK(x)               ((x) << 16)
+#define SUN4I_TVE_LEVEL_BLACK(x)               (x)
+
+#define SUN4I_TVE_DAC1_REG             0x024
+#define SUN4I_TVE_DAC1_AMPLITUDE(dac, x)       ((x) << (dac * 8))
+
+#define SUN4I_TVE_DETECT_STA_REG       0x038
+#define SUN4I_TVE_DETECT_STA_DAC(dac)          BIT((dac * 8))
+#define SUN4I_TVE_DETECT_STA_UNCONNECTED               0
+#define SUN4I_TVE_DETECT_STA_CONNECTED                 1
+#define SUN4I_TVE_DETECT_STA_GROUND                    2
+
+#define SUN4I_TVE_CB_CR_LVL_REG                0x10c
+#define SUN4I_TVE_CB_CR_LVL_CR_BURST(x)                ((x) << 8)
+#define SUN4I_TVE_CB_CR_LVL_CB_BURST(x)                (x)
+
+#define SUN4I_TVE_TINT_BURST_PHASE_REG 0x110
+#define SUN4I_TVE_TINT_BURST_PHASE_CHROMA(x)   (x)
+
+#define SUN4I_TVE_BURST_WIDTH_REG      0x114
+#define SUN4I_TVE_BURST_WIDTH_BREEZEWAY(x)     ((x) << 16)
+#define SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(x)   ((x) << 8)
+#define SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(x)   (x)
+
+#define SUN4I_TVE_CB_CR_GAIN_REG       0x118
+#define SUN4I_TVE_CB_CR_GAIN_CR(x)             ((x) << 8)
+#define SUN4I_TVE_CB_CR_GAIN_CB(x)             (x)
+
+#define SUN4I_TVE_SYNC_VBI_REG         0x11c
+#define SUN4I_TVE_SYNC_VBI_SYNC(x)             ((x) << 16)
+#define SUN4I_TVE_SYNC_VBI_VBLANK(x)           (x)
+
+#define SUN4I_TVE_ACTIVE_LINE_REG      0x124
+#define SUN4I_TVE_ACTIVE_LINE(x)               (x)
+
+#define SUN4I_TVE_CHROMA_REG           0x128
+#define SUN4I_TVE_CHROMA_COMP_GAIN(x)          ((x) & 3)
+#define SUN4I_TVE_CHROMA_COMP_GAIN_50          SUN4I_TVE_CHROMA_COMP_GAIN(2)
+
+#define SUN4I_TVE_12C_REG              0x12c
+#define SUN4I_TVE_12C_NOTCH_WIDTH_WIDE         BIT(8)
+#define SUN4I_TVE_12C_COMP_YUV_EN              BIT(0)
+
+#define SUN4I_TVE_RESYNC_REG           0x130
+#define SUN4I_TVE_RESYNC_FIELD                 BIT(31)
+#define SUN4I_TVE_RESYNC_LINE(x)               ((x) << 16)
+#define SUN4I_TVE_RESYNC_PIXEL(x)              (x)
+
+#define SUN4I_TVE_SLAVE_REG            0x134
+
+#define SUN4I_TVE_WSS_DATA2_REG                0x244
+
+struct color_gains {
+       u16     cb;
+       u16     cr;
+};
+
+struct burst_levels {
+       u16     cb;
+       u16     cr;
+};
+
+struct video_levels {
+       u16     black;
+       u16     blank;
+};
+
+struct resync_parameters {
+       bool    field;
+       u16     line;
+       u16     pixel;
+};
+
+struct tv_mode {
+       char            *name;
+
+       u32             mode;
+       u32             chroma_freq;
+       u16             back_porch;
+       u16             front_porch;
+       u16             line_number;
+       u16             vblank_level;
+
+       u32             hdisplay;
+       u16             hfront_porch;
+       u16             hsync_len;
+       u16             hback_porch;
+
+       u32             vdisplay;
+       u16             vfront_porch;
+       u16             vsync_len;
+       u16             vback_porch;
+
+       bool            yc_en;
+       bool            dac3_en;
+       bool            dac_bit25_en;
+
+       struct color_gains              *color_gains;
+       struct burst_levels             *burst_levels;
+       struct video_levels             *video_levels;
+       struct resync_parameters        *resync_params;
+};
+
+struct sun4i_tv {
+       struct drm_connector    connector;
+       struct drm_encoder      encoder;
+
+       struct clk              *clk;
+       struct regmap           *regs;
+       struct reset_control    *reset;
+
+       struct sun4i_drv        *drv;
+};
+
+struct video_levels ntsc_video_levels = {
+       .black = 282,   .blank = 240,
+};
+
+struct video_levels pal_video_levels = {
+       .black = 252,   .blank = 252,
+};
+
+struct burst_levels ntsc_burst_levels = {
+       .cb = 79,       .cr = 0,
+};
+
+struct burst_levels pal_burst_levels = {
+       .cb = 40,       .cr = 40,
+};
+
+struct color_gains ntsc_color_gains = {
+       .cb = 160,      .cr = 160,
+};
+
+struct color_gains pal_color_gains = {
+       .cb = 224,      .cr = 224,
+};
+
+struct resync_parameters ntsc_resync_parameters = {
+       .field = false, .line = 14,     .pixel = 12,
+};
+
+struct resync_parameters pal_resync_parameters = {
+       .field = true,  .line = 13,     .pixel = 12,
+};
+
+struct tv_mode tv_modes[] = {
+       {
+               .name           = "NTSC",
+               .mode           = SUN4I_TVE_CFG0_RES_480i,
+               .chroma_freq    = 0x21f07c1f,
+               .yc_en          = true,
+               .dac3_en        = true,
+               .dac_bit25_en   = true,
+
+               .back_porch     = 118,
+               .front_porch    = 32,
+               .line_number    = 525,
+
+               .hdisplay       = 720,
+               .hfront_porch   = 18,
+               .hsync_len      = 2,
+               .hback_porch    = 118,
+
+               .vdisplay       = 480,
+               .vfront_porch   = 26,
+               .vsync_len      = 2,
+               .vback_porch    = 17,
+
+               .vblank_level   = 240,
+
+               .color_gains    = &ntsc_color_gains,
+               .burst_levels   = &ntsc_burst_levels,
+               .video_levels   = &ntsc_video_levels,
+               .resync_params  = &ntsc_resync_parameters,
+       },
+       {
+               .name           = "PAL",
+               .mode           = SUN4I_TVE_CFG0_RES_576i,
+               .chroma_freq    = 0x2a098acb,
+
+               .back_porch     = 138,
+               .front_porch    = 24,
+               .line_number    = 625,
+
+               .hdisplay       = 720,
+               .hfront_porch   = 3,
+               .hsync_len      = 2,
+               .hback_porch    = 139,
+
+               .vdisplay       = 576,
+               .vfront_porch   = 28,
+               .vsync_len      = 2,
+               .vback_porch    = 19,
+
+               .vblank_level   = 252,
+
+               .color_gains    = &pal_color_gains,
+               .burst_levels   = &pal_burst_levels,
+               .video_levels   = &pal_video_levels,
+               .resync_params  = &pal_resync_parameters,
+       },
+};
+
+static inline struct sun4i_tv *
+drm_encoder_to_sun4i_tv(struct drm_encoder *encoder)
+{
+       return container_of(encoder, struct sun4i_tv,
+                           encoder);
+}
+
+static inline struct sun4i_tv *
+drm_connector_to_sun4i_tv(struct drm_connector *connector)
+{
+       return container_of(connector, struct sun4i_tv,
+                           connector);
+}
+
+/*
+ * FIXME: If only the drm_display_mode private field was usable, this
+ * could go away...
+ *
+ * So far, it doesn't seem to be preserved when the mode is passed by
+ * to mode_set for some reason.
+ */
+static struct tv_mode *sun4i_tv_find_tv_by_mode(struct drm_display_mode *mode)
+{
+       int i;
+
+       /* First try to identify the mode by name */
+       for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
+               struct tv_mode *tv_mode = &tv_modes[i];
+
+               DRM_DEBUG_DRIVER("Comparing mode %s vs %s",
+                                mode->name, tv_mode->name);
+
+               if (!strcmp(mode->name, tv_mode->name))
+                       return tv_mode;
+       }
+
+       /* Then by number of lines */
+       for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
+               struct tv_mode *tv_mode = &tv_modes[i];
+
+               DRM_DEBUG_DRIVER("Comparing mode %s vs %s (X: %d vs %d)",
+                                mode->name, tv_mode->name,
+                                mode->vdisplay, tv_mode->vdisplay);
+
+               if (mode->vdisplay == tv_mode->vdisplay)
+                       return tv_mode;
+       }
+
+       return NULL;
+}
+
+static void sun4i_tv_mode_to_drm_mode(struct tv_mode *tv_mode,
+                                     struct drm_display_mode *mode)
+{
+       DRM_DEBUG_DRIVER("Creating mode %s\n", mode->name);
+
+       mode->type = DRM_MODE_TYPE_DRIVER;
+       mode->clock = 13500;
+       mode->flags = DRM_MODE_FLAG_INTERLACE;
+
+       mode->hdisplay = tv_mode->hdisplay;
+       mode->hsync_start = mode->hdisplay + tv_mode->hfront_porch;
+       mode->hsync_end = mode->hsync_start + tv_mode->hsync_len;
+       mode->htotal = mode->hsync_end  + tv_mode->hback_porch;
+
+       mode->vdisplay = tv_mode->vdisplay;
+       mode->vsync_start = mode->vdisplay + tv_mode->vfront_porch;
+       mode->vsync_end = mode->vsync_start + tv_mode->vsync_len;
+       mode->vtotal = mode->vsync_end  + tv_mode->vback_porch;
+}
+
+static int sun4i_tv_atomic_check(struct drm_encoder *encoder,
+                                struct drm_crtc_state *crtc_state,
+                                struct drm_connector_state *conn_state)
+{
+       return 0;
+}
+
+static void sun4i_tv_disable(struct drm_encoder *encoder)
+{
+       struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
+       struct sun4i_drv *drv = tv->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       DRM_DEBUG_DRIVER("Disabling the TV Output\n");
+
+       sun4i_tcon_channel_disable(tcon, 1);
+
+       regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
+                          SUN4I_TVE_EN_ENABLE,
+                          0);
+       sun4i_backend_disable_color_correction(drv->backend);
+}
+
+static void sun4i_tv_enable(struct drm_encoder *encoder)
+{
+       struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
+       struct sun4i_drv *drv = tv->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+
+       DRM_DEBUG_DRIVER("Enabling the TV Output\n");
+
+       sun4i_backend_apply_color_correction(drv->backend);
+
+       regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
+                          SUN4I_TVE_EN_ENABLE,
+                          SUN4I_TVE_EN_ENABLE);
+
+       sun4i_tcon_channel_enable(tcon, 1);
+}
+
+static void sun4i_tv_mode_set(struct drm_encoder *encoder,
+                             struct drm_display_mode *mode,
+                             struct drm_display_mode *adjusted_mode)
+{
+       struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
+       struct sun4i_drv *drv = tv->drv;
+       struct sun4i_tcon *tcon = drv->tcon;
+       struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
+
+       sun4i_tcon1_mode_set(tcon, mode);
+
+       /* Enable and map the DAC to the output */
+       regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
+                          SUN4I_TVE_EN_DAC_MAP_MASK,
+                          SUN4I_TVE_EN_DAC_MAP(0, 1) |
+                          SUN4I_TVE_EN_DAC_MAP(1, 2) |
+                          SUN4I_TVE_EN_DAC_MAP(2, 3) |
+                          SUN4I_TVE_EN_DAC_MAP(3, 4));
+
+       /* Set PAL settings */
+       regmap_write(tv->regs, SUN4I_TVE_CFG0_REG,
+                    tv_mode->mode |
+                    (tv_mode->yc_en ? SUN4I_TVE_CFG0_YC_EN : 0) |
+                    SUN4I_TVE_CFG0_COMP_EN |
+                    SUN4I_TVE_CFG0_DAC_CONTROL_54M |
+                    SUN4I_TVE_CFG0_CORE_DATAPATH_54M |
+                    SUN4I_TVE_CFG0_CORE_CONTROL_54M);
+
+       /* Configure the DAC for a composite output */
+       regmap_write(tv->regs, SUN4I_TVE_DAC0_REG,
+                    SUN4I_TVE_DAC0_DAC_EN(0) |
+                    (tv_mode->dac3_en ? SUN4I_TVE_DAC0_DAC_EN(3) : 0) |
+                    SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS |
+                    SUN4I_TVE_DAC0_CHROMA_0_75 |
+                    SUN4I_TVE_DAC0_LUMA_0_4 |
+                    SUN4I_TVE_DAC0_CLOCK_INVERT |
+                    (tv_mode->dac_bit25_en ? BIT(25) : 0) |
+                    BIT(30));
+
+       /* Configure the sample delay between DAC0 and the other DAC */
+       regmap_write(tv->regs, SUN4I_TVE_NOTCH_REG,
+                    SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(1, 0) |
+                    SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(2, 0));
+
+       regmap_write(tv->regs, SUN4I_TVE_CHROMA_FREQ_REG,
+                    tv_mode->chroma_freq);
+
+       /* Set the front and back porch */
+       regmap_write(tv->regs, SUN4I_TVE_PORCH_REG,
+                    SUN4I_TVE_PORCH_BACK(tv_mode->back_porch) |
+                    SUN4I_TVE_PORCH_FRONT(tv_mode->front_porch));
+
+       /* Set the lines setup */
+       regmap_write(tv->regs, SUN4I_TVE_LINE_REG,
+                    SUN4I_TVE_LINE_FIRST(22) |
+                    SUN4I_TVE_LINE_NUMBER(tv_mode->line_number));
+
+       regmap_write(tv->regs, SUN4I_TVE_LEVEL_REG,
+                    SUN4I_TVE_LEVEL_BLANK(tv_mode->video_levels->blank) |
+                    SUN4I_TVE_LEVEL_BLACK(tv_mode->video_levels->black));
+
+       regmap_write(tv->regs, SUN4I_TVE_DAC1_REG,
+                    SUN4I_TVE_DAC1_AMPLITUDE(0, 0x18) |
+                    SUN4I_TVE_DAC1_AMPLITUDE(1, 0x18) |
+                    SUN4I_TVE_DAC1_AMPLITUDE(2, 0x18) |
+                    SUN4I_TVE_DAC1_AMPLITUDE(3, 0x18));
+
+       regmap_write(tv->regs, SUN4I_TVE_CB_CR_LVL_REG,
+                    SUN4I_TVE_CB_CR_LVL_CB_BURST(tv_mode->burst_levels->cb) |
+                    SUN4I_TVE_CB_CR_LVL_CR_BURST(tv_mode->burst_levels->cr));
+
+       /* Set burst width for a composite output */
+       regmap_write(tv->regs, SUN4I_TVE_BURST_WIDTH_REG,
+                    SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(126) |
+                    SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(68) |
+                    SUN4I_TVE_BURST_WIDTH_BREEZEWAY(22));
+
+       regmap_write(tv->regs, SUN4I_TVE_CB_CR_GAIN_REG,
+                    SUN4I_TVE_CB_CR_GAIN_CB(tv_mode->color_gains->cb) |
+                    SUN4I_TVE_CB_CR_GAIN_CR(tv_mode->color_gains->cr));
+
+       regmap_write(tv->regs, SUN4I_TVE_SYNC_VBI_REG,
+                    SUN4I_TVE_SYNC_VBI_SYNC(0x10) |
+                    SUN4I_TVE_SYNC_VBI_VBLANK(tv_mode->vblank_level));
+
+       regmap_write(tv->regs, SUN4I_TVE_ACTIVE_LINE_REG,
+                    SUN4I_TVE_ACTIVE_LINE(1440));
+
+       /* Set composite chroma gain to 50 % */
+       regmap_write(tv->regs, SUN4I_TVE_CHROMA_REG,
+                    SUN4I_TVE_CHROMA_COMP_GAIN_50);
+
+       regmap_write(tv->regs, SUN4I_TVE_12C_REG,
+                    SUN4I_TVE_12C_COMP_YUV_EN |
+                    SUN4I_TVE_12C_NOTCH_WIDTH_WIDE);
+
+       regmap_write(tv->regs, SUN4I_TVE_RESYNC_REG,
+                    SUN4I_TVE_RESYNC_PIXEL(tv_mode->resync_params->pixel) |
+                    SUN4I_TVE_RESYNC_LINE(tv_mode->resync_params->line) |
+                    (tv_mode->resync_params->field ?
+                     SUN4I_TVE_RESYNC_FIELD : 0));
+
+       regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0);
+
+       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+}
+
+static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = {
+       .atomic_check   = sun4i_tv_atomic_check,
+       .disable        = sun4i_tv_disable,
+       .enable         = sun4i_tv_enable,
+       .mode_set       = sun4i_tv_mode_set,
+};
+
+static void sun4i_tv_destroy(struct drm_encoder *encoder)
+{
+       drm_encoder_cleanup(encoder);
+}
+
+static struct drm_encoder_funcs sun4i_tv_funcs = {
+       .destroy        = sun4i_tv_destroy,
+};
+
+static int sun4i_tv_comp_get_modes(struct drm_connector *connector)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
+               struct drm_display_mode *mode = drm_mode_create(connector->dev);
+               struct tv_mode *tv_mode = &tv_modes[i];
+
+               strcpy(mode->name, tv_mode->name);
+
+               sun4i_tv_mode_to_drm_mode(tv_mode, mode);
+               drm_mode_probed_add(connector, mode);
+       }
+
+       return i;
+}
+
+static int sun4i_tv_comp_mode_valid(struct drm_connector *connector,
+                                   struct drm_display_mode *mode)
+{
+       /* TODO */
+       return MODE_OK;
+}
+
+static struct drm_encoder *
+sun4i_tv_comp_best_encoder(struct drm_connector *connector)
+{
+       struct sun4i_tv *tv = drm_connector_to_sun4i_tv(connector);
+
+       return &tv->encoder;
+}
+
+static struct drm_connector_helper_funcs sun4i_tv_comp_connector_helper_funcs = {
+       .get_modes      = sun4i_tv_comp_get_modes,
+       .mode_valid     = sun4i_tv_comp_mode_valid,
+       .best_encoder   = sun4i_tv_comp_best_encoder,
+};
+
+static enum drm_connector_status
+sun4i_tv_comp_connector_detect(struct drm_connector *connector, bool force)
+{
+       return connector_status_connected;
+}
+
+static void
+sun4i_tv_comp_connector_destroy(struct drm_connector *connector)
+{
+       drm_connector_cleanup(connector);
+}
+
+static struct drm_connector_funcs sun4i_tv_comp_connector_funcs = {
+       .dpms                   = drm_atomic_helper_connector_dpms,
+       .detect                 = sun4i_tv_comp_connector_detect,
+       .fill_modes             = drm_helper_probe_single_connector_modes,
+       .destroy                = sun4i_tv_comp_connector_destroy,
+       .reset                  = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
+};
+
+static struct regmap_config sun4i_tv_regmap_config = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = SUN4I_TVE_WSS_DATA2_REG,
+       .name           = "tv-encoder",
+};
+
+static int sun4i_tv_bind(struct device *dev, struct device *master,
+                        void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct drm_device *drm = data;
+       struct sun4i_drv *drv = drm->dev_private;
+       struct sun4i_tv *tv;
+       struct resource *res;
+       void __iomem *regs;
+       int ret;
+
+       tv = devm_kzalloc(dev, sizeof(*tv), GFP_KERNEL);
+       if (!tv)
+               return -ENOMEM;
+       tv->drv = drv;
+       dev_set_drvdata(dev, tv);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       regs = devm_ioremap_resource(dev, res);
+       if (IS_ERR(regs)) {
+               dev_err(dev, "Couldn't map the TV encoder registers\n");
+               return PTR_ERR(regs);
+       }
+
+       tv->regs = devm_regmap_init_mmio(dev, regs,
+                                        &sun4i_tv_regmap_config);
+       if (IS_ERR(tv->regs)) {
+               dev_err(dev, "Couldn't create the TV encoder regmap\n");
+               return PTR_ERR(tv->regs);
+       }
+
+       tv->reset = devm_reset_control_get(dev, NULL);
+       if (IS_ERR(tv->reset)) {
+               dev_err(dev, "Couldn't get our reset line\n");
+               return PTR_ERR(tv->reset);
+       }
+
+       ret = reset_control_deassert(tv->reset);
+       if (ret) {
+               dev_err(dev, "Couldn't deassert our reset line\n");
+               return ret;
+       }
+
+       tv->clk = devm_clk_get(dev, NULL);
+       if (IS_ERR(tv->clk)) {
+               dev_err(dev, "Couldn't get the TV encoder clock\n");
+               ret = PTR_ERR(tv->clk);
+               goto err_assert_reset;
+       }
+       clk_prepare_enable(tv->clk);
+
+       drm_encoder_helper_add(&tv->encoder,
+                              &sun4i_tv_helper_funcs);
+       ret = drm_encoder_init(drm,
+                              &tv->encoder,
+                              &sun4i_tv_funcs,
+                              DRM_MODE_ENCODER_TVDAC,
+                              NULL);
+       if (ret) {
+               dev_err(dev, "Couldn't initialise the TV encoder\n");
+               goto err_disable_clk;
+       }
+
+       tv->encoder.possible_crtcs = BIT(0);
+
+       drm_connector_helper_add(&tv->connector,
+                                &sun4i_tv_comp_connector_helper_funcs);
+       ret = drm_connector_init(drm, &tv->connector,
+                                &sun4i_tv_comp_connector_funcs,
+                                DRM_MODE_CONNECTOR_Composite);
+       if (ret) {
+               dev_err(dev,
+                       "Couldn't initialise the Composite connector\n");
+               goto err_cleanup_connector;
+       }
+       tv->connector.interlace_allowed = true;
+
+       drm_mode_connector_attach_encoder(&tv->connector, &tv->encoder);
+
+       return 0;
+
+err_cleanup_connector:
+       drm_encoder_cleanup(&tv->encoder);
+err_disable_clk:
+       clk_disable_unprepare(tv->clk);
+err_assert_reset:
+       reset_control_assert(tv->reset);
+       return ret;
+}
+
+static void sun4i_tv_unbind(struct device *dev, struct device *master,
+                           void *data)
+{
+       struct sun4i_tv *tv = dev_get_drvdata(dev);
+
+       drm_connector_cleanup(&tv->connector);
+       drm_encoder_cleanup(&tv->encoder);
+       clk_disable_unprepare(tv->clk);
+}
+
+static struct component_ops sun4i_tv_ops = {
+       .bind   = sun4i_tv_bind,
+       .unbind = sun4i_tv_unbind,
+};
+
+static int sun4i_tv_probe(struct platform_device *pdev)
+{
+       return component_add(&pdev->dev, &sun4i_tv_ops);
+}
+
+static int sun4i_tv_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &sun4i_tv_ops);
+
+       return 0;
+}
+
+static const struct of_device_id sun4i_tv_of_table[] = {
+       { .compatible = "allwinner,sun4i-a10-tv-encoder" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sun4i_tv_of_table);
+
+static struct platform_driver sun4i_tv_platform_driver = {
+       .probe          = sun4i_tv_probe,
+       .remove         = sun4i_tv_remove,
+       .driver         = {
+               .name           = "sun4i-tve",
+               .of_match_table = sun4i_tv_of_table,
+       },
+};
+module_platform_driver(sun4i_tv_platform_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 TV Encoder Driver");
+MODULE_LICENSE("GPL");
index 43c3149..297e527 100644 (file)
@@ -250,7 +250,6 @@ struct drm_framebuffer {
 struct drm_property_blob {
        struct drm_mode_object base;
        struct drm_device *dev;
-       struct kref refcount;
        struct list_head head_global;
        struct list_head head_file;
        size_t length;
index be62bd3..ae49c24 100644 (file)
@@ -24,6 +24,8 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb,
        unsigned int plane);
 
 #ifdef CONFIG_DEBUG_FS
+struct seq_file;
+
 int drm_fb_cma_debugfs_show(struct seq_file *m, void *arg);
 #endif
 
index 4d1e326..826615d 100644 (file)
@@ -84,7 +84,6 @@ struct drm_qxl_command {
        __u32                pad;
 };
 
-/* XXX: call it drm_qxl_commands? */
 struct drm_qxl_execbuffer {
        __u32           flags;          /* for future use */
        __u32           commands_num;
index 374858c..a20d88b 100644 (file)
@@ -27,6 +27,8 @@
 #ifndef __SIS_DRM_H__
 #define __SIS_DRM_H__
 
+#include "drm.h"
+
 /* SiS specific ioctls */
 #define NOT_USED_0_3
 #define DRM_SIS_FB_ALLOC       0x04