Browse Source

Merge branch 'drm/next/du' of git://linuxtv.org/pinchartl/media into drm-next

RCAR GEN3 DU HDMI support.

* 'drm/next/du' of git://linuxtv.org/pinchartl/media: (22 commits)
  drm: rcar-du: Add HDMI outputs to R8A7795 device description
  drm: rcar-du: Add DPLL support
  drm: rcar-du: Skip disabled outputs
  drm: rcar-du: Add Gen3 HDMI encoder support
  dt-bindings: display: renesas: Add R-Car Gen3 HDMI TX DT bindings
  drm: rcar-du: Hardcode encoders types to DRM_MODE_ENCODER_NONE
  drm: rcar-du: Replace manual bridge implementation with DRM bridge
  drm: rcar-du: Add support for LVDS mode selection
  drm: rcar-du: Use the DRM panel API
  drm: rcar-du: Document the vsps property in the DT bindings
  drm: rcar-du: Remove wait field from rcar_du_device structure
  drm: rcar-du: Make sure the VSP is initialized on platforms that need it
  drm: rcar-du: Use DRM core's atomic commit helper
  drm: rcar-du: Clear handled event pointer in CRTC state
  drm: rcar-du: Handle event when disabling CRTCs
  drm: rcar-du: Don't open code of_device_get_match_data()
  drm: rcar-du: Switch to encoder .atomic_mode_set() helper function
  drm: panels: Add LVDS panel driver
  drm: Add data transmission order bus flag
  devicetree/bindings: display: Add bindings for two Mitsubishi panels
  ...
Dave Airlie 8 năm trước cách đây
mục cha
commit
fabe2be10f
30 tập tin đã thay đổi với 1109 bổ sung565 xóa
  1. 75 0
      Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt
  2. 47 0
      Documentation/devicetree/bindings/display/panel/mitsubishi,aa104xd12.txt
  3. 47 0
      Documentation/devicetree/bindings/display/panel/mitsubishi,aa121td01.txt
  4. 91 0
      Documentation/devicetree/bindings/display/panel/panel-common.txt
  5. 120 0
      Documentation/devicetree/bindings/display/panel/panel-lvds.txt
  6. 3 0
      Documentation/devicetree/bindings/display/renesas,du.txt
  7. 1 0
      MAINTAINERS
  8. 10 0
      drivers/gpu/drm/panel/Kconfig
  9. 1 0
      drivers/gpu/drm/panel/Makefile
  10. 286 0
      drivers/gpu/drm/panel/panel-lvds.c
  11. 6 4
      drivers/gpu/drm/rcar-du/Kconfig
  12. 2 4
      drivers/gpu/drm/rcar-du/Makefile
  13. 90 4
      drivers/gpu/drm/rcar-du/rcar_du_crtc.c
  14. 3 1
      drivers/gpu/drm/rcar-du/rcar_du_crtc.h
  15. 12 20
      drivers/gpu/drm/rcar-du/rcar_du_drv.c
  16. 1 7
      drivers/gpu/drm/rcar-du/rcar_du_drv.h
  17. 119 68
      drivers/gpu/drm/rcar-du/rcar_du_encoder.c
  18. 3 11
      drivers/gpu/drm/rcar-du/rcar_du_encoder.h
  19. 0 134
      drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c
  20. 0 35
      drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h
  21. 18 125
      drivers/gpu/drm/rcar-du/rcar_du_kms.c
  22. 24 44
      drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
  23. 9 2
      drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
  24. 13 0
      drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
  25. 23 0
      drivers/gpu/drm/rcar-du/rcar_du_regs.h
  26. 0 82
      drivers/gpu/drm/rcar-du/rcar_du_vgacon.c
  27. 0 23
      drivers/gpu/drm/rcar-du/rcar_du_vgacon.h
  28. 1 1
      drivers/gpu/drm/rcar-du/rcar_du_vsp.h
  29. 100 0
      drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c
  30. 4 0
      include/drm/drm_connector.h

+ 75 - 0
Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt

@@ -0,0 +1,75 @@
+Renesas Gen3 DWC HDMI TX Encoder
+================================
+
+The HDMI transmitter is a Synopsys DesignWare HDMI 1.4 TX controller IP
+with a companion PHY IP.
+
+These DT bindings follow the Synopsys DWC HDMI TX bindings defined in
+Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt with the
+following device-specific properties.
+
+
+Required properties:
+
+- compatible : Shall contain one or more of
+  - "renesas,r8a7795-hdmi" for R8A7795 (R-Car H3) compatible HDMI TX
+  - "renesas,rcar-gen3-hdmi" for the generic R-Car Gen3 compatible HDMI TX
+
+    When compatible with generic versions, nodes must list the SoC-specific
+    version corresponding to the platform first, followed by the
+    family-specific version.
+
+- reg: See dw_hdmi.txt.
+- interrupts: HDMI interrupt number
+- clocks: See dw_hdmi.txt.
+- clock-names: Shall contain "iahb" and "isfr" as defined in dw_hdmi.txt.
+- ports: See dw_hdmi.txt. The DWC HDMI shall have one port numbered 0
+  corresponding to the video input of the controller and one port numbered 1
+  corresponding to its HDMI output. Each port shall have a single endpoint.
+
+Optional properties:
+
+- power-domains: Shall reference the power domain that contains the DWC HDMI,
+  if any.
+
+
+Example:
+
+	hdmi0: hdmi0@fead0000 {
+		compatible = "renesas,r8a7795-dw-hdmi";
+		reg = <0 0xfead0000 0 0x10000>;
+		interrupts = <0 389 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&cpg CPG_CORE R8A7795_CLK_S0D4>, <&cpg CPG_MOD 729>;
+		clock-names = "iahb", "isfr";
+		power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				dw_hdmi0_in: endpoint {
+					remote-endpoint = <&du_out_hdmi0>;
+				};
+			};
+			port@1 {
+				reg = <1>;
+				rcar_dw_hdmi0_out: endpoint {
+					remote-endpoint = <&hdmi0_con>;
+				};
+			};
+		};
+	};
+
+	hdmi0-out {
+		compatible = "hdmi-connector";
+		label = "HDMI0 OUT";
+		type = "a";
+
+		port {
+			hdmi0_con: endpoint {
+				remote-endpoint = <&rcar_dw_hdmi0_out>;
+			};
+		};
+	};

+ 47 - 0
Documentation/devicetree/bindings/display/panel/mitsubishi,aa104xd12.txt

@@ -0,0 +1,47 @@
+Mitsubishi AA204XD12 LVDS Display Panel
+=======================================
+
+The AA104XD12 is a 10.4" XGA TFT-LCD display panel.
+
+These DT bindings follow the LVDS panel bindings defined in panel-lvds.txt
+with the following device-specific properties.
+
+
+Required properties:
+
+- compatible: Shall contain "mitsubishi,aa121td01" and "panel-lvds", in that
+  order.
+- vcc-supply: Reference to the regulator powering the panel VCC pins.
+
+
+Example
+-------
+
+panel {
+	compatible = "mitsubishi,aa104xd12", "panel-lvds";
+	vcc-supply = <&vcc_3v3>;
+
+	width-mm = <210>;
+	height-mm = <158>;
+
+	data-mapping = "jeida-24";
+
+	panel-timing {
+		/* 1024x768 @65Hz */
+		clock-frequency = <65000000>;
+		hactive = <1024>;
+		vactive = <768>;
+		hsync-len = <136>;
+		hfront-porch = <20>;
+		hback-porch = <160>;
+		vfront-porch = <3>;
+		vback-porch = <29>;
+		vsync-len = <6>;
+	};
+
+	port {
+		panel_in: endpoint {
+			remote-endpoint = <&lvds_encoder>;
+		};
+	};
+};

+ 47 - 0
Documentation/devicetree/bindings/display/panel/mitsubishi,aa121td01.txt

@@ -0,0 +1,47 @@
+Mitsubishi AA121TD01 LVDS Display Panel
+=======================================
+
+The AA121TD01 is a 12.1" WXGA TFT-LCD display panel.
+
+These DT bindings follow the LVDS panel bindings defined in panel-lvds.txt
+with the following device-specific properties.
+
+
+Required properties:
+
+- compatible: Shall contain "mitsubishi,aa121td01" and "panel-lvds", in that
+  order.
+- vcc-supply: Reference to the regulator powering the panel VCC pins.
+
+
+Example
+-------
+
+panel {
+	compatible = "mitsubishi,aa121td01", "panel-lvds";
+	vcc-supply = <&vcc_3v3>;
+
+	width-mm = <261>;
+	height-mm = <163>;
+
+	data-mapping = "jeida-24";
+
+	panel-timing {
+		/* 1280x800 @60Hz */
+		clock-frequency = <71000000>;
+		hactive = <1280>;
+		vactive = <800>;
+		hsync-len = <70>;
+		hfront-porch = <20>;
+		hback-porch = <70>;
+		vsync-len = <5>;
+		vfront-porch = <3>;
+		vback-porch = <15>;
+	};
+
+	port {
+		panel_in: endpoint {
+			remote-endpoint = <&lvds_encoder>;
+		};
+	};
+};

+ 91 - 0
Documentation/devicetree/bindings/display/panel/panel-common.txt

