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@xxxxxxxxxxxxxxxx> 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@xxxxxxxxxxxx> > --- > 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@xxxxxxxxxxxxxxxx>"); > 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@xxxxxxxxxxxxxxxx) > - * > - * 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@xxxxxxxxxxxxxxxx) > - * > - * 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@xxxxxxxxxxxxxxxx) > - * > - * 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@xxxxxxxxxxxxxxxx) > - * > - * 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@xxxxxxxxxxxxxxxx) > + */ > + > +#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@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@xxxxxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver"); > +MODULE_LICENSE("GPL"); > -- > Regards, > > Laurent Pinchart > -- Regards, Niklas Söderlund