[PATCH v6 4/4] drm: rcar-du: Convert LVDS encoder code to bridge driver

Niklas Söderlund niklas.soderlund at ragnatech.se
Thu Feb 22 14:49:18 UTC 2018


Hi Laurent,

Thanks for your patch.

On 2018-02-22 15:13:36 +0200, Laurent Pinchart wrote:
> The LVDS encoders used to be described in DT as part of the DU. They now
> have their own DT node, linked to the DU using the OF graph bindings.
> This allows moving internal LVDS encoder support to a separate driver
> modelled as a DRM bridge. Backward compatibility is retained as legacy
> DT is patched live to move to the new bindings.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas at ideasonboard.com>

I'm not so strong when it comes to DRM, but I have done my best to grasp 
this patch. Looking at datasheets and what the code looked before feel 
free to add.

Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas at ragnatech.se>

> ---
> Changes since v1:
> 
> - Update the SPDX headers to use GPL-2.0 instead of GPL-2.0-only
> - Update to the <soc>-lvds compatible string format
> ---
>  drivers/gpu/drm/rcar-du/Kconfig           |   4 +-
>  drivers/gpu/drm/rcar-du/Makefile          |   3 +-
>  drivers/gpu/drm/rcar-du/rcar_du_drv.c     |  21 +-
>  drivers/gpu/drm/rcar-du/rcar_du_drv.h     |   5 -
>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 175 +---------
>  drivers/gpu/drm/rcar-du/rcar_du_encoder.h |  12 -
>  drivers/gpu/drm/rcar-du/rcar_du_kms.c     |  14 +-
>  drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c |  93 ------
>  drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h |  24 --
>  drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c | 238 --------------
>  drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h |  64 ----
>  drivers/gpu/drm/rcar-du/rcar_lvds.c       | 524 ++++++++++++++++++++++++++++++
>  12 files changed, 561 insertions(+), 616 deletions(-)
>  delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
>  delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
>  delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
>  delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
>  create mode 100644 drivers/gpu/drm/rcar-du/rcar_lvds.c
> 
> diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig
> index 3f83352a7313..edde8d4b87a3 100644
> --- a/drivers/gpu/drm/rcar-du/Kconfig
> +++ b/drivers/gpu/drm/rcar-du/Kconfig
> @@ -19,8 +19,8 @@ config DRM_RCAR_DW_HDMI
>  	  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
> +	tristate "R-Car DU LVDS Encoder Support"
> +	depends on DRM && DRM_BRIDGE && OF
>  	select DRM_PANEL
>  	select OF_FLATTREE
>  	select OF_OVERLAY
> diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
> index 86b337b4be5d..3e58ed93d5b1 100644
> --- a/drivers/gpu/drm/rcar-du/Makefile
> +++ b/drivers/gpu/drm/rcar-du/Makefile
> @@ -4,10 +4,8 @@ rcar-du-drm-y := rcar_du_crtc.o \
>  		 rcar_du_encoder.o \
>  		 rcar_du_group.o \
>  		 rcar_du_kms.o \
> -		 rcar_du_lvdscon.o \
>  		 rcar_du_plane.o
>  
> -rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)	+= rcar_du_lvdsenc.o
>  rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)	+= rcar_du_of.o \
>  					   rcar_du_of_lvds_r8a7790.dtb.o \
>  					   rcar_du_of_lvds_r8a7791.dtb.o \
> @@ -18,3 +16,4 @@ 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
> +obj-$(CONFIG_DRM_RCAR_LVDS)		+= rcar_lvds.o
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
> index 6e02c762a557..06a3fbdd728a 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
> @@ -29,6 +29,7 @@
>  
>  #include "rcar_du_drv.h"
>  #include "rcar_du_kms.h"
> +#include "rcar_du_of.h"
>  #include "rcar_du_regs.h"
>  
>  /* -----------------------------------------------------------------------------
> @@ -74,7 +75,6 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
>  			.port = 1,
>  		},
>  	},
> -	.num_lvds = 0,
>  };
>  
>  static const struct rcar_du_device_info rcar_du_r8a7779_info = {
> @@ -95,14 +95,13 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
>  			.port = 1,
>  		},
>  	},
> -	.num_lvds = 0,
>  };
>  
>  static const struct rcar_du_device_info rcar_du_r8a7790_info = {
>  	.gen = 2,
>  	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
>  		  | RCAR_DU_FEATURE_EXT_CTRL_REGS,
> -	.quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES,
> +	.quirks = RCAR_DU_QUIRK_ALIGN_128B,
>  	.num_crtcs = 3,
>  	.routes = {
>  		/*
> @@ -164,7 +163,6 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
>  			.port = 1,
>  		},
>  	},
> -	.num_lvds = 0,
>  };
>  
>  static const struct rcar_du_device_info rcar_du_r8a7794_info = {
> @@ -186,7 +184,6 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
>  			.port = 1,
>  		},
>  	},
> -	.num_lvds = 0,
>  };
>  
>  static const struct rcar_du_device_info rcar_du_r8a7795_info = {
> @@ -434,7 +431,19 @@ static struct platform_driver rcar_du_platform_driver = {
>  	},
>  };
>  
> -module_platform_driver(rcar_du_platform_driver);
> +static int __init rcar_du_init(void)
> +{
> +	rcar_du_of_init(rcar_du_of_table);
> +
> +	return platform_driver_register(&rcar_du_platform_driver);
> +}
> +module_init(rcar_du_init);
> +
> +static void __exit rcar_du_exit(void)
> +{
> +	platform_driver_unregister(&rcar_du_platform_driver);
> +}
> +module_exit(rcar_du_exit);
>  
>  MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart at ideasonboard.com>");
>  MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
> index f400fde65a0c..5c7ec15818c7 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
> @@ -26,14 +26,12 @@ struct device;
>  struct drm_device;
>  struct drm_fbdev_cma;
>  struct rcar_du_device;
> -struct rcar_du_lvdsenc;
>  
>  #define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK	(1 << 0)	/* Per-CRTC IRQ and clock */
>  #define RCAR_DU_FEATURE_EXT_CTRL_REGS	(1 << 1)	/* Has extended control registers */
>  #define RCAR_DU_FEATURE_VSP1_SOURCE	(1 << 2)	/* Has inputs from VSP1 */
>  
>  #define RCAR_DU_QUIRK_ALIGN_128B	(1 << 0)	/* Align pitches to 128 bytes */
> -#define RCAR_DU_QUIRK_LVDS_LANES	(1 << 1)	/* LVDS lanes 1 and 3 inverted */
>  
>  /*
>   * struct rcar_du_output_routing - Output routing specification
> @@ -70,7 +68,6 @@ struct rcar_du_device_info {
>  
>  #define RCAR_DU_MAX_CRTCS		4
>  #define RCAR_DU_MAX_GROUPS		DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
> -#define RCAR_DU_MAX_LVDS		2
>  #define RCAR_DU_MAX_VSPS		4
>  
>  struct rcar_du_device {
> @@ -96,8 +93,6 @@ struct rcar_du_device {
>  
>  	unsigned int dpad0_source;
>  	unsigned int vspd1_sink;
> -
> -	struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
>  };
>  
>  static inline bool rcar_du_has(struct rcar_du_device *rcdu,
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> index ba8d2804c1d1..f9c933d3bae6 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> @@ -21,134 +21,22 @@
>  #include "rcar_du_drv.h"
>  #include "rcar_du_encoder.h"
>  #include "rcar_du_kms.h"
> -#include "rcar_du_lvdscon.h"
> -#include "rcar_du_lvdsenc.h"
>  
>  /* -----------------------------------------------------------------------------
>   * Encoder
>   */
>  
> -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);
> -}
> -
> -static void rcar_du_encoder_enable(struct drm_encoder *encoder)
> -{
> -	struct rcar_du_encoder *renc = to_rcar_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,
> -					struct drm_crtc_state *crtc_state,
> -					struct drm_connector_state *conn_state)
> -{
> -	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;
> -	struct drm_connector *connector = conn_state->connector;
> -	struct drm_device *dev = encoder->dev;
> -
> -	/*
> -	 * 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);
> -	}
> -
> -	if (renc->lvds)
> -		rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
> -
> -	return 0;
> -}
> -
>  static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
>  				     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_lvdsenc_set_mode(renc->lvds, mode);
>  }
>  
>  static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
>  	.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,
>  };
>  
>  static const struct drm_encoder_funcs encoder_funcs = {
> @@ -172,33 +60,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
>  	renc->output = output;
>  	encoder = rcar_encoder_to_drm_encoder(renc);
>  
> -	switch (output) {
> -	case RCAR_DU_OUTPUT_LVDS0:
> -		renc->lvds = rcdu->lvds[0];
> -		break;
> +	dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
> +		enc_node, output);
>  
> -	case RCAR_DU_OUTPUT_LVDS1:
> -		renc->lvds = rcdu->lvds[1];
> -		break;
> -
> -	default:
> -		break;
> -	}
> -
> -	if (enc_node) {
> -		dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
> -			enc_node, output);
> -
> -		/* Locate the DRM bridge from the encoder DT node. */
> -		bridge = of_drm_find_bridge(enc_node);
> -		if (!bridge) {
> -			ret = -EPROBE_DEFER;
> -			goto done;
> -		}
> -	} else {
> -		dev_dbg(rcdu->dev,
> -			"initializing internal encoder for output %u\n",
> -			output);
> +	/* Locate the DRM bridge from the encoder DT node. */
> +	bridge = of_drm_find_bridge(enc_node);
> +	if (!bridge) {
> +		ret = -EPROBE_DEFER;
> +		goto done;
>  	}
>  
>  	ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
> @@ -208,28 +77,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
>  
>  	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;
> -		}
> +	/*
> +	 * 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;
>  	}
>  
>  done:
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
> index 5422fa4df272..2d2abcacd169 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
> @@ -19,13 +19,10 @@
>  
>  struct drm_panel;
>  struct rcar_du_device;
> -struct rcar_du_lvdsenc;
>  
>  struct rcar_du_encoder {
>  	struct drm_encoder base;
>  	enum rcar_du_output output;
> -	struct rcar_du_connector *connector;
> -	struct rcar_du_lvdsenc *lvds;
>  };
>  
>  #define to_rcar_encoder(e) \
> @@ -33,15 +30,6 @@ struct rcar_du_encoder {
>  
>  #define rcar_encoder_to_drm_encoder(e)	(&(e)->base)
>  
> -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_output output,
>  			 struct device_node *enc_node,
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
> index 566d1a948c8f..0329b354bfa0 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
> @@ -27,7 +27,6 @@
>  #include "rcar_du_drv.h"
>  #include "rcar_du_encoder.h"
>  #include "rcar_du_kms.h"
> -#include "rcar_du_lvdsenc.h"
>  #include "rcar_du_regs.h"
>  #include "rcar_du_vsp.h"
>  
> @@ -341,11 +340,10 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
>  	of_node_put(entity_ep_node);
>  
>  	if (!encoder) {
> -		/*
> -		 * If no encoder has been found the entity must be the
> -		 * connector.
> -		 */
> -		connector = entity;
> +		dev_warn(rcdu->dev,
> +			 "no encoder found for endpoint %pOF, skipping\n",
> +			 ep->local_node);
> +		return -ENODEV;
>  	}
>  
>  	ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
> @@ -595,10 +593,6 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
>  	}
>  
>  	/* Initialize the encoders. */
> -	ret = rcar_du_lvdsenc_init(rcdu);
> -	if (ret < 0)
> -		return ret;
> -
>  	ret = rcar_du_encoders_init(rcdu);
>  	if (ret < 0)
>  		return ret;
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
> deleted file mode 100644
> index e96f2df0c305..000000000000
> --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
> +++ /dev/null
> @@ -1,93 +0,0 @@
> -/*
> - * rcar_du_lvdscon.c  --  R-Car Display Unit LVDS Connector
> - *
> - * Copyright (C) 2013-2014 Renesas Electronics Corporation
> - *
> - * Contact: Laurent Pinchart (laurent.pinchart at 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 <drm/drm_panel.h>
> -
> -#include <video/display_timing.h>
> -#include <video/of_display_timing.h>
> -#include <video/videomode.h>
> -
> -#include "rcar_du_drv.h"
> -#include "rcar_du_encoder.h"
> -#include "rcar_du_kms.h"
> -#include "rcar_du_lvdscon.h"
> -
> -static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
> -{
> -	struct rcar_du_connector *rcon = to_rcar_connector(connector);
> -
> -	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 = {
> -	.reset = drm_atomic_helper_connector_reset,
> -	.fill_modes = drm_helper_probe_single_connector_modes,
> -	.destroy = rcar_du_lvds_connector_destroy,
> -	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> -	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> -};
> -
> -int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
> -				struct rcar_du_encoder *renc,
> -				const struct device_node *np)
> -{
> -	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;
> -
> -	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);
> -	if (ret < 0)
> -		return ret;
> -
> -	drm_connector_helper_add(connector, &connector_helper_funcs);
> -
> -	ret = drm_mode_connector_attach_encoder(connector, encoder);
> -	if (ret < 0)
> -		return ret;
> -
> -	ret = drm_panel_attach(rcon->panel, connector);
> -	if (ret < 0)
> -		return ret;
> -
> -	rcon->encoder = renc;
> -
> -	return 0;
> -}
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
> deleted file mode 100644
> index 639071dd235c..000000000000
> --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
> +++ /dev/null
> @@ -1,24 +0,0 @@
> -/*
> - * rcar_du_lvdscon.h  --  R-Car Display Unit LVDS Connector
> - *
> - * Copyright (C) 2013-2014 Renesas Electronics Corporation
> - *
> - * Contact: Laurent Pinchart (laurent.pinchart at 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_LVDSCON_H__
> -#define __RCAR_DU_LVDSCON_H__
> -
> -struct rcar_du_device;
> -struct rcar_du_encoder;
> -
> -int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
> -				struct rcar_du_encoder *renc,
> -				const struct device_node *np);
> -
> -#endif /* __RCAR_DU_LVDSCON_H__ */
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
> deleted file mode 100644
> index 4defa8123eb2..000000000000
> --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
> +++ /dev/null
> @@ -1,238 +0,0 @@
> -/*
> - * rcar_du_lvdsenc.c  --  R-Car Display Unit LVDS Encoder
> - *
> - * Copyright (C) 2013-2014 Renesas Electronics Corporation
> - *
> - * Contact: Laurent Pinchart (laurent.pinchart at 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/clk.h>
> -#include <linux/delay.h>
> -#include <linux/io.h>
> -#include <linux/platform_device.h>
> -#include <linux/slab.h>
> -
> -#include "rcar_du_drv.h"
> -#include "rcar_du_encoder.h"
> -#include "rcar_du_lvdsenc.h"
> -#include "rcar_lvds_regs.h"
> -
> -struct rcar_du_lvdsenc {
> -	struct rcar_du_device *dev;
> -
> -	unsigned int index;
> -	void __iomem *mmio;
> -	struct clk *clock;
> -	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)
> -{
> -	iowrite32(data, lvds->mmio + reg);
> -}
> -
> -static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
> -{
> -	if (freq < 39000)
> -		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
> -	else if (freq < 61000)
> -		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
> -	else if (freq < 121000)
> -		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
> -	else
> -		return LVDPLLCR_PLLDLYCNT_150M;
> -}
> -
> -static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
> -{
> -	if (freq < 42000)
> -		return LVDPLLCR_PLLDIVCNT_42M;
> -	else if (freq < 85000)
> -		return LVDPLLCR_PLLDIVCNT_85M;
> -	else if (freq < 128000)
> -		return LVDPLLCR_PLLDIVCNT_128M;
> -	else
> -		return LVDPLLCR_PLLDIVCNT_148M;
> -}
> -
> -static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
> -				 struct rcar_du_crtc *rcrtc)
> -{
> -	const struct drm_display_mode *mode = &rcrtc->crtc.mode;
> -	u32 lvdpllcr;
> -	u32 lvdhcr;
> -	u32 lvdcr0;
> -	int ret;
> -
> -	if (lvds->enabled)
> -		return 0;
> -
> -	ret = clk_prepare_enable(lvds->clock);
> -	if (ret < 0)
> -		return ret;
> -
> -	/*
> -	 * Hardcode the channels and control signals routing for now.
> -	 *
> -	 * HSYNC -> CTRL0
> -	 * VSYNC -> CTRL1
> -	 * DISP  -> CTRL2
> -	 * 0     -> CTRL3
> -	 */
> -	rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
> -			LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
> -			LVDCTRCR_CTR0SEL_HSYNC);
> -
> -	if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES))
> -		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
> -		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
> -	else
> -		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
> -		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
> -
> -	rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
> -
> -	/* PLL clock configuration. */
> -	if (lvds->dev->info->gen < 3)
> -		lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
> -	else
> -		lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
> -	rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
> -
> -	/* Set the LVDS mode and select the input. */
> -	lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
> -	if (rcrtc->index == 2)
> -		lvdcr0 |= LVDCR0_DUSEL;
> -	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> -
> -	/* Turn all the channels on. */
> -	rcar_lvds_write(lvds, LVDCR1,
> -			LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
> -			LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
> -
> -	if (lvds->dev->info->gen < 3) {
> -		/* Enable LVDS operation and turn the bias circuitry on. */
> -		lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
> -		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> -	}
> -
> -	/* Turn the PLL on. */
> -	lvdcr0 |= LVDCR0_PLLON;
> -	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> -
> -	if (lvds->dev->info->gen > 2) {
> -		/* Set LVDS normal mode. */
> -		lvdcr0 |= LVDCR0_PWD;
> -		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> -	}
> -
> -	/* Wait for the startup delay. */
> -	usleep_range(100, 150);
> -
> -	/* Turn the output on. */
> -	lvdcr0 |= LVDCR0_LVRES;
> -	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> -
> -	lvds->enabled = true;
> -
> -	return 0;
> -}
> -
> -static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
> -{
> -	if (!lvds->enabled)
> -		return;
> -
> -	rcar_lvds_write(lvds, LVDCR0, 0);
> -	rcar_lvds_write(lvds, LVDCR1, 0);
> -
> -	clk_disable_unprepare(lvds->clock);
> -
> -	lvds->enabled = false;
> -}
> -
> -int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
> -			   bool enable)
> -{
> -	if (!enable) {
> -		rcar_du_lvdsenc_stop(lvds);
> -		return 0;
> -	} else if (crtc) {
> -		struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
> -		return rcar_du_lvdsenc_start(lvds, rcrtc);
> -	} else
> -		return -EINVAL;
> -}
> -
> -void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
> -				  struct drm_display_mode *mode)
> -{
> -	/*
> -	 * The internal LVDS encoder has a restricted clock frequency operating
> -	 * range (31MHz to 148.5MHz). Clamp the clock accordingly.
> -	 */
> -	mode->clock = clamp(mode->clock, 31000, 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)
> -{
> -	struct resource *mem;
> -	char name[7];
> -
> -	sprintf(name, "lvds.%u", lvds->index);
> -
> -	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
> -	lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
> -	if (IS_ERR(lvds->mmio))
> -		return PTR_ERR(lvds->mmio);
> -
> -	lvds->clock = devm_clk_get(&pdev->dev, name);
> -	if (IS_ERR(lvds->clock)) {
> -		dev_err(&pdev->dev, "failed to get clock for %s\n", name);
> -		return PTR_ERR(lvds->clock);
> -	}
> -
> -	return 0;
> -}
> -
> -int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
> -{
> -	struct platform_device *pdev = to_platform_device(rcdu->dev);
> -	struct rcar_du_lvdsenc *lvds;
> -	unsigned int i;
> -	int ret;
> -
> -	for (i = 0; i < rcdu->info->num_lvds; ++i) {
> -		lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
> -		if (lvds == NULL)
> -			return -ENOMEM;
> -
> -		lvds->dev = rcdu;
> -		lvds->index = i;
> -		lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
> -		lvds->enabled = false;
> -
> -		ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
> -		if (ret < 0)
> -			return ret;
> -
> -		rcdu->lvds[i] = lvds;
> -	}
> -
> -	return 0;
> -}
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
> deleted file mode 100644
> index 7218ac89333e..000000000000
> --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
> +++ /dev/null
> @@ -1,64 +0,0 @@
> -/*
> - * rcar_du_lvdsenc.h  --  R-Car Display Unit LVDS Encoder
> - *
> - * Copyright (C) 2013-2014 Renesas Electronics Corporation
> - *
> - * Contact: Laurent Pinchart (laurent.pinchart at 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_LVDSENC_H__
> -#define __RCAR_DU_LVDSENC_H__
> -
> -#include <linux/io.h>
> -#include <linux/module.h>
> -
> -struct rcar_drm_crtc;
> -struct rcar_du_lvdsenc;
> -
> -enum rcar_lvds_input {
> -	RCAR_LVDS_INPUT_DU0,
> -	RCAR_LVDS_INPUT_DU1,
> -	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,
> -				  struct drm_display_mode *mode);
> -#else
> -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)
> -{
> -	return 0;
> -}
> -static inline void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
> -						struct drm_display_mode *mode)
> -{
> -}
> -#endif
> -
> -#endif /* __RCAR_DU_LVDSENC_H__ */
> diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> new file mode 100644
> index 000000000000..0a5aa39b1967
> --- /dev/null
> +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> @@ -0,0 +1,524 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * rcar_lvds.c  --  R-Car LVDS Encoder
> + *
> + * Copyright (C) 2013-2014 Renesas Electronics Corporation
> + *
> + * Contact: Laurent Pinchart (laurent.pinchart at ideasonboard.com)
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_panel.h>
> +
> +#include "rcar_lvds_regs.h"
> +
> +/* 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,
> +};
> +
> +#define RCAR_LVDS_QUIRK_LANES	(1 << 0)	/* LVDS lanes 1 and 3 inverted */
> +
> +struct rcar_lvds_device_info {
> +	unsigned int gen;
> +	unsigned int quirks;
> +};
> +
> +struct rcar_lvds {
> +	struct device *dev;
> +	const struct rcar_lvds_device_info *info;
> +
> +	struct drm_bridge bridge;
> +
> +	struct drm_bridge *next_bridge;
> +	struct drm_connector connector;
> +	struct drm_panel *panel;
> +
> +	void __iomem *mmio;
> +	struct clk *clock;
> +	bool enabled;
> +
> +	struct drm_display_mode display_mode;
> +	enum rcar_lvds_mode mode;
> +};
> +
> +#define bridge_to_rcar_lvds(bridge) \
> +	container_of(bridge, struct rcar_lvds, bridge)
> +
> +#define connector_to_rcar_lvds(connector) \
> +	container_of(connector, struct rcar_lvds, connector)
> +
> +static void rcar_lvds_write(struct rcar_lvds *lvds, u32 reg, u32 data)
> +{
> +	iowrite32(data, lvds->mmio + reg);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Connector & Panel
> + */
> +
> +static int rcar_lvds_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
> +
> +	return drm_panel_get_modes(lvds->panel);
> +}
> +
> +static int rcar_lvds_connector_atomic_check(struct drm_connector *connector,
> +					    struct drm_connector_state *state)
> +{
> +	struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
> +	const struct drm_display_mode *panel_mode;
> +	struct drm_crtc_state *crtc_state;
> +
> +	if (list_empty(&connector->modes)) {
> +		dev_dbg(lvds->dev, "connector: 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. */
> +	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
> +	if (IS_ERR(crtc_state))
> +		return PTR_ERR(crtc_state);
> +
> +	if (crtc_state->mode.hdisplay != panel_mode->hdisplay ||
> +	    crtc_state->mode.vdisplay != panel_mode->vdisplay)
> +		return -EINVAL;
> +
> +	/* The flat panel mode is fixed, just copy it to the adjusted mode. */
> +	drm_mode_copy(&crtc_state->adjusted_mode, panel_mode);
> +
> +	return 0;
> +}
> +
> +static const struct drm_connector_helper_funcs rcar_lvds_conn_helper_funcs = {
> +	.get_modes = rcar_lvds_connector_get_modes,
> +	.atomic_check = rcar_lvds_connector_atomic_check,
> +};
> +
> +static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.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,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge
> + */
> +
> +static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
> +{
> +	if (freq < 39000)
> +		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
> +	else if (freq < 61000)
> +		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
> +	else if (freq < 121000)
> +		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
> +	else
> +		return LVDPLLCR_PLLDLYCNT_150M;
> +}
> +
> +static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
> +{
> +	if (freq < 42000)
> +		return LVDPLLCR_PLLDIVCNT_42M;
> +	else if (freq < 85000)
> +		return LVDPLLCR_PLLDIVCNT_85M;
> +	else if (freq < 128000)
> +		return LVDPLLCR_PLLDIVCNT_128M;
> +	else
> +		return LVDPLLCR_PLLDIVCNT_148M;
> +}
> +
> +static void rcar_lvds_enable(struct drm_bridge *bridge)
> +{
> +	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
> +	const struct drm_display_mode *mode = &lvds->display_mode;
> +	/*
> +	 * FIXME: We should really retrieve the CRTC through the state, but how
> +	 * do we get a state pointer?
> +	 */
> +	struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
> +	u32 lvdpllcr;
> +	u32 lvdhcr;
> +	u32 lvdcr0;
> +	int ret;
> +
> +	WARN_ON(lvds->enabled);
> +
> +	ret = clk_prepare_enable(lvds->clock);
> +	if (ret < 0)
> +		return;
> +
> +	/*
> +	 * Hardcode the channels and control signals routing for now.
> +	 *
> +	 * HSYNC -> CTRL0
> +	 * VSYNC -> CTRL1
> +	 * DISP  -> CTRL2
> +	 * 0     -> CTRL3
> +	 */
> +	rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
> +			LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
> +			LVDCTRCR_CTR0SEL_HSYNC);
> +
> +	if (lvds->info->quirks & RCAR_LVDS_QUIRK_LANES)
> +		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
> +		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
> +	else
> +		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
> +		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
> +
> +	rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
> +
> +	/* PLL clock configuration. */
> +	if (lvds->info->gen < 3)
> +		lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
> +	else
> +		lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
> +	rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
> +
> +	/* Set the LVDS mode and select the input. */
> +	lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
> +	if (drm_crtc_index(crtc) == 2)
> +		lvdcr0 |= LVDCR0_DUSEL;
> +	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> +
> +	/* Turn all the channels on. */
> +	rcar_lvds_write(lvds, LVDCR1,
> +			LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
> +			LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
> +
> +	if (lvds->info->gen < 3) {
> +		/* Enable LVDS operation and turn the bias circuitry on. */
> +		lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
> +		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> +	}
> +
> +	/* Turn the PLL on. */
> +	lvdcr0 |= LVDCR0_PLLON;
> +	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> +
> +	if (lvds->info->gen > 2) {
> +		/* Set LVDS normal mode. */
> +		lvdcr0 |= LVDCR0_PWD;
> +		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> +	}
> +
> +	/* Wait for the startup delay. */
> +	usleep_range(100, 150);
> +
> +	/* Turn the output on. */
> +	lvdcr0 |= LVDCR0_LVRES;
> +	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
> +
> +	if (lvds->panel) {
> +		drm_panel_prepare(lvds->panel);
> +		drm_panel_enable(lvds->panel);
> +	}
> +
> +	lvds->enabled = true;
> +}
> +
> +static void rcar_lvds_disable(struct drm_bridge *bridge)
> +{
> +	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
> +
> +	WARN_ON(!lvds->enabled);
> +
> +	if (lvds->panel) {
> +		drm_panel_disable(lvds->panel);
> +		drm_panel_unprepare(lvds->panel);
> +	}
> +
> +	rcar_lvds_write(lvds, LVDCR0, 0);
> +	rcar_lvds_write(lvds, LVDCR1, 0);
> +
> +	clk_disable_unprepare(lvds->clock);
> +
> +	lvds->enabled = false;
> +}
> +
> +static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
> +				 const struct drm_display_mode *mode,
> +				 struct drm_display_mode *adjusted_mode)
> +{
> +	/*
> +	 * The internal LVDS encoder has a restricted clock frequency operating
> +	 * range (31MHz to 148.5MHz). Clamp the clock accordingly.
> +	 */
> +	adjusted_mode->clock = clamp(adjusted_mode->clock, 31000, 148500);
> +
> +	return true;
> +}
> +
> +static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
> +{
> +	struct drm_display_info *info = &lvds->connector.display_info;
> +	enum rcar_lvds_mode mode;
> +
> +	/*
> +	 * There is no API yet to retrieve LVDS mode from a bridge, only panels
> +	 * are supported.
> +	 */
> +	if (!lvds->panel)
> +		return;
> +
> +	if (!info->num_bus_formats || !info->bus_formats) {
> +		dev_err(lvds->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(lvds->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;
> +
> +	lvds->mode = mode;
> +}
> +
> +static void rcar_lvds_mode_set(struct drm_bridge *bridge,
> +			       struct drm_display_mode *mode,
> +			       struct drm_display_mode *adjusted_mode)
> +{
> +	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
> +
> +	WARN_ON(lvds->enabled);
> +
> +	lvds->display_mode = *adjusted_mode;
> +
> +	rcar_lvds_get_lvds_mode(lvds);
> +}
> +
> +static int rcar_lvds_attach(struct drm_bridge *bridge)
> +{
> +	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
> +	struct drm_connector *connector = &lvds->connector;
> +	struct drm_encoder *encoder = bridge->encoder;
> +	int ret;
> +
> +	/* If we have a next bridge just attach it. */
> +	if (lvds->next_bridge)
> +		return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
> +					 bridge);
> +
> +	/* Otherwise we have a panel, create a connector. */
> +	ret = drm_connector_init(bridge->dev, connector, &rcar_lvds_conn_funcs,
> +				 DRM_MODE_CONNECTOR_LVDS);
> +	if (ret < 0)
> +		return ret;
> +
> +	drm_connector_helper_add(connector, &rcar_lvds_conn_helper_funcs);
> +
> +	ret = drm_mode_connector_attach_encoder(connector, encoder);
> +	if (ret < 0)
> +		return ret;
> +
> +	return drm_panel_attach(lvds->panel, connector);
> +}
> +
> +static void rcar_lvds_detach(struct drm_bridge *bridge)
> +{
> +	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
> +
> +	if (lvds->panel)
> +		drm_panel_detach(lvds->panel);
> +}
> +
> +static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
> +	.attach = rcar_lvds_attach,
> +	.detach = rcar_lvds_detach,
> +	.enable = rcar_lvds_enable,
> +	.disable = rcar_lvds_disable,
> +	.mode_fixup = rcar_lvds_mode_fixup,
> +	.mode_set = rcar_lvds_mode_set,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Probe & Remove
> + */
> +
> +static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
> +{
> +	struct device_node *local_output = NULL;
> +	struct device_node *remote_input = NULL;
> +	struct device_node *remote = NULL;
> +	struct device_node *node;
> +	bool is_bridge = false;
> +	int ret = 0;
> +
> +	local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
> +	if (!local_output) {
> +		dev_dbg(lvds->dev, "unconnected port at 1\n");
> +		return -ENODEV;
> +	}
> +
> +	/*
> +	 * Locate the connected entity and infer its type from the number of
> +	 * endpoints.
> +	 */
> +	remote = of_graph_get_remote_port_parent(local_output);
> +	if (!remote) {
> +		dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
> +		ret = -ENODEV;
> +		goto done;
> +	}
> +
> +	if (!of_device_is_available(remote)) {
> +		dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
> +			remote);
> +		ret = -ENODEV;
> +		goto done;
> +	}
> +
> +	remote_input = of_graph_get_remote_endpoint(local_output);
> +
> +	for_each_endpoint_of_node(remote, node) {
> +		if (node != remote_input) {
> +			/*
> +			 * We've found one endpoint other than the input, this
> +			 * must be a bridge.
> +			 */
> +			is_bridge = true;
> +			of_node_put(node);
> +			break;
> +		}
> +	}
> +
> +	if (is_bridge) {
> +		lvds->next_bridge = of_drm_find_bridge(remote);
> +		if (!lvds->next_bridge)
> +			ret = -EPROBE_DEFER;
> +	} else {
> +		lvds->panel = of_drm_find_panel(remote);
> +		if (!lvds->panel)
> +			ret = -EPROBE_DEFER;
> +	}
> +
> +done:
> +	of_node_put(local_output);
> +	of_node_put(remote_input);
> +	of_node_put(remote);
> +
> +	return ret;
> +}
> +
> +static int rcar_lvds_probe(struct platform_device *pdev)
> +{
> +	struct rcar_lvds *lvds;
> +	struct resource *mem;
> +	int ret;
> +
> +	lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
> +	if (lvds == NULL)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, lvds);
> +
> +	lvds->dev = &pdev->dev;
> +	lvds->info = of_device_get_match_data(&pdev->dev);
> +	lvds->enabled = false;
> +
> +	ret = rcar_lvds_parse_dt(lvds);
> +	if (ret < 0)
> +		return ret;
> +
> +	lvds->bridge.driver_private = lvds;
> +	lvds->bridge.funcs = &rcar_lvds_bridge_ops;
> +	lvds->bridge.of_node = pdev->dev.of_node;
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
> +	if (IS_ERR(lvds->mmio))
> +		return PTR_ERR(lvds->mmio);
> +
> +	lvds->clock = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(lvds->clock)) {
> +		dev_err(&pdev->dev, "failed to get clock\n");
> +		return PTR_ERR(lvds->clock);
> +	}
> +
> +	drm_bridge_add(&lvds->bridge);
> +
> +	return 0;
> +}
> +
> +static int rcar_lvds_remove(struct platform_device *pdev)
> +{
> +	struct rcar_lvds *lvds = platform_get_drvdata(pdev);
> +
> +	drm_bridge_remove(&lvds->bridge);
> +
> +	return 0;
> +}
> +
> +static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
> +	.gen = 2,
> +};
> +
> +static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
> +	.gen = 2,
> +	.quirks = RCAR_LVDS_QUIRK_LANES,
> +};
> +
> +static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
> +	.gen = 3,
> +};
> +
> +static const struct of_device_id rcar_lvds_of_table[] = {
> +	{ .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
> +	{ .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info },
> +	{ .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
> +	{ .compatible = "renesas,r8a7793-lvds", .data = &rcar_lvds_gen2_info },
> +	{ .compatible = "renesas,r8a7795-lvds", .data = &rcar_lvds_gen3_info },
> +	{ .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, rcar_lvds_of_table);
> +
> +static struct platform_driver rcar_lvds_platform_driver = {
> +	.probe		= rcar_lvds_probe,
> +	.remove		= rcar_lvds_remove,
> +	.driver		= {
> +		.name	= "rcar-lvds",
> +		.of_match_table = rcar_lvds_of_table,
> +	},
> +};
> +
> +module_platform_driver(rcar_lvds_platform_driver);
> +
> +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart at ideasonboard.com>");
> +MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver");
> +MODULE_LICENSE("GPL");
> -- 
> Regards,
> 
> Laurent Pinchart
> 

-- 
Regards,
Niklas Söderlund


More information about the dri-devel mailing list