@@ -0,0 +1,91 @@
+Common Properties for Display Panel
+===================================
+
+This document defines device tree properties common to several classes of
+display panels. It doesn't constitue a device tree binding specification by
+itself but is meant to be referenced by device tree bindings.
+
+When referenced from panel device tree bindings the properties defined in this
+document are defined as follows. The panel device tree bindings are
+responsible for defining whether each property is required or optional.
+
+
+Descriptive Properties
+----------------------
+
+- width-mm,
+- height-mm: The width-mm and height-mm specify the width and height of the
+  physical area where images are displayed. These properties are expressed in
+  millimeters and rounded to the closest unit.
+
+- label: The label property specifies a symbolic name for the panel as a
+  string suitable for use by humans. It typically contains a name inscribed on
+  the system (e.g. as an affixed label) or specified in the system's
+  documentation (e.g. in the user's manual).
+
+  If no such name exists, and unless the property is mandatory according to
+  device tree bindings, it shall rather be omitted than constructed of
+  non-descriptive information. For instance an LCD panel in a system that
+  contains a single panel shall not be labelled "LCD" if that name is not
+  inscribed on the system or used in a descriptive fashion in system
+  documentation.
+
+
+Display Timings
+---------------
+
+- panel-timing: Most display panels are restricted to a single resolution and
+  require specific display timings. The panel-timing subnode expresses those
+  timings as specified in the timing subnode section of the display timing
+  bindings defined in
+  Documentation/devicetree/bindings/display/display-timing.txt.
+
+
+Connectivity
+------------
+
+- ports: Panels receive video data through one or multiple connections. While
+  the nature of those connections is specific to the panel type, the
+  connectivity is expressed in a standard fashion using ports as specified in
+  the device graph bindings defined in
+  Documentation/devicetree/bindings/graph.txt.
+
+- ddc-i2c-bus: Some panels expose EDID information through an I2C-compatible
+  bus such as DDC2 or E-DDC. For such panels the ddc-i2c-bus contains a
+  phandle to the system I2C controller connected to that bus.
+
+
+Control I/Os
+------------
+
+Many display panels can be controlled through pins driven by GPIOs. The nature
+and timing of those control signals are device-specific and left for panel
+device tree bindings to specify. The following GPIO specifiers can however be
+used for panels that implement compatible control signals.
+
+- enable-gpios: Specifier for a GPIO connected to the panel enable control
+  signal. The enable signal is active high and enables operation of the panel.
+  This property can also be used for panels implementing an active low power
+  down signal, which is a negated version of the enable signal. Active low
+  enable signals (or active high power down signals) can be supported by
+  inverting the GPIO specifier polarity flag.
+
+  Note that the enable signal control panel operation only and must not be
+  confused with a backlight enable signal.
+
+- reset-gpios: Specifier for a GPIO coonnected to the panel reset control
+  signal. The reset signal is active low and resets the panel internal logic
+  while active. Active high reset signals can be supported by inverting the
+  GPIO specifier polarity flag.
+
+
+Backlight
+---------
+
+Most display panels include a backlight. Some of them also include a backlight
+controller exposed through a control bus such as I2C or DSI. Others expose
+backlight control through GPIO, PWM or other signals connected to an external
+backlight controller.
+
+- backlight: For panels whose backlight is controlled by an external backlight
+  controller, this property contains a phandle that references the controller.

+ 120 - 0
Documentation/devicetree/bindings/display/panel/panel-lvds.txt

@@ -0,0 +1,120 @@
+LVDS Display Panel
+==================
+
+LVDS is a physical layer specification defined in ANSI/TIA/EIA-644-A. Multiple
+incompatible data link layers have been used over time to transmit image data
+to LVDS panels. This bindings supports display panels compatible with the
+following specifications.
+
+[JEIDA] "Digital Interface Standards for Monitor", JEIDA-59-1999, February
+1999 (Version 1.0), Japan Electronic Industry Development Association (JEIDA)
+[LDI] "Open LVDS Display Interface", May 1999 (Version 0.95), National
+Semiconductor
+[VESA] "VESA Notebook Panel Standard", October 2007 (Version 1.0), Video
+Electronics Standards Association (VESA)
+
+Device compatible with those specifications have been marketed under the
+FPD-Link and FlatLink brands.
+
+
+Required properties:
+
+- compatible: Shall contain "panel-lvds" in addition to a mandatory
+  panel-specific compatible string defined in individual panel bindings. The
+  "panel-lvds" value shall never be used on its own.
+- width-mm: See panel-common.txt.
+- height-mm: See panel-common.txt.
+- data-mapping: The color signals mapping order, "jeida-18", "jeida-24"
+  or "vesa-24".
+
+Optional properties:
+
+- label: See panel-common.txt.
+- gpios: See panel-common.txt.
+- backlight: See panel-common.txt.
+- data-mirror: If set, reverse the bit order described in the data mappings
+  below on all data lanes, transmitting bits for slots 6 to 0 instead of
+  0 to 6.
+
+Required nodes:
+
+- panel-timing: See panel-common.txt.
+- ports: See panel-common.txt. These bindings require a single port subnode
+  corresponding to the panel LVDS input.
+
+
+LVDS data mappings are defined as follows.
+
+- "jeida-18" - 18-bit data mapping compatible with the [JEIDA], [LDI] and
+  [VESA] specifications. Data are transferred as follows on 3 LVDS lanes.
+
+Slot	    0       1       2       3       4       5       6
+	________________                         _________________
+Clock	                \_______________________/
+	  ______  ______  ______  ______  ______  ______  ______
+DATA0	><__G0__><__R5__><__R4__><__R3__><__R2__><__R1__><__R0__><
+DATA1	><__B1__><__B0__><__G5__><__G4__><__G3__><__G2__><__G1__><
+DATA2	><_CTL2_><_CTL1_><_CTL0_><__B5__><__B4__><__B3__><__B2__><
+
+- "jeida-24" - 24-bit data mapping compatible with the [DSIM] and [LDI]
+  specifications. Data are transferred as follows on 4 LVDS lanes.
+
+Slot	    0       1       2       3       4       5       6
+	________________                         _________________
+Clock	                \_______________________/
+	  ______  ______  ______  ______  ______  ______  ______
+DATA0	><__G2__><__R7__><__R6__><__R5__><__R4__><__R3__><__R2__><
+DATA1	><__B3__><__B2__><__G7__><__G6__><__G5__><__G4__><__G3__><
+DATA2	><_CTL2_><_CTL1_><_CTL0_><__B7__><__B6__><__B5__><__B4__><
+DATA3	><_CTL3_><__B1__><__B0__><__G1__><__G0__><__R1__><__R0__><
+
+- "vesa-24" - 24-bit data mapping compatible with the [VESA] specification.
+  Data are transferred as follows on 4 LVDS lanes.
+
+Slot	    0       1       2       3       4       5       6
+	________________                         _________________
+Clock	                \_______________________/
+	  ______  ______  ______  ______  ______  ______  ______
+DATA0	><__G0__><__R5__><__R4__><__R3__><__R2__><__R1__><__R0__><
+DATA1	><__B1__><__B0__><__G5__><__G4__><__G3__><__G2__><__G1__><
+DATA2	><_CTL2_><_CTL1_><_CTL0_><__B5__><__B4__><__B3__><__B2__><
+DATA3	><_CTL3_><__B7__><__B6__><__G7__><__G6__><__R7__><__R6__><
+
+Control signals are mapped as follows.
+
+CTL0: HSync
+CTL1: VSync
+CTL2: Data Enable
+CTL3: 0
+
+
+Example
+-------
+
+panel {
+	compatible = "mitsubishi,aa121td01", "panel-lvds";
+
+	width-mm = <261>;
+	height-mm = <163>;
+
+	data-mapping = "jeida-24";
+
+	panel-timing {
+		/* 1280x800 @60Hz */
+		clock-frequency = <71000000>;
+		hactive = <1280>;
+		vactive = <800>;
+		hsync-len = <70>;
+		hfront-porch = <20>;
+		hback-porch = <70>;
+		vsync-len = <5>;
+		vfront-porch = <3>;
+		vback-porch = <15>;
+	};
+
+	port {
+		panel_in: endpoint {
+			remote-endpoint = <&lvds_encoder>;
+		};
+	};
+};

+ 3 - 0
Documentation/devicetree/bindings/display/renesas,du.txt

@@ -36,6 +36,9 @@ Required Properties:
       When supplied they must be named "dclkin.x" with "x" being the input
       clock numerical index.
 
+  - vsps: A list of phandles to the VSP nodes that handle the memory
+    interfaces for the DU channels.
+
 Required nodes:
 
 The connections to the DU output video ports are modeled using the OF graph

+ 1 - 0
MAINTAINERS

@@ -4383,6 +4383,7 @@ S:	Supported
 F:	drivers/gpu/drm/rcar-du/
 F:	drivers/gpu/drm/shmobile/
 F:	include/linux/platform_data/shmob_drm.h
+F:	Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt
 F:	Documentation/devicetree/bindings/display/renesas,du.txt
 
 DRM DRIVER FOR QXL VIRTUAL GPU

+ 10 - 0
drivers/gpu/drm/panel/Kconfig

@@ -7,6 +7,16 @@ config DRM_PANEL
 menu "Display Panels"
 	depends on DRM && DRM_PANEL
 
+config DRM_PANEL_LVDS
+	tristate "Generic LVDS panel driver"
+	depends on OF
+	depends on BACKLIGHT_CLASS_DEVICE
+	select VIDEOMODE_HELPERS
+	help
+	  This driver supports LVDS panels that don't require device-specific
+	  handling of power supplies or control signals. It implements automatic
+	  backlight handling if the panel is attached to a backlight controller.
+
 config DRM_PANEL_SIMPLE
 	tristate "support for simple panels"
 	depends on OF

+ 1 - 0
drivers/gpu/drm/panel/Makefile

@@ -1,3 +1,4 @@
+obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
 obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
 obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o

+ 286 - 0
drivers/gpu/drm/panel/panel-lvds.c

@@ -0,0 +1,286 @@
+/*
+ * rcar_du_crtc.c  --  R-Car Display Unit CRTCs
+ *
+ * Copyright (C) 2016 Laurent Pinchart
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/backlight.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_panel.h>
+
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+struct panel_lvds {
+	struct drm_panel panel;
+	struct device *dev;
+
+	const char *label;
+	unsigned int width;
+	unsigned int height;
+	struct videomode video_mode;
+	unsigned int bus_format;
+	bool data_mirror;
+
+	struct backlight_device *backlight;
+
+	struct gpio_desc *enable_gpio;
+	struct gpio_desc *reset_gpio;
+};
+
+static inline struct panel_lvds *to_panel_lvds(struct drm_panel *panel)
+{
+	return container_of(panel, struct panel_lvds, panel);
+}
+
+static int panel_lvds_disable(struct drm_panel *panel)
+{
+	struct panel_lvds *lvds = to_panel_lvds(panel);
+
+	if (lvds->backlight) {
+		lvds->backlight->props.power = FB_BLANK_POWERDOWN;
+		lvds->backlight->props.state |= BL_CORE_FBBLANK;
+		backlight_update_status(lvds->backlight);
+	}
+
+	return 0;
+}
+
+static int panel_lvds_unprepare(struct drm_panel *panel)
+{
+	struct panel_lvds *lvds = to_panel_lvds(panel);
+
+	if (lvds->enable_gpio)
+		gpiod_set_value_cansleep(lvds->enable_gpio, 0);
+
+	return 0;
+}
+
+static int panel_lvds_prepare(struct drm_panel *panel)
+{
+	struct panel_lvds *lvds = to_panel_lvds(panel);
+
+	if (lvds->enable_gpio)
+		gpiod_set_value_cansleep(lvds->enable_gpio, 1);
+
+	return 0;
+}
+
+static int panel_lvds_enable(struct drm_panel *panel)
+{
+	struct panel_lvds *lvds = to_panel_lvds(panel);
+
+	if (lvds->backlight) {
+		lvds->backlight->props.state &= ~BL_CORE_FBBLANK;
+		lvds->backlight->props.power = FB_BLANK_UNBLANK;
+		backlight_update_status(lvds->backlight);
+	}
+
+	return 0;
+}
+
+static int panel_lvds_get_modes(struct drm_panel *panel)
+{
+	struct panel_lvds *lvds = to_panel_lvds(panel);
+	struct drm_connector *connector = lvds->panel.connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_create(lvds->panel.drm);
+	if (!mode)
+		return 0;
+
+	drm_display_mode_from_videomode(&lvds->video_mode, mode);
+	mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = lvds->width;
+	connector->display_info.height_mm = lvds->height;
+	drm_display_info_set_bus_formats(&connector->display_info,
+					 &lvds->bus_format, 1);
+	connector->display_info.bus_flags = lvds->data_mirror
+					  ? DRM_BUS_FLAG_DATA_LSB_TO_MSB
+					  : DRM_BUS_FLAG_DATA_MSB_TO_LSB;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs panel_lvds_funcs = {
+	.disable = panel_lvds_disable,
+	.unprepare = panel_lvds_unprepare,
+	.prepare = panel_lvds_prepare,
+	.enable = panel_lvds_enable,
+	.get_modes = panel_lvds_get_modes,
+};
+
+static int panel_lvds_parse_dt(struct panel_lvds *lvds)
+{
+	struct device_node *np = lvds->dev->of_node;
+	struct display_timing timing;
+	const char *mapping;
+	int ret;
+
+	ret = of_get_display_timing(np, "panel-timing", &timing);
+	if (ret < 0)
+		return ret;
+
+	videomode_from_timing(&timing, &lvds->video_mode);
+
+	ret = of_property_read_u32(np, "width-mm", &lvds->width);
+	if (ret < 0) {
+		dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
+			of_node_full_name(np), "width-mm");
+		return -ENODEV;
+	}
+	ret = of_property_read_u32(np, "height-mm", &lvds->height);
+	if (ret < 0) {
+		dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
+			of_node_full_name(np), "height-mm");
+		return -ENODEV;
+	}
+
+	of_property_read_string(np, "label", &lvds->label);
+
+	ret = of_property_read_string(np, "data-mapping", &mapping);
+	if (ret < 0) {
+		dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
+			of_node_full_name(np), "data-mapping");
+		return -ENODEV;
+	}
+
+	if (!strcmp(mapping, "jeida-18")) {
+		lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
+	} else if (!strcmp(mapping, "jeida-24")) {
+		lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
+	} else if (!strcmp(mapping, "vesa-24")) {
+		lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
+	} else {
+		dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
+			of_node_full_name(np), "data-mapping");
+		return -EINVAL;
+	}
+
+	lvds->data_mirror = of_property_read_bool(np, "data-mirror");
+
+	return 0;
+}
+
+static int panel_lvds_probe(struct platform_device *pdev)
+{
+	struct panel_lvds *lvds;
+	struct device_node *np;
+	int ret;
+
+	lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+	if (!lvds)
+		return -ENOMEM;
+
+	lvds->dev = &pdev->dev;
+
+	ret = panel_lvds_parse_dt(lvds);
+	if (ret < 0)
+		return ret;
+
+	/* Get GPIOs and backlight controller. */
+	lvds->enable_gpio = devm_gpiod_get_optional(lvds->dev, "enable",
+						     GPIOD_OUT_LOW);
+	if (IS_ERR(lvds->enable_gpio)) {
+		ret = PTR_ERR(lvds->enable_gpio);
+		dev_err(lvds->dev, "failed to request %s GPIO: %d\n",
+			"enable", ret);
+		return ret;
+	}
+
+	lvds->reset_gpio = devm_gpiod_get_optional(lvds->dev, "reset",
+						     GPIOD_OUT_HIGH);
+	if (IS_ERR(lvds->reset_gpio)) {
+		ret = PTR_ERR(lvds->reset_gpio);
+		dev_err(lvds->dev, "failed to request %s GPIO: %d\n",
+			"reset", ret);
+		return ret;
+	}
+
+	np = of_parse_phandle(lvds->dev->of_node, "backlight", 0);
+	if (np) {
+		lvds->backlight = of_find_backlight_by_node(np);
+		of_node_put(np);
+
+		if (!lvds->backlight)
+			return -EPROBE_DEFER;
+	}
+
+	/*
+	 * TODO: Handle all power supplies specified in the DT node in a generic
+	 * way for panels that don't care about power supply ordering. LVDS
+	 * panels that require a specific power sequence will need a dedicated
+	 * driver.
+	 */
+
+	/* Register the panel. */
+	drm_panel_init(&lvds->panel);
+	lvds->panel.dev = lvds->dev;
+	lvds->panel.funcs = &panel_lvds_funcs;
+
+	ret = drm_panel_add(&lvds->panel);
+	if (ret < 0)
+		goto error;
+
+	dev_set_drvdata(lvds->dev, lvds);
+	return 0;
+
+error:
+	put_device(&lvds->backlight->dev);
+	return ret;
+}
+
+static int panel_lvds_remove(struct platform_device *pdev)
+{
+	struct panel_lvds *lvds = dev_get_drvdata(&pdev->dev);
+
+	drm_panel_detach(&lvds->panel);
+	drm_panel_remove(&lvds->panel);
+
+	panel_lvds_disable(&lvds->panel);
+
+	if (lvds->backlight)
+		put_device(&lvds->backlight->dev);
+
+	return 0;
+}
+
+static const struct of_device_id panel_lvds_of_table[] = {
+	{ .compatible = "panel-lvds", },
+	{ /* Sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, panel_lvds_of_table);
+
+static struct platform_driver panel_lvds_driver = {
+	.probe		= panel_lvds_probe,
+	.remove		= panel_lvds_remove,
+	.driver		= {
+		.name	= "panel-lvds",
+		.of_match_table = panel_lvds_of_table,
+	},
+};
+
+module_platform_driver(panel_lvds_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("LVDS Panel Driver");
+MODULE_LICENSE("GPL");

+ 6 - 4
drivers/gpu/drm/rcar-du/Kconfig

@@ -11,15 +11,17 @@ config DRM_RCAR_DU
 	  Choose this option if you have an R-Car chipset.
 	  If M is selected the module will be called rcar-du-drm.
 
-config DRM_RCAR_HDMI
-	bool "R-Car DU HDMI Encoder Support"
-	depends on DRM_RCAR_DU
+config DRM_RCAR_DW_HDMI
+	tristate "R-Car DU Gen3 HDMI Encoder Support"
+	depends on DRM && OF
+	select DRM_DW_HDMI
 	help
-	  Enable support for external HDMI encoders.
+	  Enable support for R-Car Gen3 internal HDMI encoder.
 
 config DRM_RCAR_LVDS
 	bool "R-Car DU LVDS Encoder Support"
 	depends on DRM_RCAR_DU
+	select DRM_PANEL
 	help
 	  Enable support for the R-Car Display Unit embedded LVDS encoders.
 

+ 2 - 4
drivers/gpu/drm/rcar-du/Makefile

@@ -4,13 +4,11 @@ rcar-du-drm-y := rcar_du_crtc.o \
 		 rcar_du_group.o \
 		 rcar_du_kms.o \
 		 rcar_du_lvdscon.o \
-		 rcar_du_plane.o \
-		 rcar_du_vgacon.o
-
-rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI)	+= rcar_du_hdmienc.o
+		 rcar_du_plane.o
 
 rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)	+= rcar_du_lvdsenc.o
 
 rcar-du-drm-$(CONFIG_DRM_RCAR_VSP)	+= rcar_du_vsp.o
 
 obj-$(CONFIG_DRM_RCAR_DU)		+= rcar-du-drm.o
+obj-$(CONFIG_DRM_RCAR_DW_HDMI)		+= rcar_dw_hdmi.o

+ 90 - 4
drivers/gpu/drm/rcar-du/rcar_du_crtc.c

@@ -106,9 +106,62 @@ static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
  * Hardware Setup
  */
 
+struct dpll_info {
+	unsigned int output;
+	unsigned int fdpll;
+	unsigned int n;
+	unsigned int m;
+};
+
+static void rcar_du_dpll_divider(struct rcar_du_crtc *rcrtc,
+				 struct dpll_info *dpll,
+				 unsigned long input,
+				 unsigned long target)
+{
+	unsigned long best_diff = (unsigned long)-1;
+	unsigned long diff;
+	unsigned int fdpll;
+	unsigned int m;
+	unsigned int n;
+
+	for (n = 39; n < 120; n++) {
+		for (m = 0; m < 4; m++) {
+			for (fdpll = 1; fdpll < 32; fdpll++) {
+				unsigned long output;
+
+				/* 1/2 (FRQSEL=1) for duty rate 50% */
+				output = input * (n + 1) / (m + 1)
+				       / (fdpll + 1) / 2;
+
+				if (output >= 400000000)
+					continue;
+
+				diff = abs((long)output - (long)target);
+				if (best_diff > diff) {
+					best_diff = diff;
+					dpll->n = n;
+					dpll->m = m;
+					dpll->fdpll = fdpll;
+					dpll->output = output;
+				}
+
+				if (diff == 0)
+					goto done;
+			}
+		}
+	}
+
+done:
+	dev_dbg(rcrtc->group->dev->dev,
+		"output:%u, fdpll:%u, n:%u, m:%u, diff:%lu\n",
+		 dpll->output, dpll->fdpll, dpll->n, dpll->m,
+		 best_diff);
+}
+
 static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
 {
 	const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode;
+	struct rcar_du_device *rcdu = rcrtc->group->dev;
 	unsigned long mode_clock = mode->clock * 1000;
 	unsigned long clk;
 	u32 value;
@@ -124,12 +177,18 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
 	escr = div | ESCR_DCLKSEL_CLKS;
 
 	if (rcrtc->extclock) {
+		struct dpll_info dpll = { 0 };
 		unsigned long extclk;
 		unsigned long extrate;
 		unsigned long rate;
 		u32 extdiv;
 
 		extclk = clk_get_rate(rcrtc->extclock);
+		if (rcdu->info->dpll_ch & (1 << rcrtc->index)) {
+			rcar_du_dpll_divider(rcrtc, &dpll, extclk, mode_clock);
+			extclk = dpll.output;
+		}
+
 		extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock);
 		extdiv = clamp(extdiv, 1U, 64U) - 1;
 
@@ -140,7 +199,27 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
 		    abs((long)rate - (long)mode_clock)) {
 			dev_dbg(rcrtc->group->dev->dev,
 				"crtc%u: using external clock\n", rcrtc->index);
-			escr = extdiv | ESCR_DCLKSEL_DCLKIN;
+
+			if (rcdu->info->dpll_ch & (1 << rcrtc->index)) {
+				u32 dpllcr = DPLLCR_CODE | DPLLCR_CLKE
+					   | DPLLCR_FDPLL(dpll.fdpll)
+					   | DPLLCR_N(dpll.n) | DPLLCR_M(dpll.m)
+					   | DPLLCR_STBY;
+
+				if (rcrtc->index == 1)
+					dpllcr |= DPLLCR_PLCS1
+					       |  DPLLCR_INCS_DOTCLKIN1;
+				else
+					dpllcr |= DPLLCR_PLCS0
+					       |  DPLLCR_INCS_DOTCLKIN0;
+
+				rcar_du_group_write(rcrtc->group, DPLLCR,
+						    dpllcr);
+
+				escr = ESCR_DCLKSEL_DCLKIN | 1;
+			} else {
+				escr = ESCR_DCLKSEL_DCLKIN | extdiv;
+			}
 		}
 	}
 
@@ -488,22 +567,29 @@ static void rcar_du_crtc_disable(struct drm_crtc *crtc)
 	rcar_du_crtc_stop(rcrtc);
 	rcar_du_crtc_put(rcrtc);
 
+	spin_lock_irq(&crtc->dev->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&crtc->dev->event_lock);
+
 	rcrtc->outputs = 0;
 }
 
 static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
 				      struct drm_crtc_state *old_crtc_state)
 {
-	struct drm_pending_vblank_event *event = crtc->state->event;
 	struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
 	struct drm_device *dev = rcrtc->crtc.dev;
 	unsigned long flags;
 
-	if (event) {
+	if (crtc->state->event) {
 		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
 
 		spin_lock_irqsave(&dev->event_lock, flags);
-		rcrtc->event = event;
+		rcrtc->event = crtc->state->event;
+		crtc->state->event = NULL;
 		spin_unlock_irqrestore(&dev->event_lock, flags);
 	}
 

+ 3 - 1
drivers/gpu/drm/rcar-du/rcar_du_crtc.h

@@ -1,7 +1,7 @@
 /*
  * rcar_du_crtc.h  --  R-Car Display Unit CRTCs
  *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2013-2015 Renesas Electronics Corporation
  *
  * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
  *
@@ -61,6 +61,8 @@ enum rcar_du_output {
 	RCAR_DU_OUTPUT_DPAD1,
 	RCAR_DU_OUTPUT_LVDS0,
 	RCAR_DU_OUTPUT_LVDS1,
+	RCAR_DU_OUTPUT_HDMI0,
+	RCAR_DU_OUTPUT_HDMI1,
 	RCAR_DU_OUTPUT_TCON,
 	RCAR_DU_OUTPUT_MAX,
 };

+ 12 - 20
drivers/gpu/drm/rcar-du/rcar_du_drv.c

@@ -44,12 +44,10 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
 		 */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
 		[RCAR_DU_OUTPUT_DPAD1] = {
 			.possible_crtcs = BIT(1) | BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 1,
 		},
 	},
@@ -68,17 +66,14 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
 		 */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(2) | BIT(1) | BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
 		[RCAR_DU_OUTPUT_LVDS0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_LVDS,
 			.port = 1,
 		},
 		[RCAR_DU_OUTPUT_LVDS1] = {
 			.possible_crtcs = BIT(2) | BIT(1),
-			.encoder_type = DRM_MODE_ENCODER_LVDS,
 			.port = 2,
 		},
 	},
@@ -97,12 +92,10 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = {
 		 */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(1) | BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
 		[RCAR_DU_OUTPUT_LVDS0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_LVDS,
 			.port = 1,
 		},
 	},
@@ -118,12 +111,10 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
 		/* R8A7792 has two RGB outputs. */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
 		[RCAR_DU_OUTPUT_DPAD1] = {
 			.possible_crtcs = BIT(1),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 1,
 		},
 	},
@@ -141,12 +132,10 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
 		 */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
 		[RCAR_DU_OUTPUT_DPAD1] = {
 			.possible_crtcs = BIT(1),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 1,
 		},
 	},
@@ -160,21 +149,28 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
 		  | RCAR_DU_FEATURE_VSP1_SOURCE,
 	.num_crtcs = 4,
 	.routes = {
-		/* R8A7795 has one RGB output, one LVDS output and two
-		 * (currently unsupported) HDMI outputs.
+		/* R8A7795 has one RGB output, two HDMI outputs and one
+		 * LVDS output.
 		 */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(3),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
+		[RCAR_DU_OUTPUT_HDMI0] = {
+			.possible_crtcs = BIT(1),
+			.port = 1,
+		},
+		[RCAR_DU_OUTPUT_HDMI1] = {
+			.possible_crtcs = BIT(2),
+			.port = 2,
+		},
 		[RCAR_DU_OUTPUT_LVDS0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_LVDS,
 			.port = 3,
 		},
 	},
 	.num_lvds = 1,
+	.dpll_ch =  BIT(1) | BIT(2),
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7796_info = {
@@ -189,12 +185,10 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
 		 */
 		[RCAR_DU_OUTPUT_DPAD0] = {
 			.possible_crtcs = BIT(2),
-			.encoder_type = DRM_MODE_ENCODER_NONE,
 			.port = 0,
 		},
 		[RCAR_DU_OUTPUT_LVDS0] = {
 			.possible_crtcs = BIT(0),
-			.encoder_type = DRM_MODE_ENCODER_LVDS,
 			.port = 2,
 		},
 	},
@@ -318,10 +312,8 @@ static int rcar_du_probe(struct platform_device *pdev)
 	if (rcdu == NULL)
 		return -ENOMEM;
 
-	init_waitqueue_head(&rcdu->commit.wait);
-
 	rcdu->dev = &pdev->dev;
-	rcdu->info = of_match_device(rcar_du_of_table, rcdu->dev)->data;
+	rcdu->info = of_device_get_match_data(rcdu->dev);
 
 	platform_set_drvdata(pdev, rcdu);
 

+ 1 - 7
drivers/gpu/drm/rcar-du/rcar_du_drv.h

@@ -38,7 +38,6 @@ struct rcar_du_lvdsenc;
 /*
  * struct rcar_du_output_routing - Output routing specification
  * @possible_crtcs: bitmask of possible CRTCs for the output
- * @encoder_type: DRM type of the internal encoder associated with the output
  * @port: device tree port number corresponding to this output route
  *
  * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data
@@ -47,7 +46,6 @@ struct rcar_du_lvdsenc;
  */
 struct rcar_du_output_routing {
 	unsigned int possible_crtcs;
-	unsigned int encoder_type;
 	unsigned int port;
 };
 
@@ -67,6 +65,7 @@ struct rcar_du_device_info {
 	unsigned int num_crtcs;
 	struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
 	unsigned int num_lvds;
+	unsigned int dpll_ch;
 };
 
 #define RCAR_DU_MAX_CRTCS		4
@@ -98,11 +97,6 @@ struct rcar_du_device {
 	unsigned int vspd1_sink;
 
 	struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
-
-	struct {
-		wait_queue_head_t wait;
-		u32 pending;
-	} commit;
 };
 
 static inline bool rcar_du_has(struct rcar_du_device *rcdu,

+ 119 - 68
drivers/gpu/drm/rcar-du/rcar_du_encoder.c

@@ -16,14 +16,13 @@
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
 
 #include "rcar_du_drv.h"
 #include "rcar_du_encoder.h"
-#include "rcar_du_hdmienc.h"
 #include "rcar_du_kms.h"
 #include "rcar_du_lvdscon.h"
 #include "rcar_du_lvdsenc.h"
-#include "rcar_du_vgacon.h"
 
 /* -----------------------------------------------------------------------------
  * Encoder
@@ -33,6 +32,11 @@ static void rcar_du_encoder_disable(struct drm_encoder *encoder)
 {
 	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
 
+	if (renc->connector && renc->connector->panel) {
+		drm_panel_disable(renc->connector->panel);
+		drm_panel_unprepare(renc->connector->panel);
+	}
+
 	if (renc->lvds)
 		rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false);
 }
@@ -43,6 +47,11 @@ static void rcar_du_encoder_enable(struct drm_encoder *encoder)
 
 	if (renc->lvds)
 		rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true);
+
+	if (renc->connector && renc->connector->panel) {
+		drm_panel_prepare(renc->connector->panel);
+		drm_panel_enable(renc->connector->panel);
+	}
 }
 
 static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
@@ -52,30 +61,36 @@ static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
 	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
 	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
 	const struct drm_display_mode *mode = &crtc_state->mode;
-	const struct drm_display_mode *panel_mode;
 	struct drm_connector *connector = conn_state->connector;
 	struct drm_device *dev = encoder->dev;
 
-	/* DAC encoders have currently no restriction on the mode. */
-	if (encoder->encoder_type == DRM_MODE_ENCODER_DAC)
-		return 0;
-
-	if (list_empty(&connector->modes)) {
-		dev_dbg(dev->dev, "encoder: empty modes list\n");
-		return -EINVAL;
+	/*
+	 * Only panel-related encoder types require validation here, everything
+	 * else is handled by the bridge drivers.
+	 */
+	if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
+		const struct drm_display_mode *panel_mode;
+
+		if (list_empty(&connector->modes)) {
+			dev_dbg(dev->dev, "encoder: empty modes list\n");
+			return -EINVAL;
+		}
+
+		panel_mode = list_first_entry(&connector->modes,
+					      struct drm_display_mode, head);
+
+		/* We're not allowed to modify the resolution. */
+		if (mode->hdisplay != panel_mode->hdisplay ||
+		    mode->vdisplay != panel_mode->vdisplay)
+			return -EINVAL;
+
+		/*
+		 * The flat panel mode is fixed, just copy it to the adjusted
+		 * mode.
+		 */
+		drm_mode_copy(adjusted_mode, panel_mode);
 	}
 
-	panel_mode = list_first_entry(&connector->modes,
-				      struct drm_display_mode, head);
-
-	/* We're not allowed to modify the resolution. */
-	if (mode->hdisplay != panel_mode->hdisplay ||
-	    mode->vdisplay != panel_mode->vdisplay)
-		return -EINVAL;
-
-	/* The flat panel mode is fixed, just copy it to the adjusted mode. */
-	drm_mode_copy(adjusted_mode, panel_mode);
-
 	if (renc->lvds)
 		rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
 
@@ -83,16 +98,54 @@ static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
 }
 
 static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
-				     struct drm_display_mode *mode,
-				     struct drm_display_mode *adjusted_mode)
+				     struct drm_crtc_state *crtc_state,
+				     struct drm_connector_state *conn_state)
 {
 	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
+	struct drm_display_info *info = &conn_state->connector->display_info;
+	enum rcar_lvds_mode mode;
+
+	rcar_du_crtc_route_output(crtc_state->crtc, renc->output);
+
+	if (!renc->lvds) {
+		/*
+		 * The DU driver creates connectors only for the outputs of the
+		 * internal LVDS encoders.
+		 */
+		renc->connector = NULL;
+		return;
+	}
+
+	renc->connector = to_rcar_connector(conn_state->connector);
+
+	if (!info->num_bus_formats || !info->bus_formats) {
+		dev_err(encoder->dev->dev, "no LVDS bus format reported\n");
+		return;
+	}
+
+	switch (info->bus_formats[0]) {
+	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+		mode = RCAR_LVDS_MODE_JEIDA;
+		break;
+	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+		mode = RCAR_LVDS_MODE_VESA;
+		break;
+	default:
+		dev_err(encoder->dev->dev,
+			"unsupported LVDS bus format 0x%04x\n",
+			info->bus_formats[0]);
+		return;
+	}
+
+	if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
+		mode |= RCAR_LVDS_MODE_MIRROR;
 
-	rcar_du_crtc_route_output(encoder->crtc, renc->output);
+	rcar_du_lvdsenc_set_mode(renc->lvds, mode);
 }
 
 static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
-	.mode_set = rcar_du_encoder_mode_set,
+	.atomic_mode_set = rcar_du_encoder_mode_set,
 	.disable = rcar_du_encoder_disable,
 	.enable = rcar_du_encoder_enable,
 	.atomic_check = rcar_du_encoder_atomic_check,
@@ -103,14 +156,13 @@ static const struct drm_encoder_funcs encoder_funcs = {
 };
 
 int rcar_du_encoder_init(struct rcar_du_device *rcdu,
-			 enum rcar_du_encoder_type type,
 			 enum rcar_du_output output,
 			 struct device_node *enc_node,
 			 struct device_node *con_node)
 {
 	struct rcar_du_encoder *renc;
 	struct drm_encoder *encoder;
-	unsigned int encoder_type;
+	struct drm_bridge *bridge = NULL;
 	int ret;
 
 	renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
@@ -133,52 +185,51 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
 		break;
 	}
 
-	switch (type) {
-	case RCAR_DU_ENCODER_VGA:
-		encoder_type = DRM_MODE_ENCODER_DAC;
-		break;
-	case RCAR_DU_ENCODER_LVDS:
-		encoder_type = DRM_MODE_ENCODER_LVDS;
-		break;
-	case RCAR_DU_ENCODER_HDMI:
-		encoder_type = DRM_MODE_ENCODER_TMDS;
-		break;
-	case RCAR_DU_ENCODER_NONE:
-	default:
-		/* No external encoder, use the internal encoder type. */
-		encoder_type = rcdu->info->routes[output].encoder_type;
-		break;
-	}
+	if (enc_node) {
+		dev_dbg(rcdu->dev, "initializing encoder %s for output %u\n",
+			of_node_full_name(enc_node), output);
 
-	if (type == RCAR_DU_ENCODER_HDMI) {
-		ret = rcar_du_hdmienc_init(rcdu, renc, enc_node);
-		if (ret < 0)
+		/* Locate the DRM bridge from the encoder DT node. */
+		bridge = of_drm_find_bridge(enc_node);
+		if (!bridge) {
+			ret = -EPROBE_DEFER;
 			goto done;
+		}
 	} else {
-		ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
-				       encoder_type, NULL);
-		if (ret < 0)
-			goto done;
-
-		drm_encoder_helper_add(encoder, &encoder_helper_funcs);
+		dev_dbg(rcdu->dev,
+			"initializing internal encoder for output %u\n",
+			output);
 	}
 
-	switch (encoder_type) {
-	case DRM_MODE_ENCODER_LVDS:
-		ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
-		break;
-
-	case DRM_MODE_ENCODER_DAC:
-		ret = rcar_du_vga_connector_init(rcdu, renc);
-		break;
-
-	case DRM_MODE_ENCODER_TMDS:
-		/* connector managed by the bridge driver */
-		break;
-
-	default:
-		ret = -EINVAL;
-		break;
+	ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
+			       DRM_MODE_ENCODER_NONE, NULL);
+	if (ret < 0)
+		goto done;
+
+	drm_encoder_helper_add(encoder, &encoder_helper_funcs);
+
+	if (bridge) {
+		/*
+		 * Attach the bridge to the encoder. The bridge will create the
+		 * connector.
+		 */
+		ret = drm_bridge_attach(encoder, bridge, NULL);
+		if (ret) {
+			drm_encoder_cleanup(encoder);
+			return ret;
+		}
+	} else {
+		/* There's no bridge, create the connector manually. */
+		switch (output) {
+		case RCAR_DU_OUTPUT_LVDS0:
+		case RCAR_DU_OUTPUT_LVDS1:
+			ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
+			break;
+
+		default:
+			ret = -EINVAL;
+			break;
+		}
 	}
 
 done:

+ 3 - 11
drivers/gpu/drm/rcar-du/rcar_du_encoder.h

@@ -17,22 +17,14 @@
 #include <drm/drm_crtc.h>
 #include <drm/drm_encoder.h>
 
+struct drm_panel;
 struct rcar_du_device;
-struct rcar_du_hdmienc;
 struct rcar_du_lvdsenc;
 
-enum rcar_du_encoder_type {
-	RCAR_DU_ENCODER_UNUSED = 0,
-	RCAR_DU_ENCODER_NONE,
-	RCAR_DU_ENCODER_VGA,
-	RCAR_DU_ENCODER_LVDS,
-	RCAR_DU_ENCODER_HDMI,
-};
-
 struct rcar_du_encoder {
 	struct drm_encoder base;
 	enum rcar_du_output output;
-	struct rcar_du_hdmienc *hdmi;
+	struct rcar_du_connector *connector;
 	struct rcar_du_lvdsenc *lvds;
 };
 
@@ -44,13 +36,13 @@ struct rcar_du_encoder {
 struct rcar_du_connector {
 	struct drm_connector connector;
 	struct rcar_du_encoder *encoder;
+	struct drm_panel *panel;
 };
 
 #define to_rcar_connector(c) \
 	container_of(c, struct rcar_du_connector, connector)
 
 int rcar_du_encoder_init(struct rcar_du_device *rcdu,
-			 enum rcar_du_encoder_type type,
 			 enum rcar_du_output output,
 			 struct device_node *enc_node,
 			 struct device_node *con_node);

+ 0 - 134
drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c

@@ -1,134 +0,0 @@
-/*
- * R-Car Display Unit HDMI Encoder
- *
- * Copyright (C) 2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/slab.h>
-
-#include <drm/drmP.h>
-#include <drm/drm_crtc.h>
-#include <drm/drm_crtc_helper.h>
-
-#include "rcar_du_drv.h"
-#include "rcar_du_encoder.h"
-#include "rcar_du_hdmienc.h"
-#include "rcar_du_lvdsenc.h"
-
-struct rcar_du_hdmienc {
-	struct rcar_du_encoder *renc;
-	bool enabled;
-};
-
-#define to_rcar_hdmienc(e)	(to_rcar_encoder(e)->hdmi)
-
-static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
-{
-	struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
-
-	if (hdmienc->renc->lvds)
-		rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
-				       false);
-
-	hdmienc->enabled = false;
-}
-
-static void rcar_du_hdmienc_enable(struct drm_encoder *encoder)
-{
-	struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
-
-	if (hdmienc->renc->lvds)
-		rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
-				       true);
-
-	hdmienc->enabled = true;
-}
-
-static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder,
-					struct drm_crtc_state *crtc_state,
-					struct drm_connector_state *conn_state)
-{
-	struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
-	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
-
-	if (hdmienc->renc->lvds)
-		rcar_du_lvdsenc_atomic_check(hdmienc->renc->lvds,
-					     adjusted_mode);
-
-	return 0;
-}
-
-
-static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder,
-				     struct drm_display_mode *mode,
-				     struct drm_display_mode *adjusted_mode)
-{
-	struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
-
-	rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output);
-}
-
-static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
-	.mode_set = rcar_du_hdmienc_mode_set,
-	.disable = rcar_du_hdmienc_disable,
-	.enable = rcar_du_hdmienc_enable,
-	.atomic_check = rcar_du_hdmienc_atomic_check,
-};
-
-static void rcar_du_hdmienc_cleanup(struct drm_encoder *encoder)
-{
-	struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
-
-	if (hdmienc->enabled)
-		rcar_du_hdmienc_disable(encoder);
-
-	drm_encoder_cleanup(encoder);
-}
-
-static const struct drm_encoder_funcs encoder_funcs = {
-	.destroy = rcar_du_hdmienc_cleanup,
-};
-
-int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
-			 struct rcar_du_encoder *renc, struct device_node *np)
-{
-	struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
-	struct drm_bridge *bridge;
-	struct rcar_du_hdmienc *hdmienc;
-	int ret;
-
-	hdmienc = devm_kzalloc(rcdu->dev, sizeof(*hdmienc), GFP_KERNEL);
-	if (hdmienc == NULL)
-		return -ENOMEM;
-
-	/* Locate the DRM bridge from the HDMI encoder DT node. */
-	bridge = of_drm_find_bridge(np);
-	if (!bridge)
-		return -EPROBE_DEFER;
-
-	ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
-			       DRM_MODE_ENCODER_TMDS, NULL);
-	if (ret < 0)
-		return ret;
-
-	drm_encoder_helper_add(encoder, &encoder_helper_funcs);
-
-	renc->hdmi = hdmienc;
-	hdmienc->renc = renc;
-
-	/* Link the bridge to the encoder. */
-	ret = drm_bridge_attach(encoder, bridge, NULL);
-	if (ret) {
-		drm_encoder_cleanup(encoder);
-		return ret;
-	}
-
-	return 0;
-}

+ 0 - 35
drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h

@@ -1,35 +0,0 @@
-/*
- * R-Car Display Unit HDMI Encoder
- *
- * Copyright (C) 2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __RCAR_DU_HDMIENC_H__
-#define __RCAR_DU_HDMIENC_H__
-
-#include <linux/module.h>
-
-struct device_node;
-struct rcar_du_device;
-struct rcar_du_encoder;
-
-#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
-int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
-			 struct rcar_du_encoder *renc, struct device_node *np);
-#else
-static inline int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
-				       struct rcar_du_encoder *renc,
-				       struct device_node *np)
-{
-	return -ENOSYS;
-}
-#endif
-
-#endif /* __RCAR_DU_HDMIENC_H__ */

+ 18 - 125
drivers/gpu/drm/rcar-du/rcar_du_kms.c

@@ -249,18 +249,9 @@ static int rcar_du_atomic_check(struct drm_device *dev,
 	return rcar_du_atomic_check_planes(dev, state);
 }
 
-struct rcar_du_commit {
-	struct work_struct work;
-	struct drm_device *dev;
-	struct drm_atomic_state *state;
-	u32 crtcs;
-};
-
-static void rcar_du_atomic_complete(struct rcar_du_commit *commit)
+static void rcar_du_atomic_commit_tail(struct drm_atomic_state *old_state)
 {
-	struct drm_device *dev = commit->dev;
-	struct rcar_du_device *rcdu = dev->dev_private;
-	struct drm_atomic_state *old_state = commit->state;
+	struct drm_device *dev = old_state->dev;
 
 	/* Apply the atomic update. */
 	drm_atomic_helper_commit_modeset_disables(dev, old_state);
@@ -268,114 +259,31 @@ static void rcar_du_atomic_complete(struct rcar_du_commit *commit)
 	drm_atomic_helper_commit_planes(dev, old_state,
 					DRM_PLANE_COMMIT_ACTIVE_ONLY);
 
+	drm_atomic_helper_commit_hw_done(old_state);
 	drm_atomic_helper_wait_for_vblanks(dev, old_state);
 
 	drm_atomic_helper_cleanup_planes(dev, old_state);
-
-	drm_atomic_state_put(old_state);
-
-	/* Complete the commit, wake up any waiter. */
-	spin_lock(&rcdu->commit.wait.lock);
-	rcdu->commit.pending &= ~commit->crtcs;
-	wake_up_all_locked(&rcdu->commit.wait);
-	spin_unlock(&rcdu->commit.wait.lock);
-
-	kfree(commit);
-}
-
-static void rcar_du_atomic_work(struct work_struct *work)
-{
-	struct rcar_du_commit *commit =
-		container_of(work, struct rcar_du_commit, work);
-
-	rcar_du_atomic_complete(commit);
-}
-
-static int rcar_du_atomic_commit(struct drm_device *dev,
-				 struct drm_atomic_state *state,
-				 bool nonblock)
-{
-	struct rcar_du_device *rcdu = dev->dev_private;
-	struct rcar_du_commit *commit;
-	struct drm_crtc *crtc;
-	struct drm_crtc_state *crtc_state;
-	unsigned int i;
-	int ret;
-
-	ret = drm_atomic_helper_prepare_planes(dev, state);
-	if (ret)
-		return ret;
-
-	/* Allocate the commit object. */
-	commit = kzalloc(sizeof(*commit), GFP_KERNEL);
-	if (commit == NULL) {
-		ret = -ENOMEM;
-		goto error;
-	}
-
-	INIT_WORK(&commit->work, rcar_du_atomic_work);
-	commit->dev = dev;
-	commit->state = state;
-
-	/* Wait until all affected CRTCs have completed previous commits and
-	 * mark them as pending.
-	 */
-	for_each_crtc_in_state(state, crtc, crtc_state, i)
-		commit->crtcs |= drm_crtc_mask(crtc);
-
-	spin_lock(&rcdu->commit.wait.lock);
-	ret = wait_event_interruptible_locked(rcdu->commit.wait,
-			!(rcdu->commit.pending & commit->crtcs));
-	if (ret == 0)
-		rcdu->commit.pending |= commit->crtcs;
-	spin_unlock(&rcdu->commit.wait.lock);
-
-	if (ret) {
-		kfree(commit);
-		goto error;
-	}
-
-	/* Swap the state, this is the point of no return. */
-	drm_atomic_helper_swap_state(state, true);
-
-	drm_atomic_state_get(state);
-	if (nonblock)
-		schedule_work(&commit->work);
-	else
-		rcar_du_atomic_complete(commit);
-
-	return 0;
-
-error:
-	drm_atomic_helper_cleanup_planes(dev, state);
-	return ret;
 }
 
 /* -----------------------------------------------------------------------------
  * Initialization
  */
 
+static const struct drm_mode_config_helper_funcs rcar_du_mode_config_helper = {
+	.atomic_commit_tail = rcar_du_atomic_commit_tail,
+};
+
 static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = {
 	.fb_create = rcar_du_fb_create,
 	.output_poll_changed = rcar_du_output_poll_changed,
 	.atomic_check = rcar_du_atomic_check,
-	.atomic_commit = rcar_du_atomic_commit,
+	.atomic_commit = drm_atomic_helper_commit,
 };
 
 static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
 				     enum rcar_du_output output,
 				     struct of_endpoint *ep)
 {
-	static const struct {
-		const char *compatible;
-		enum rcar_du_encoder_type type;
-	} encoders[] = {
-		{ "adi,adv7123", RCAR_DU_ENCODER_VGA },
-		{ "adi,adv7511w", RCAR_DU_ENCODER_HDMI },
-		{ "thine,thc63lvdm83d", RCAR_DU_ENCODER_LVDS },
-	};
-
-	enum rcar_du_encoder_type enc_type = RCAR_DU_ENCODER_NONE;
 	struct device_node *connector = NULL;
 	struct device_node *encoder = NULL;
 	struct device_node *ep_node = NULL;
@@ -394,6 +302,13 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
 		return -ENODEV;
 	}
 
+	if (!of_device_is_available(entity)) {
+		dev_dbg(rcdu->dev,
+			"connected entity %s is disabled, skipping\n",
+			entity->full_name);
+		return -ENODEV;
+	}
+
 	entity_ep_node = of_parse_phandle(ep->local_node, "remote-endpoint", 0);
 
 	for_each_endpoint_of_node(entity, ep_node) {
@@ -422,30 +337,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
 
 	of_node_put(entity_ep_node);
 
-	if (encoder) {
-		/*
-		 * If an encoder has been found, get its type based on its
-		 * compatible string.
-		 */
-		unsigned int i;
-
-		for (i = 0; i < ARRAY_SIZE(encoders); ++i) {
-			if (of_device_is_compatible(encoder,
-						    encoders[i].compatible)) {
-				enc_type = encoders[i].type;
-				break;
-			}
-		}
-
-		if (i == ARRAY_SIZE(encoders)) {
-			dev_warn(rcdu->dev,
-				 "unknown encoder type for %s, skipping\n",
-				 encoder->full_name);
-			of_node_put(encoder);
-			of_node_put(connector);
-			return -EINVAL;
-		}
-	} else {
+	if (!encoder) {
 		/*
 		 * If no encoder has been found the entity must be the
 		 * connector.
@@ -453,7 +345,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
 		connector = entity;
 	}
 
-	ret = rcar_du_encoder_init(rcdu, enc_type, output, encoder, connector);
+	ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
 	if (ret && ret != -EPROBE_DEFER)
 		dev_warn(rcdu->dev,
 			 "failed to initialize encoder %s on output %u (%d), skipping\n",
@@ -561,6 +453,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
 	dev->mode_config.max_width = 4095;
 	dev->mode_config.max_height = 2047;
 	dev->mode_config.funcs = &rcar_du_mode_config_funcs;
+	dev->mode_config.helper_private = &rcar_du_mode_config_helper;
 
 	rcdu->num_crtcs = rcdu->info->num_crtcs;
 

+ 24 - 44
drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c

@@ -15,6 +15,7 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
 
 #include <video/display_timing.h>
 #include <video/of_display_timing.h>
@@ -25,47 +26,30 @@
 #include "rcar_du_kms.h"
 #include "rcar_du_lvdscon.h"
 
-struct rcar_du_lvds_connector {
-	struct rcar_du_connector connector;
-
-	struct {
-		unsigned int width_mm;		/* Panel width in mm */
-		unsigned int height_mm;		/* Panel height in mm */
-		struct videomode mode;
-	} panel;
-};
-
-#define to_rcar_lvds_connector(c) \
-	container_of(c, struct rcar_du_lvds_connector, connector.connector)
-
 static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
 {
-	struct rcar_du_lvds_connector *lvdscon =
-		to_rcar_lvds_connector(connector);
-	struct drm_display_mode *mode;
-
-	mode = drm_mode_create(connector->dev);
-	if (mode == NULL)
-		return 0;
+	struct rcar_du_connector *rcon = to_rcar_connector(connector);
 
-	mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
-
-	drm_display_mode_from_videomode(&lvdscon->panel.mode, mode);
-
-	drm_mode_probed_add(connector, mode);
-
-	return 1;
+	return drm_panel_get_modes(rcon->panel);
 }
 
 static const struct drm_connector_helper_funcs connector_helper_funcs = {
 	.get_modes = rcar_du_lvds_connector_get_modes,
 };
 
+static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
+{
+	struct rcar_du_connector *rcon = to_rcar_connector(connector);
+
+	drm_panel_detach(rcon->panel);
+	drm_connector_cleanup(connector);
+}
+
 static const struct drm_connector_funcs connector_funcs = {
 	.dpms = drm_atomic_helper_connector_dpms,
 	.reset = drm_atomic_helper_connector_reset,
 	.fill_modes = drm_helper_probe_single_connector_modes,
-	.destroy = drm_connector_cleanup,
+	.destroy = rcar_du_lvds_connector_destroy,
 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
@@ -75,27 +59,19 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
 				const struct device_node *np)
 {
 	struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
-	struct rcar_du_lvds_connector *lvdscon;
+	struct rcar_du_connector *rcon;
 	struct drm_connector *connector;
-	struct display_timing timing;
 	int ret;
 
-	lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL);
-	if (lvdscon == NULL)
+	rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
+	if (rcon == NULL)
 		return -ENOMEM;
 
-	ret = of_get_display_timing(np, "panel-timing", &timing);
-	if (ret < 0)
-		return ret;
-
-	videomode_from_timing(&timing, &lvdscon->panel.mode);
+	connector = &rcon->connector;
 
-	of_property_read_u32(np, "width-mm", &lvdscon->panel.width_mm);
-	of_property_read_u32(np, "height-mm", &lvdscon->panel.height_mm);
-
-	connector = &lvdscon->connector.connector;
-	connector->display_info.width_mm = lvdscon->panel.width_mm;
-	connector->display_info.height_mm = lvdscon->panel.height_mm;
+	rcon->panel = of_drm_find_panel(np);
+	if (!rcon->panel)
+		return -EPROBE_DEFER;
 
 	ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
 				 DRM_MODE_CONNECTOR_LVDS);
@@ -112,7 +88,11 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
 	if (ret < 0)
 		return ret;
 
-	lvdscon->connector.encoder = renc;
+	ret = drm_panel_attach(rcon->panel, connector);
+	if (ret < 0)
+		return ret;
+
+	rcon->encoder = renc;
 
 	return 0;
 }

+ 9 - 2
drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c

@@ -31,6 +31,7 @@ struct rcar_du_lvdsenc {
 	bool enabled;
 
 	enum rcar_lvds_input input;
+	enum rcar_lvds_mode mode;
 };
 
 static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
@@ -61,7 +62,7 @@ static void rcar_du_lvdsenc_start_gen2(struct rcar_du_lvdsenc *lvds,
 	/* Select the input, hardcode mode 0, enable LVDS operation and turn
 	 * bias circuitry on.
 	 */
-	lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN;
+	lvdcr0 = (lvds->mode << LVDCR0_LVMD_SHIFT) | LVDCR0_BEN | LVDCR0_LVEN;
 	if (rcrtc->index == 2)
 		lvdcr0 |= LVDCR0_DUSEL;
 	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
@@ -114,7 +115,7 @@ static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds,
 	 * Turn the PLL on, set it to LVDS normal mode, wait for the startup
 	 * delay and turn the output on.
 	 */
-	lvdcr0 = LVDCR0_PLLON;
+	lvdcr0 = (lvds->mode << LVDCR0_LVMD_SHIFT) | LVDCR0_PLLON;
 	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
 
 	lvdcr0 |= LVDCR0_PWD;
@@ -211,6 +212,12 @@ void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
 		mode->clock = clamp(mode->clock, 25175, 148500);
 }
 
+void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
+			      enum rcar_lvds_mode mode)
+{
+	lvds->mode = mode;
+}
+
 static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
 					 struct platform_device *pdev)
 {

+ 13 - 0
drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h

@@ -26,8 +26,17 @@ enum rcar_lvds_input {
 	RCAR_LVDS_INPUT_DU2,
 };
 
+/* Keep in sync with the LVDCR0.LVMD hardware register values. */
+enum rcar_lvds_mode {
+	RCAR_LVDS_MODE_JEIDA = 0,
+	RCAR_LVDS_MODE_MIRROR = 1,
+	RCAR_LVDS_MODE_VESA = 4,
+};
+
 #if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
 int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu);
+void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
+			      enum rcar_lvds_mode mode);
 int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
 			   struct drm_crtc *crtc, bool enable);
 void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
@@ -37,6 +46,10 @@ static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
 {
 	return 0;
 }
+static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
+					    enum rcar_lvds_mode mode)
+{
+}
 static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
 					 struct drm_crtc *crtc, bool enable)
 {

+ 23 - 0
drivers/gpu/drm/rcar-du/rcar_du_regs.h

@@ -277,6 +277,29 @@
 #define DEFR10_TSEL_H3_TCON1	(0 << 1) /* DEFR102 register only (DU2/DU3) */
 #define DEFR10_DEFE10		(1 << 0)
 
+#define DPLLCR			0x20044
+#define DPLLCR_CODE		(0x95 << 24)
+#define DPLLCR_PLCS1		(1 << 23)
+/*
+ * PLCS0 is bit 21, but H3 ES1.x requires bit 20 to be set as well. As bit 20
+ * isn't implemented by other SoC in the Gen3 family it can safely be set
+ * unconditionally.
+ */
+#define DPLLCR_PLCS0		(3 << 20)
+#define DPLLCR_CLKE		(1 << 18)
+#define DPLLCR_FDPLL(n)		((n) << 12)
+#define DPLLCR_N(n)		((n) << 5)
+#define DPLLCR_M(n)		((n) << 3)
+#define DPLLCR_STBY		(1 << 2)
+#define DPLLCR_INCS_DOTCLKIN0	(0 << 0)
+#define DPLLCR_INCS_DOTCLKIN1	(1 << 1)
+
+#define DPLLC2R			0x20048
+#define DPLLC2R_CODE		(0x95 << 24)
+#define DPLLC2R_SELC		(1 << 12)
+#define DPLLC2R_M(n)		((n) << 8)
+#define DPLLC2R_FDPLL(n)	((n) << 0)
+
 /* -----------------------------------------------------------------------------
  * Display Timing Generation Registers
  */

+ 0 - 82
drivers/gpu/drm/rcar-du/rcar_du_vgacon.c

@@ -1,82 +0,0 @@
-/*
- * rcar_du_vgacon.c  --  R-Car Display Unit VGA Connector
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 "rcar_du_drv.h"
-#include "rcar_du_encoder.h"
-#include "rcar_du_kms.h"
-#include "rcar_du_vgacon.h"
-
-static int rcar_du_vga_connector_get_modes(struct drm_connector *connector)
-{
-	return 0;
-}
-
-static const struct drm_connector_helper_funcs connector_helper_funcs = {
-	.get_modes = rcar_du_vga_connector_get_modes,
-};
-
-static enum drm_connector_status
-rcar_du_vga_connector_detect(struct drm_connector *connector, bool force)
-{
-	return connector_status_connected;
-}
-
-static const struct drm_connector_funcs connector_funcs = {
-	.dpms = drm_atomic_helper_connector_dpms,
-	.reset = drm_atomic_helper_connector_reset,
-	.detect = rcar_du_vga_connector_detect,
-	.fill_modes = drm_helper_probe_single_connector_modes,
-	.destroy = drm_connector_cleanup,
-	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-int rcar_du_vga_connector_init(struct rcar_du_device *rcdu,
-			       struct rcar_du_encoder *renc)
-{
-	struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
-	struct rcar_du_connector *rcon;
-	struct drm_connector *connector;
-	int ret;
-
-	rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
-	if (rcon == NULL)
-		return -ENOMEM;
-
-	connector = &rcon->connector;
-	connector->display_info.width_mm = 0;
-	connector->display_info.height_mm = 0;
-	connector->interlace_allowed = true;
-
-	ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
-				 DRM_MODE_CONNECTOR_VGA);
-	if (ret < 0)
-		return ret;
-
-	drm_connector_helper_add(connector, &connector_helper_funcs);
-
-	connector->dpms = DRM_MODE_DPMS_OFF;
-	drm_object_property_set_value(&connector->base,
-		rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
-
-	ret = drm_mode_connector_attach_encoder(connector, encoder);
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}

+ 0 - 23
drivers/gpu/drm/rcar-du/rcar_du_vgacon.h

@@ -1,23 +0,0 @@
-/*
- * rcar_du_vgacon.h  --  R-Car Display Unit VGA Connector
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __RCAR_DU_VGACON_H__
-#define __RCAR_DU_VGACON_H__
-
-struct rcar_du_device;
-struct rcar_du_encoder;
-
-int rcar_du_vga_connector_init(struct rcar_du_device *rcdu,
-			       struct rcar_du_encoder *renc);
-
-#endif /* __RCAR_DU_VGACON_H__ */

+ 1 - 1
drivers/gpu/drm/rcar-du/rcar_du_vsp.h

@@ -68,7 +68,7 @@ void rcar_du_vsp_disable(struct rcar_du_crtc *crtc);
 void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc);
 void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc);
 #else
-static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp) { return 0; };
+static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp) { return -ENXIO; };
 static inline void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) { };
 static inline void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) { };
 static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { };

+ 100 - 0
drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c

@@ -0,0 +1,100 @@
+/*
+ * R-Car Gen3 HDMI PHY
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/module.h>
+#include <linux/platform_device.h>
+
+#include <drm/bridge/dw_hdmi.h>
+
+#define RCAR_HDMI_PHY_OPMODE_PLLCFG	0x06	/* Mode of operation and PLL dividers */
+#define RCAR_HDMI_PHY_PLLCURRGMPCTRL	0x10	/* PLL current and Gmp (conductance) */
+#define RCAR_HDMI_PHY_PLLDIVCTRL	0x11	/* PLL dividers */
+
+struct rcar_hdmi_phy_params {
+	unsigned long mpixelclock;
+	u16 opmode_div;	/* Mode of operation and PLL dividers */
+	u16 curr_gmp;	/* PLL current and Gmp (conductance) */
+	u16 div;	/* PLL dividers */
+};
+
+static const struct rcar_hdmi_phy_params rcar_hdmi_phy_params[] = {
+	{ 35500000,  0x0003, 0x0344, 0x0328 },
+	{ 44900000,  0x0003, 0x0285, 0x0128 },
+	{ 71000000,  0x0002, 0x1184, 0x0314 },
+	{ 90000000,  0x0002, 0x1144, 0x0114 },
+	{ 140250000, 0x0001, 0x20c4, 0x030a },
+	{ 182750000, 0x0001, 0x2084, 0x010a },
+	{ 281250000, 0x0000, 0x0084, 0x0305 },
+	{ 297000000, 0x0000, 0x0084, 0x0105 },
+	{ ~0UL,      0x0000, 0x0000, 0x0000 },
+};
+
+static int rcar_hdmi_phy_configure(struct dw_hdmi *hdmi,
+				   const struct dw_hdmi_plat_data *pdata,
+				   unsigned long mpixelclock)
+{
+	const struct rcar_hdmi_phy_params *params = rcar_hdmi_phy_params;
+
+	for (; params && params->mpixelclock != ~0UL; ++params) {
+		if (mpixelclock <= params->mpixelclock)
+			break;
+	}
+
+	if (params->mpixelclock == ~0UL)
+		return -EINVAL;
+
+	dw_hdmi_phy_i2c_write(hdmi, params->opmode_div,
+			      RCAR_HDMI_PHY_OPMODE_PLLCFG);
+	dw_hdmi_phy_i2c_write(hdmi, params->curr_gmp,
+			      RCAR_HDMI_PHY_PLLCURRGMPCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->div, RCAR_HDMI_PHY_PLLDIVCTRL);
+
+	return 0;
+}
+
+static const struct dw_hdmi_plat_data rcar_dw_hdmi_plat_data = {
+	.configure_phy	= rcar_hdmi_phy_configure,
+};
+
+static int rcar_dw_hdmi_probe(struct platform_device *pdev)
+{
+	return dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data);
+}
+
+static int rcar_dw_hdmi_remove(struct platform_device *pdev)
+{
+	dw_hdmi_remove(pdev);
+
+	return 0;
+}
+
+static const struct of_device_id rcar_dw_hdmi_of_table[] = {
+	{ .compatible = "renesas,rcar-gen3-hdmi" },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rcar_dw_hdmi_of_table);
+
+static struct platform_driver rcar_dw_hdmi_platform_driver = {
+	.probe		= rcar_dw_hdmi_probe,
+	.remove		= rcar_dw_hdmi_remove,
+	.driver		= {
+		.name	= "rcar-dw-hdmi",
+		.of_match_table = rcar_dw_hdmi_of_table,
+	},
+};
+
+module_platform_driver(rcar_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R-Car Gen3 HDMI Encoder Driver");
+MODULE_LICENSE("GPL");

+ 4 - 0
include/drm/drm_connector.h

@@ -224,6 +224,10 @@ struct drm_display_info {
 #define DRM_BUS_FLAG_PIXDATA_POSEDGE	(1<<2)
 /* drive data on neg. edge */
 #define DRM_BUS_FLAG_PIXDATA_NEGEDGE	(1<<3)
+/* data is transmitted MSB to LSB on the bus */
+#define DRM_BUS_FLAG_DATA_MSB_TO_LSB	(1<<4)
+/* data is transmitted LSB to MSB on the bus */
+#define DRM_BUS_FLAG_DATA_LSB_TO_MSB	(1<<5)
 
 	/**
 	 * @bus_flags: Additional information (like pixel signal polarity) for