All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel
@ 2019-07-07 18:07 Laurent Pinchart
  2019-07-07 18:07 ` [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks Laurent Pinchart
                   ` (4 more replies)
  0 siblings, 5 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:07 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Hello,

This patch series (nearly, see [1]) completes the rework of the omapdrm
driver to move to drm_bridge and drm_panel.

What a journey. This work was started more than a year ago, and this
last piece is perhaps the one that will generate the most bikeshedding
as it touches the DRM core. I'm braced for the impact, but please be
gentle :-)

Let's start with some context to understand the problem. omapdrm
contains custom drivers for external encoders, panels and connectors
(collectively referred to as display drivers). It combines them to
create output pipelines abstracted by a drm_encoder and a drm_connector,
with the ability to delegate the encoder and connector operations to the
component in the pipeline that implements them. For instance, for an
HDMI output pipeline, the hot plug detection and the EDID read can be
implemented by two different components, when they are handled by two
different devices at the hardware level.

DRM/KMS uses drm_bridge and drm_panel to abstract external encoders and
panels. The model is however simpler than what omapdrm provides, as
bridges were designed to be simple add-ons at the output of a
drm_encoder. The ability to chain bridges exists, but a bridge driver
hardcodes in its design its position in the pipeline : bridges that
expect to terminate the pipeline create a drm_connector, while bridges
that expect to be an intermediate component in the pipeline do not
create a connector. In addition to not supporting bridges that can be
either internal or a termination point in the pipeline depending on the
hardware design, implementing the drm_connector inside a bridge driver
makes it impossible to support hardware where bridge operations are
handled by different hardware components, as explained above.

The omapdrm driver has received support for drm_bridge and drm_panel,
but these issues prevented completely moving away from the omapdrm
custom display drivers. This patch series thus first reworks the
drm_bridge infrastructure to support the omapdrm use cases, and then
transitions the omapdrm driver.

The series starts by 01/60 that adds a new flag to the drm_display_info
structure to identify HDMI sinks. This is a feature needed by the OMAP4
and OMAP5 HDMI encoders, and I believe it can be useful to other HDMI
encoders as well. 02/60 is then a small drive-by cleanup.

The first sizeable change follows with the rename of the dumb-vga-dac
driver to simple-bridge (03/60 and 04/60) and support for non-VGA
bridges (05/60). This doesn't change the spirit of the driver that still
focusses on transparent bridges, but prepares it to support an analog
video amplifier. Patches 06/60 then add support for an enable GPIO, and
07/60 support for the OPA362 video amplifier itself.

The next two patches address the drm_bridge issues explained above.
Patch 08/60 makes it possible to attach to a bridge without having the
bridge create a connector. The connector is expected to be created by
the display controller driver. Patch 09/60 adds connector-related
operations to drm_bridge to make this possible.

The approach taken here is slightly intrusive as path 08/60 adds a
parameter to tbe bridge .attach() operation, and thus touches all bridge
drivers, even if the changes are very simple (as a consequence I haven't
CC'ed all the individual bridge maintainers as the CC list was too
large). Other options may be possible, what matters most to me is the
feature, not so much its implementation. Please note that I envision the
parameter to be removed down the road once all bridge drivers will be
converted to the new model (but this will likely take time, and both
models can co-exist for as long as necessary).

The next six patches make use of these new features: patches 10/60 and
11/60 add new bridge drivers for display connectors and for the TI
TPD12S015 HDMI level shifter respectively, patch 12/60 supports the new
API in the panel bridge driver, and patches 13/60 to 15/60 do the same
in the ti-tfp410 driver.

The nine patches that follow add support for six new panels, with the
related DT bindings (16/60 to 18/60) and the drm_panel drivers (19/60 to
24/60). The code originates from the corresponding omapdrm-specific
panel drivers (which explains why only three DT patches are needed as
most of the bindings are already present).

Patch 25/60 is possibly the most remarkable one in the series, with the
drm_bridge operations extension, as it provides a helper for display
controller drivers to construct a drm_connector entirerly backed by a
chain of bridges. This offsets the complexity of the additional bridge
operations by handling it all in a single place. An example usage for
omapdrm can be found in patch 43/60. Don't let its diffstat mislead you,
usage of the helper would remove lots of code if it wasn't for the fact
that the legacy implementation still has to be kept for the DSI panel
(see [1]). Down the road this helper and the new operation paradigm
should remove code from both display controller and bridge drivers.

The rest of the series is omapdrm-focussed, slowly preparing the driver
for the switch to drm_bridge drivers using the new helper (43/60), the
removal of the omapdrm-specific display drivers (44/60 and 50/60), and
lots of simplification and code removal in the other patches.

[1] The only notable exception is the omapdrm-specific DSI panel driver
that implements a large number of custom operations. This should be
addressed separately.

Laurent Pinchart (60):
  drm/edid: Add flag to drm_display_info to identify HDMI sinks
  video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
  drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge
  drm/bridge: dumb-vga-dac: Rename driver to simple-bridge
  drm/bridge: simple-bridge: Add support for non-VGA bridges
  drm/bridge: simple-bridge: Add support for enable GPIO
  drm/bridge: simple-bridge: Add support for the TI OP362
  drm/bridge: Extend bridge API to disable connector creation
  drm/bridge: Add connector-related bridge operations and data
  drm/bridge: Add bridge driver for display connectors
  drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter
  drm/bridge: panel: Implement bridge connector operations
  drm/bridge: tfp410: Don't include drmP.h
  drm/bridge: tfp410: Replace manual connector handling with bridge
  drm/bridge: tfp410: Allow operation without drm_connector
  dt-bindings: Add vendor prefix for LG Display
  dt-bindings: Add legacy 'toppoly' vendor prefix
  dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel
  drm/panel: Add driver for the LG Philips LB035Q02 panel
  drm/panel: Add driver for the NEC NL8048HL11 panel
  drm/panel: Add driver for the Sharp LS037V7DW01 panel
  drm/panel: Add driver for the Sony ACX565AKM panel
  drm/panel: Add driver for the Toppology TD028TTEC1 panel
  drm/panel: Add driver for the Toppology TD043MTEA1 panel
  drm: Add helper to create a connector for a chain of bridges
  drm/omap: Detach from panels at remove time
  drm/omap: Simplify HDMI mode and infoframe configuration
  drm/omap: Factor out display type to connector type conversion
  drm/omap: Use the drm_panel_bridge API
  drm/omap: dss: Fix output next device lookup in DT
  drm/omap: Add infrastructure to support drm_bridge local to DSS
    outputs
  drm/omap: dss: Make omap_dss_device_ops optional
  drm/omap: hdmi: Allocate EDID in the .read_edid() operation
  drm/omap: hdmi4: Rework EDID read to isolate data read
  drm/omap: hdmi5: Rework EDID read to isolate data read
  drm/omap: hdmi4: Register a drm_bridge for EDID read
  drm/omap: hdmi5: Register a drm_bridge for EDID read
  drm/omap: hdmi4: Move mode set, enable and disable operations to
    bridge
  drm/omap: hdmi5: Move mode set, enable and disable operations to
    bridge
  drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation
  drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations
  drm/omap: venc: Register a drm_bridge
  drm/omap: Create connector for bridges
  drm/omap: Switch the HDMI and VENC outputs to drm_bridge
  drm/omap: Remove HPD, detect and EDID omapdss operations
  drm/omap: hdmi: Remove omap_dss_device operations
  drm/omap: venc: Remove omap_dss_device operations
  drm/omap: hdmi4: Simplify EDID read
  drm/omap: hdmi5: Simplify EDID read
  drm/omap: displays: Remove unused panel drivers
  drm/omap: dpi: Sort includes alphabetically
  drm/omap: dpi: Reorder functions in sections
  drm/omap: dpi: Simplify clock setting API
  drm/omap: dpi: Register a drm_bridge
  drm/omap: sdi: Sort includes alphabetically
  drm/omap: sdi: Register a drm_bridge
  drm/omap: Simplify connector implementation
  drm/omap: dss: Remove unused omap_dss_device operations
  drm/omap: dss: Inline the omapdss_display_get() function
  drm/omap: dss: Remove unused omapdss_of_find_connected_device()
    function

 .../bindings/display/panel/nec,nl8048hl11.txt |  38 +
 .../devicetree/bindings/vendor-prefixes.yaml  |   4 +
 arch/arm/configs/davinci_all_defconfig        |   2 +-
 arch/arm/configs/integrator_defconfig         |   2 +-
 arch/arm/configs/multi_v7_defconfig           |   2 +-
 arch/arm/configs/shmobile_defconfig           |   2 +-
 arch/arm/configs/sunxi_defconfig              |   2 +-
 arch/arm/configs/versatile_defconfig          |   2 +-
 drivers/gpu/drm/Makefile                      |   3 +-
 drivers/gpu/drm/arc/arcpgu_hdmi.c             |   2 +-
 .../gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c  |   2 +-
 drivers/gpu/drm/bridge/Kconfig                |  29 +-
 drivers/gpu/drm/bridge/Makefile               |   4 +-
 drivers/gpu/drm/bridge/adv7511/adv7511_drv.c  |   6 +-
 drivers/gpu/drm/bridge/analogix-anx78xx.c     |   6 +-
 .../drm/bridge/analogix/analogix_dp_core.c    |   8 +-
 drivers/gpu/drm/bridge/cdns-dsi.c             |   6 +-
 drivers/gpu/drm/bridge/display-connector.c    | 327 ++++++++
 drivers/gpu/drm/bridge/dumb-vga-dac.c         | 296 -------
 drivers/gpu/drm/bridge/lvds-encoder.c         |   4 +-
 .../bridge/megachips-stdpxxxx-ge-b850v3-fw.c  |   6 +-
 drivers/gpu/drm/bridge/nxp-ptn3460.c          |   6 +-
 drivers/gpu/drm/bridge/panel.c                |  21 +-
 drivers/gpu/drm/bridge/parade-ps8622.c        |   5 +-
 drivers/gpu/drm/bridge/sii902x.c              |   6 +-
 drivers/gpu/drm/bridge/sil-sii8620.c          |   2 +-
 drivers/gpu/drm/bridge/simple-bridge.c        | 337 ++++++++
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   8 +-
 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c |   8 +-
 drivers/gpu/drm/bridge/tc358764.c             |   5 +-
 drivers/gpu/drm/bridge/tc358767.c             |   5 +-
 drivers/gpu/drm/bridge/thc63lvd1024.c         |   5 +-
 drivers/gpu/drm/bridge/ti-sn65dsi86.c         |   5 +-
 drivers/gpu/drm/bridge/ti-tfp410.c            | 202 ++---
 drivers/gpu/drm/bridge/ti-tpd12s015.c         | 204 +++++
 drivers/gpu/drm/drm_bridge.c                  |  97 ++-
 drivers/gpu/drm/drm_bridge_connector.c        | 385 +++++++++
 drivers/gpu/drm/drm_edid.c                    |   8 +-
 drivers/gpu/drm/drm_simple_kms_helper.c       |   2 +-
 drivers/gpu/drm/exynos/exynos_dp.c            |   3 +-
 drivers/gpu/drm/exynos/exynos_drm_dsi.c       |   4 +-
 drivers/gpu/drm/exynos/exynos_hdmi.c          |   2 +-
 drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c     |   2 +-
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c  |   2 +-
 drivers/gpu/drm/i2c/tda998x_drv.c             |   8 +-
 drivers/gpu/drm/imx/imx-ldb.c                 |   2 +-
 drivers/gpu/drm/imx/parallel-display.c        |   2 +-
 drivers/gpu/drm/mcde/mcde_dsi.c               |   6 +-
 drivers/gpu/drm/mediatek/mtk_dpi.c            |   2 +-
 drivers/gpu/drm/mediatek/mtk_dsi.c            |   2 +-
 drivers/gpu/drm/mediatek/mtk_hdmi.c           |   8 +-
 drivers/gpu/drm/msm/dsi/dsi_manager.c         |   4 +-
 drivers/gpu/drm/msm/edp/edp_bridge.c          |   2 +-
 drivers/gpu/drm/msm/hdmi/hdmi_bridge.c        |   2 +-
 drivers/gpu/drm/omapdrm/displays/Kconfig      |  60 --
 drivers/gpu/drm/omapdrm/displays/Makefile     |  10 -
 .../omapdrm/displays/connector-analog-tv.c    | 100 ---
 .../gpu/drm/omapdrm/displays/connector-hdmi.c | 186 -----
 .../gpu/drm/omapdrm/displays/encoder-opa362.c | 140 ----
 .../drm/omapdrm/displays/encoder-tpd12s015.c  | 220 -----
 .../gpu/drm/omapdrm/displays/panel-dsi-cm.c   |   2 +-
 .../displays/panel-lgphilips-lb035q02.c       | 254 ------
 .../omapdrm/displays/panel-nec-nl8048hl11.c   | 271 -------
 .../displays/panel-sharp-ls037v7dw01.c        | 265 ------
 .../omapdrm/displays/panel-sony-acx565akm.c   | 766 ------------------
 .../omapdrm/displays/panel-tpo-td028ttec1.c   | 401 ---------
 .../omapdrm/displays/panel-tpo-td043mtea1.c   | 513 ------------
 drivers/gpu/drm/omapdrm/dss/Makefile          |   2 +-
 drivers/gpu/drm/omapdrm/dss/base.c            |  72 +-
 drivers/gpu/drm/omapdrm/dss/display.c         |   9 -
 drivers/gpu/drm/omapdrm/dss/dpi.c             | 336 ++++----
 drivers/gpu/drm/omapdrm/dss/dsi.c             |   4 +-
 drivers/gpu/drm/omapdrm/dss/dss-of.c          |  28 -
 drivers/gpu/drm/omapdrm/dss/dss.c             |   3 +-
 drivers/gpu/drm/omapdrm/dss/hdmi.h            |   4 +-
 drivers/gpu/drm/omapdrm/dss/hdmi4.c           | 321 ++++----
 drivers/gpu/drm/omapdrm/dss/hdmi4_core.c      |  59 +-
 drivers/gpu/drm/omapdrm/dss/hdmi4_core.h      |   4 +-
 drivers/gpu/drm/omapdrm/dss/hdmi5.c           | 303 +++----
 drivers/gpu/drm/omapdrm/dss/hdmi5_core.c      |  48 +-
 drivers/gpu/drm/omapdrm/dss/hdmi5_core.h      |   5 +-
 .../gpu/drm/omapdrm/dss/omapdss-boot-init.c   |  12 -
 drivers/gpu/drm/omapdrm/dss/omapdss.h         |  47 +-
 drivers/gpu/drm/omapdrm/dss/output.c          |  55 +-
 drivers/gpu/drm/omapdrm/dss/sdi.c             | 187 +++--
 drivers/gpu/drm/omapdrm/dss/venc.c            | 269 +++---
 drivers/gpu/drm/omapdrm/omap_connector.c      | 246 +-----
 drivers/gpu/drm/omapdrm/omap_connector.h      |   3 -
 drivers/gpu/drm/omapdrm/omap_drv.c            |  98 ++-
 drivers/gpu/drm/omapdrm/omap_encoder.c        |  83 +-
 drivers/gpu/drm/panel/Kconfig                 |  44 +
 drivers/gpu/drm/panel/Makefile                |   6 +
 drivers/gpu/drm/panel/panel-lg-lb035q02.c     | 235 ++++++
 drivers/gpu/drm/panel/panel-nec-nl8048hl11.c  | 249 ++++++
 .../gpu/drm/panel/panel-sharp-ls037v7dw01.c   | 231 ++++++
 drivers/gpu/drm/panel/panel-sony-acx565akm.c  | 691 ++++++++++++++++
 drivers/gpu/drm/panel/panel-tpo-td028ttec1.c  | 382 +++++++++
 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c  | 510 ++++++++++++
 drivers/gpu/drm/rcar-du/rcar_du_encoder.c     |   2 +-
 drivers/gpu/drm/rcar-du/rcar_lvds.c           |   7 +-
 drivers/gpu/drm/rockchip/rockchip_lvds.c      |   2 +-
 drivers/gpu/drm/rockchip/rockchip_rgb.c       |   2 +-
 drivers/gpu/drm/sti/sti_dvo.c                 |   2 +-
 drivers/gpu/drm/sti/sti_hda.c                 |   2 +-
 drivers/gpu/drm/sti/sti_hdmi.c                |   2 +-
 drivers/gpu/drm/stm/ltdc.c                    |   2 +-
 drivers/gpu/drm/sun4i/sun4i_lvds.c            |   2 +-
 drivers/gpu/drm/sun4i/sun4i_rgb.c             |   2 +-
 drivers/gpu/drm/tilcdc/tilcdc_external.c      |   2 +-
 drivers/gpu/drm/vc4/vc4_dpi.c                 |   2 +-
 drivers/gpu/drm/vc4/vc4_dsi.c                 |   2 +-
 drivers/video/hdmi.c                          |   9 +-
 include/drm/drm_bridge.h                      | 174 +++-
 include/drm/drm_bridge_connector.h            |  18 +
 include/drm/drm_connector.h                   |   5 +
 include/linux/hdmi.h                          |   2 +-
 116 files changed, 5188 insertions(+), 4900 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt
 create mode 100644 drivers/gpu/drm/bridge/display-connector.c
 delete mode 100644 drivers/gpu/drm/bridge/dumb-vga-dac.c
 create mode 100644 drivers/gpu/drm/bridge/simple-bridge.c
 create mode 100644 drivers/gpu/drm/bridge/ti-tpd12s015.c
 create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
 delete mode 100644 drivers/gpu/drm/omapdrm/dss/dss-of.c
 create mode 100644 drivers/gpu/drm/panel/panel-lg-lb035q02.c
 create mode 100644 drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
 create mode 100644 drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
 create mode 100644 drivers/gpu/drm/panel/panel-sony-acx565akm.c
 create mode 100644 drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
 create mode 100644 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
 create mode 100644 include/drm/drm_bridge_connector.h

-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks
  2019-07-07 18:07 [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Laurent Pinchart
@ 2019-07-07 18:07 ` Laurent Pinchart
  2019-07-09 13:20   ` Andrzej Hajda
  2019-07-10 15:59   ` Ville Syrjälä
  2019-07-07 18:07 ` [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void Laurent Pinchart
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:07 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The drm_display_info structure contains many fields related to HDMI
sinks, but none that identifies if a sink compliant with CEA-861 (EDID)
shall be treated as an HDMI sink or a DVI sink. Add such a flag, and
populate it according to section 8.3.3 ("DVI/HDMI Device
Discrimination") of the HDMI v1.3 specification.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/drm_edid.c  | 3 +++
 include/drm/drm_connector.h | 5 +++++
 2 files changed, 8 insertions(+)

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 82a4ceed3fcf..d2e7a5334c3f 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -4559,6 +4559,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
 	struct drm_display_info *info = &connector->display_info;
 	u8 len = cea_db_payload_len(db);
 
+	info->is_hdmi = true;
+
 	if (len >= 6)
 		info->dvi_dual = db[6] & 1;
 	if (len >= 7)
@@ -4627,6 +4629,7 @@ drm_reset_display_info(struct drm_connector *connector)
 	info->cea_rev = 0;
 	info->max_tmds_clock = 0;
 	info->dvi_dual = false;
+	info->is_hdmi = false;
 	info->has_hdmi_infoframe = false;
 	info->rgb_quant_range_selectable = false;
 	memset(&info->hdmi, 0, sizeof(info->hdmi));
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index ca745d9feaf5..e80ca0d149e5 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -426,6 +426,11 @@ struct drm_display_info {
 	 */
 	bool dvi_dual;
 
+	/**
+	 * @is_hdmi: True if the sink is an HDMI device.
+	 */
+	bool is_hdmi;
+
 	/**
 	 * @has_hdmi_infoframe: Does the sink support the HDMI infoframe?
 	 */
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
  2019-07-07 18:07 [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Laurent Pinchart
  2019-07-07 18:07 ` [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks Laurent Pinchart
@ 2019-07-07 18:07 ` Laurent Pinchart
  2019-07-07 18:14   ` Laurent Pinchart
  2019-07-09 13:22   ` Andrzej Hajda
  2019-07-07 18:07 ` [PATCH 03/60] drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge Laurent Pinchart
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:07 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The hdmi_avi_infoframe_init() never needs to return an error, change its
return type to void.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/drm_edid.c | 5 +----
 drivers/video/hdmi.c       | 9 ++-------
 include/linux/hdmi.h       | 2 +-
 3 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index d2e7a5334c3f..a7d3189ea8f3 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -5086,14 +5086,11 @@ drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame,
 					 const struct drm_display_mode *mode)
 {
 	enum hdmi_picture_aspect picture_aspect;
-	int err;
 
 	if (!frame || !mode)
 		return -EINVAL;
 
-	err = hdmi_avi_infoframe_init(frame);
-	if (err < 0)
-		return err;
+	hdmi_avi_infoframe_init(frame);
 
 	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
 		frame->pixel_repeat = 1;
diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index b939bc28d886..54fb7cf11d1a 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -56,15 +56,13 @@ static void hdmi_infoframe_set_checksum(void *buffer, size_t size)
  *
  * Returns 0 on success or a negative error code on failure.
  */
-int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
+void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
 {
 	memset(frame, 0, sizeof(*frame));
 
 	frame->type = HDMI_INFOFRAME_TYPE_AVI;
 	frame->version = 2;
 	frame->length = HDMI_AVI_INFOFRAME_SIZE;
-
-	return 0;
 }
 EXPORT_SYMBOL(hdmi_avi_infoframe_init);
 
@@ -1553,7 +1551,6 @@ static int hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame,
 				     const void *buffer, size_t size)
 {
 	const u8 *ptr = buffer;
-	int ret;
 
 	if (size < HDMI_INFOFRAME_SIZE(AVI))
 		return -EINVAL;
@@ -1566,9 +1563,7 @@ static int hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame,
 	if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(AVI)) != 0)
 		return -EINVAL;
 
-	ret = hdmi_avi_infoframe_init(frame);
-	if (ret)
-		return ret;
+	hdmi_avi_infoframe_init(frame);
 
 	ptr += HDMI_INFOFRAME_HEADER_SIZE;
 
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index 9918a6c910c5..9613d796cfb1 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -207,7 +207,7 @@ struct hdmi_drm_infoframe {
 	u16 max_fall;
 };
 
-int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
+void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
 ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer,
 				size_t size);
 ssize_t hdmi_avi_infoframe_pack_only(const struct hdmi_avi_infoframe *frame,
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 03/60] drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge
  2019-07-07 18:07 [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Laurent Pinchart
  2019-07-07 18:07 ` [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks Laurent Pinchart
  2019-07-07 18:07 ` [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void Laurent Pinchart
@ 2019-07-07 18:07 ` Laurent Pinchart
  2019-07-09 13:34   ` Andrzej Hajda
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
  2019-07-11  7:37 ` [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Daniel Vetter
  4 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:07 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The dumb-vga-dac driver is a simple DRM bridge driver for simple VGA
DACs that don't require configuration. Other non-VGA bridges fall in a
similar category, and would benefit from a common driver. Prepare for
this by renaming the internal symbols from dumb-vga-dac to
simple-bridge.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/dumb-vga-dac.c | 149 +++++++++++++-------------
 1 file changed, 75 insertions(+), 74 deletions(-)

diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c
index d32885b906ae..d46e461ae039 100644
--- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
+++ b/drivers/gpu/drm/bridge/dumb-vga-dac.c
@@ -16,7 +16,7 @@
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 
-struct dumb_vga {
+struct simple_bridge {
 	struct drm_bridge	bridge;
 	struct drm_connector	connector;
 
@@ -24,28 +24,28 @@ struct dumb_vga {
 	struct regulator	*vdd;
 };
 
-static inline struct dumb_vga *
-drm_bridge_to_dumb_vga(struct drm_bridge *bridge)
+static inline struct simple_bridge *
+drm_bridge_to_simple_bridge(struct drm_bridge *bridge)
 {
-	return container_of(bridge, struct dumb_vga, bridge);
+	return container_of(bridge, struct simple_bridge, bridge);
 }
 
-static inline struct dumb_vga *
-drm_connector_to_dumb_vga(struct drm_connector *connector)
+static inline struct simple_bridge *
+drm_connector_to_simple_bridge(struct drm_connector *connector)
 {
-	return container_of(connector, struct dumb_vga, connector);
+	return container_of(connector, struct simple_bridge, connector);
 }
 
-static int dumb_vga_get_modes(struct drm_connector *connector)
+static int simple_bridge_get_modes(struct drm_connector *connector)
 {
-	struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
+	struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
 	struct edid *edid;
 	int ret;
 
-	if (IS_ERR(vga->ddc))
+	if (IS_ERR(sbridge->ddc))
 		goto fallback;
 
-	edid = drm_get_edid(connector, vga->ddc);
+	edid = drm_get_edid(connector, sbridge->ddc);
 	if (!edid) {
 		DRM_INFO("EDID readout failed, falling back to standard modes\n");
 		goto fallback;
@@ -69,14 +69,14 @@ static int dumb_vga_get_modes(struct drm_connector *connector)
 	return ret;
 }
 
-static const struct drm_connector_helper_funcs dumb_vga_con_helper_funcs = {
-	.get_modes	= dumb_vga_get_modes,
+static const struct drm_connector_helper_funcs simple_bridge_con_helper_funcs = {
+	.get_modes	= simple_bridge_get_modes,
 };
 
 static enum drm_connector_status
-dumb_vga_connector_detect(struct drm_connector *connector, bool force)
+simple_bridge_connector_detect(struct drm_connector *connector, bool force)
 {
-	struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
+	struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
 
 	/*
 	 * Even if we have an I2C bus, we can't assume that the cable
@@ -84,14 +84,14 @@ dumb_vga_connector_detect(struct drm_connector *connector, bool force)
 	 * wire the DDC pins, or the I2C bus might not be working at
 	 * all.
 	 */
-	if (!IS_ERR(vga->ddc) && drm_probe_ddc(vga->ddc))
+	if (!IS_ERR(sbridge->ddc) && drm_probe_ddc(sbridge->ddc))
 		return connector_status_connected;
 
 	return connector_status_unknown;
 }
 
-static const struct drm_connector_funcs dumb_vga_con_funcs = {
-	.detect			= dumb_vga_connector_detect,
+static const struct drm_connector_funcs simple_bridge_con_funcs = {
+	.detect			= simple_bridge_connector_detect,
 	.fill_modes		= drm_helper_probe_single_connector_modes,
 	.destroy		= drm_connector_cleanup,
 	.reset			= drm_atomic_helper_connector_reset,
@@ -99,9 +99,9 @@ static const struct drm_connector_funcs dumb_vga_con_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 };
 
-static int dumb_vga_attach(struct drm_bridge *bridge)
+static int simple_bridge_attach(struct drm_bridge *bridge)
 {
-	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
+	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
 	int ret;
 
 	if (!bridge->encoder) {
@@ -109,48 +109,49 @@ static int dumb_vga_attach(struct drm_bridge *bridge)
 		return -ENODEV;
 	}
 
-	drm_connector_helper_add(&vga->connector,
-				 &dumb_vga_con_helper_funcs);
-	ret = drm_connector_init(bridge->dev, &vga->connector,
-				 &dumb_vga_con_funcs, DRM_MODE_CONNECTOR_VGA);
+	drm_connector_helper_add(&sbridge->connector,
+				 &simple_bridge_con_helper_funcs);
+	ret = drm_connector_init(bridge->dev, &sbridge->connector,
+				 &simple_bridge_con_funcs,
+				 DRM_MODE_CONNECTOR_VGA);
 	if (ret) {
 		DRM_ERROR("Failed to initialize connector\n");
 		return ret;
 	}
 
-	drm_connector_attach_encoder(&vga->connector,
+	drm_connector_attach_encoder(&sbridge->connector,
 					  bridge->encoder);
 
 	return 0;
 }
 
-static void dumb_vga_enable(struct drm_bridge *bridge)
+static void simple_bridge_enable(struct drm_bridge *bridge)
 {
-	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
+	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
 	int ret = 0;
 
-	if (vga->vdd)
-		ret = regulator_enable(vga->vdd);
+	if (sbridge->vdd)
+		ret = regulator_enable(sbridge->vdd);
 
 	if (ret)
 		DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
 }
 
-static void dumb_vga_disable(struct drm_bridge *bridge)
+static void simple_bridge_disable(struct drm_bridge *bridge)
 {
-	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
+	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
 
-	if (vga->vdd)
-		regulator_disable(vga->vdd);
+	if (sbridge->vdd)
+		regulator_disable(sbridge->vdd);
 }
 
-static const struct drm_bridge_funcs dumb_vga_bridge_funcs = {
-	.attach		= dumb_vga_attach,
-	.enable		= dumb_vga_enable,
-	.disable	= dumb_vga_disable,
+static const struct drm_bridge_funcs simple_bridge_bridge_funcs = {
+	.attach		= simple_bridge_attach,
+	.enable		= simple_bridge_enable,
+	.disable	= simple_bridge_disable,
 };
 
-static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
+static struct i2c_adapter *simple_bridge_retrieve_ddc(struct device *dev)
 {
 	struct device_node *phandle, *remote;
 	struct i2c_adapter *ddc;
@@ -172,52 +173,52 @@ static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
 	return ddc;
 }
 
-static int dumb_vga_probe(struct platform_device *pdev)
+static int simple_bridge_probe(struct platform_device *pdev)
 {
-	struct dumb_vga *vga;
+	struct simple_bridge *sbridge;
 
-	vga = devm_kzalloc(&pdev->dev, sizeof(*vga), GFP_KERNEL);
-	if (!vga)
+	sbridge = devm_kzalloc(&pdev->dev, sizeof(*sbridge), GFP_KERNEL);
+	if (!sbridge)
 		return -ENOMEM;
-	platform_set_drvdata(pdev, vga);
+	platform_set_drvdata(pdev, sbridge);
 
-	vga->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
-	if (IS_ERR(vga->vdd)) {
-		int ret = PTR_ERR(vga->vdd);
+	sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
+	if (IS_ERR(sbridge->vdd)) {
+		int ret = PTR_ERR(sbridge->vdd);
 		if (ret == -EPROBE_DEFER)
 			return -EPROBE_DEFER;
-		vga->vdd = NULL;
+		sbridge->vdd = NULL;
 		dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
 	}
 
-	vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
-	if (IS_ERR(vga->ddc)) {
-		if (PTR_ERR(vga->ddc) == -ENODEV) {
+	sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
+	if (IS_ERR(sbridge->ddc)) {
+		if (PTR_ERR(sbridge->ddc) == -ENODEV) {
 			dev_dbg(&pdev->dev,
 				"No i2c bus specified. Disabling EDID readout\n");
 		} else {
 			dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
-			return PTR_ERR(vga->ddc);
+			return PTR_ERR(sbridge->ddc);
 		}
 	}
 
-	vga->bridge.funcs = &dumb_vga_bridge_funcs;
-	vga->bridge.of_node = pdev->dev.of_node;
-	vga->bridge.timings = of_device_get_match_data(&pdev->dev);
+	sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
+	sbridge->bridge.of_node = pdev->dev.of_node;
+	sbridge->bridge.timings = of_device_get_match_data(&pdev->dev);
 
-	drm_bridge_add(&vga->bridge);
+	drm_bridge_add(&sbridge->bridge);
 
 	return 0;
 }
 
-static int dumb_vga_remove(struct platform_device *pdev)
+static int simple_bridge_remove(struct platform_device *pdev)
 {
-	struct dumb_vga *vga = platform_get_drvdata(pdev);
+	struct simple_bridge *sbridge = platform_get_drvdata(pdev);
 
-	drm_bridge_remove(&vga->bridge);
+	drm_bridge_remove(&sbridge->bridge);
 
-	if (!IS_ERR(vga->ddc))
-		i2c_put_adapter(vga->ddc);
+	if (!IS_ERR(sbridge->ddc))
+		i2c_put_adapter(sbridge->ddc);
 
 	return 0;
 }
@@ -228,7 +229,7 @@ static int dumb_vga_remove(struct platform_device *pdev)
  * NOTE: the ADV7123EP seems to have other timings and need a new timings
  * set if used.
  */
-static const struct drm_bridge_timings default_dac_timings = {
+static const struct drm_bridge_timings default_bridge_timings = {
 	/* Timing specifications, datasheet page 7 */
 	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
 	.setup_time_ps = 500,
@@ -239,7 +240,7 @@ static const struct drm_bridge_timings default_dac_timings = {
  * Information taken from the THS8134, THS8134A, THS8134B datasheet named
  * "SLVS205D", dated May 1990, revised March 2000.
  */
-static const struct drm_bridge_timings ti_ths8134_dac_timings = {
+static const struct drm_bridge_timings ti_ths8134_bridge_timings = {
 	/* From timing diagram, datasheet page 9 */
 	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
 	/* From datasheet, page 12 */
@@ -252,7 +253,7 @@ static const struct drm_bridge_timings ti_ths8134_dac_timings = {
  * Information taken from the THS8135 datasheet named "SLAS343B", dated
  * May 2001, revised April 2013.
  */
-static const struct drm_bridge_timings ti_ths8135_dac_timings = {
+static const struct drm_bridge_timings ti_ths8135_bridge_timings = {
 	/* From timing diagram, datasheet page 14 */
 	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
 	/* From datasheet, page 16 */
@@ -260,37 +261,37 @@ static const struct drm_bridge_timings ti_ths8135_dac_timings = {
 	.hold_time_ps = 500,
 };
 
-static const struct of_device_id dumb_vga_match[] = {
+static const struct of_device_id simple_bridge_match[] = {
 	{
 		.compatible = "dumb-vga-dac",
 		.data = NULL,
 	},
 	{
 		.compatible = "adi,adv7123",
-		.data = &default_dac_timings,
+		.data = &default_bridge_timings,
 	},
 	{
 		.compatible = "ti,ths8135",
-		.data = &ti_ths8135_dac_timings,
+		.data = &ti_ths8135_bridge_timings,
 	},
 	{
 		.compatible = "ti,ths8134",
-		.data = &ti_ths8134_dac_timings,
+		.data = &ti_ths8134_bridge_timings,
 	},
 	{},
 };
-MODULE_DEVICE_TABLE(of, dumb_vga_match);
+MODULE_DEVICE_TABLE(of, simple_bridge_match);
 
-static struct platform_driver dumb_vga_driver = {
-	.probe	= dumb_vga_probe,
-	.remove	= dumb_vga_remove,
+static struct platform_driver simple_bridge_driver = {
+	.probe	= simple_bridge_probe,
+	.remove	= simple_bridge_remove,
 	.driver		= {
 		.name		= "dumb-vga-dac",
-		.of_match_table	= dumb_vga_match,
+		.of_match_table	= simple_bridge_match,
 	},
 };
-module_platform_driver(dumb_vga_driver);
+module_platform_driver(simple_bridge_driver);
 
 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
-MODULE_DESCRIPTION("Dumb VGA DAC bridge driver");
+MODULE_DESCRIPTION("Simple DRM bridge driver");
 MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* Re: [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
  2019-07-07 18:07 ` [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void Laurent Pinchart
@ 2019-07-07 18:14   ` Laurent Pinchart
  2019-07-07 18:21     ` Ilia Mirkin
  2019-07-09 13:22   ` Andrzej Hajda
  1 sibling, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:14 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel,
	Bartlomiej Zolnierkiewicz

Sorry, forgot to CC Bartlomiej on this patch.

On Sun, Jul 07, 2019 at 09:07:54PM +0300, Laurent Pinchart wrote:
> The hdmi_avi_infoframe_init() never needs to return an error, change its
> return type to void.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/drm_edid.c | 5 +----
>  drivers/video/hdmi.c       | 9 ++-------
>  include/linux/hdmi.h       | 2 +-
>  3 files changed, 4 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> index d2e7a5334c3f..a7d3189ea8f3 100644
> --- a/drivers/gpu/drm/drm_edid.c
> +++ b/drivers/gpu/drm/drm_edid.c
> @@ -5086,14 +5086,11 @@ drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame,
>  					 const struct drm_display_mode *mode)
>  {
>  	enum hdmi_picture_aspect picture_aspect;
> -	int err;
>  
>  	if (!frame || !mode)
>  		return -EINVAL;
>  
> -	err = hdmi_avi_infoframe_init(frame);
> -	if (err < 0)
> -		return err;
> +	hdmi_avi_infoframe_init(frame);
>  
>  	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
>  		frame->pixel_repeat = 1;
> diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
> index b939bc28d886..54fb7cf11d1a 100644
> --- a/drivers/video/hdmi.c
> +++ b/drivers/video/hdmi.c
> @@ -56,15 +56,13 @@ static void hdmi_infoframe_set_checksum(void *buffer, size_t size)
>   *
>   * Returns 0 on success or a negative error code on failure.
>   */
> -int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
> +void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
>  {
>  	memset(frame, 0, sizeof(*frame));
>  
>  	frame->type = HDMI_INFOFRAME_TYPE_AVI;
>  	frame->version = 2;
>  	frame->length = HDMI_AVI_INFOFRAME_SIZE;
> -
> -	return 0;
>  }
>  EXPORT_SYMBOL(hdmi_avi_infoframe_init);
>  
> @@ -1553,7 +1551,6 @@ static int hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame,
>  				     const void *buffer, size_t size)
>  {
>  	const u8 *ptr = buffer;
> -	int ret;
>  
>  	if (size < HDMI_INFOFRAME_SIZE(AVI))
>  		return -EINVAL;
> @@ -1566,9 +1563,7 @@ static int hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame,
>  	if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(AVI)) != 0)
>  		return -EINVAL;
>  
> -	ret = hdmi_avi_infoframe_init(frame);
> -	if (ret)
> -		return ret;
> +	hdmi_avi_infoframe_init(frame);
>  
>  	ptr += HDMI_INFOFRAME_HEADER_SIZE;
>  
> diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
> index 9918a6c910c5..9613d796cfb1 100644
> --- a/include/linux/hdmi.h
> +++ b/include/linux/hdmi.h
> @@ -207,7 +207,7 @@ struct hdmi_drm_infoframe {
>  	u16 max_fall;
>  };
>  
> -int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
> +void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
>  ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer,
>  				size_t size);
>  ssize_t hdmi_avi_infoframe_pack_only(const struct hdmi_avi_infoframe *frame,
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver to simple-bridge
  2019-07-07 18:07 [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Laurent Pinchart
                   ` (2 preceding siblings ...)
  2019-07-07 18:07 ` [PATCH 03/60] drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge Laurent Pinchart
@ 2019-07-07 18:18 ` Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges Laurent Pinchart
                     ` (56 more replies)
  2019-07-11  7:37 ` [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Daniel Vetter
  4 siblings, 57 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Simon Horman, Maxime Ripard, Sebastian Reichel, Russell King,
	Chen-Yu Tsai, Tomi Valkeinen, Sean Paul

The dumb-vga-dac driver can support simple DRM bridges without being
limited to VGA DACs. Rename it to simple-bridge.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 arch/arm/configs/davinci_all_defconfig           |  2 +-
 arch/arm/configs/integrator_defconfig            |  2 +-
 arch/arm/configs/multi_v7_defconfig              |  2 +-
 arch/arm/configs/shmobile_defconfig              |  2 +-
 arch/arm/configs/sunxi_defconfig                 |  2 +-
 arch/arm/configs/versatile_defconfig             |  2 +-
 drivers/gpu/drm/bridge/Kconfig                   | 16 ++++++++--------
 drivers/gpu/drm/bridge/Makefile                  |  2 +-
 .../bridge/{dumb-vga-dac.c => simple-bridge.c}   |  2 +-
 9 files changed, 16 insertions(+), 16 deletions(-)
 rename drivers/gpu/drm/bridge/{dumb-vga-dac.c => simple-bridge.c} (99%)

diff --git a/arch/arm/configs/davinci_all_defconfig b/arch/arm/configs/davinci_all_defconfig
index 4a8cad4d3707..f422d34a4e4e 100644
--- a/arch/arm/configs/davinci_all_defconfig
+++ b/arch/arm/configs/davinci_all_defconfig
@@ -154,7 +154,7 @@ CONFIG_VIDEO_TVP514X=m
 CONFIG_VIDEO_ADV7343=m
 CONFIG_DRM=m
 CONFIG_DRM_TILCDC=m
-CONFIG_DRM_DUMB_VGA_DAC=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
 CONFIG_DRM_TINYDRM=m
 CONFIG_TINYDRM_ST7586=m
 CONFIG_FB=y
diff --git a/arch/arm/configs/integrator_defconfig b/arch/arm/configs/integrator_defconfig
index 747550c7af2f..4d265a689655 100644
--- a/arch/arm/configs/integrator_defconfig
+++ b/arch/arm/configs/integrator_defconfig
@@ -55,7 +55,7 @@ CONFIG_SMC91X=y
 # CONFIG_KEYBOARD_ATKBD is not set
 # CONFIG_SERIO_SERPORT is not set
 CONFIG_DRM=y
-CONFIG_DRM_DUMB_VGA_DAC=y
+CONFIG_DRM_SIMPLE_BRIDGE=y
 CONFIG_DRM_PL111=y
 CONFIG_FB_MODE_HELPERS=y
 CONFIG_FB_MATROX=y
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 6b748f214eae..634e029a5736 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -643,11 +643,11 @@ CONFIG_DRM_PANEL_ORISETECH_OTM8009A=m
 CONFIG_DRM_PANEL_RAYDIUM_RM68200=m
 CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03=m
 CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0=m
-CONFIG_DRM_DUMB_VGA_DAC=m
 CONFIG_DRM_NXP_PTN3460=m
 CONFIG_DRM_PARADE_PS8622=m
 CONFIG_DRM_SII902X=m
 CONFIG_DRM_SII9234=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
 CONFIG_DRM_TOSHIBA_TC358764=m
 CONFIG_DRM_I2C_ADV7511=m
 CONFIG_DRM_I2C_ADV7511_AUDIO=y
diff --git a/arch/arm/configs/shmobile_defconfig b/arch/arm/configs/shmobile_defconfig
index eb02ba9ec6e6..771074e399fb 100644
--- a/arch/arm/configs/shmobile_defconfig
+++ b/arch/arm/configs/shmobile_defconfig
@@ -125,8 +125,8 @@ CONFIG_VIDEO_ADV7604=y
 CONFIG_VIDEO_ML86V7667=y
 CONFIG_DRM=y
 CONFIG_DRM_RCAR_DU=y
-CONFIG_DRM_DUMB_VGA_DAC=y
 CONFIG_DRM_SII902X=y
+CONFIG_DRM_SIMPLE_BRIDGE=y
 CONFIG_DRM_I2C_ADV7511=y
 CONFIG_DRM_I2C_ADV7511_AUDIO=y
 CONFIG_FB_SH_MOBILE_LCDC=y
diff --git a/arch/arm/configs/sunxi_defconfig b/arch/arm/configs/sunxi_defconfig
index df433abfcb02..19cccae84a19 100644
--- a/arch/arm/configs/sunxi_defconfig
+++ b/arch/arm/configs/sunxi_defconfig
@@ -99,7 +99,7 @@ CONFIG_RC_DEVICES=y
 CONFIG_IR_SUNXI=y
 CONFIG_DRM=y
 CONFIG_DRM_SUN4I=y
-CONFIG_DRM_DUMB_VGA_DAC=y
+CONFIG_DRM_SIMPLE_BRIDGE=y
 CONFIG_FB_SIMPLE=y
 CONFIG_SOUND=y
 CONFIG_SND=y
diff --git a/arch/arm/configs/versatile_defconfig b/arch/arm/configs/versatile_defconfig
index 5282324c7cef..afc44c99e7f9 100644
--- a/arch/arm/configs/versatile_defconfig
+++ b/arch/arm/configs/versatile_defconfig
@@ -59,7 +59,7 @@ CONFIG_GPIO_PL061=y
 CONFIG_DRM=y
 CONFIG_DRM_PANEL_ARM_VERSATILE=y
 CONFIG_DRM_PANEL_SIMPLE=y
-CONFIG_DRM_DUMB_VGA_DAC=y
+CONFIG_DRM_SIMPLE_BRIDGE=y
 CONFIG_DRM_PL111=y
 CONFIG_FB_MODE_HELPERS=y
 CONFIG_BACKLIGHT_LCD_SUPPORT=y
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index ee777469293a..a78392e2dbb9 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -37,14 +37,6 @@ config DRM_CDNS_DSI
 	  Support Cadence DPI to DSI bridge. This is an internal
 	  bridge and is meant to be directly embedded in a SoC.
 
-config DRM_DUMB_VGA_DAC
-	tristate "Dumb VGA DAC Bridge support"
-	depends on OF
-	select DRM_KMS_HELPER
-	help
-	  Support for non-programmable RGB to VGA DAC bridges, such as ADI
-	  ADV7123, TI THS8134 and THS8135 or passive resistor ladder DACs.
-
 config DRM_LVDS_ENCODER
 	tristate "Transparent parallel to LVDS encoder support"
 	depends on OF
@@ -108,6 +100,14 @@ config DRM_SII9234
 	  It is an I2C driver, that detects connection of MHL bridge
 	  and starts encapsulation of HDMI signal.
 
+config DRM_SIMPLE_BRIDGE
+	tristate "Simple DRM bridge support"
+	depends on OF
+	select DRM_KMS_HELPER
+	help
+	  Support for non-programmable DRM bridges, such as ADI ADV7123, TI
+	  THS8134 and THS8135 or passive resistor ladder DACs.
+
 config DRM_THINE_THC63LVD1024
 	tristate "Thine THC63LVD1024 LVDS decoder bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 4934fcf5a6f8..6ff7f2adbb0e 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,7 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
-obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
 obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
 obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
@@ -9,6 +8,7 @@ obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
 obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
 obj-$(CONFIG_DRM_SII902X) += sii902x.o
 obj-$(CONFIG_DRM_SII9234) += sii9234.o
+obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
 obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
 obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
 obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/simple-bridge.c
similarity index 99%
rename from drivers/gpu/drm/bridge/dumb-vga-dac.c
rename to drivers/gpu/drm/bridge/simple-bridge.c
index d46e461ae039..da5479bd5878 100644
--- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -286,7 +286,7 @@ static struct platform_driver simple_bridge_driver = {
 	.probe	= simple_bridge_probe,
 	.remove	= simple_bridge_remove,
 	.driver		= {
-		.name		= "dumb-vga-dac",
+		.name		= "simple-bridge",
 		.of_match_table	= simple_bridge_match,
 	},
 };
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-09 14:08     ` Andrzej Hajda
  2019-07-26 13:24     ` Stefan Agner
  2019-07-07 18:18   ` [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO Laurent Pinchart
                     ` (55 subsequent siblings)
  56 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Create a new simple_bridge_info structure that stores information about
the bridge model, and store the bridge timings in there, along with the
connector type. Use that new structure for of_device_id data. This
enables support for non-VGA bridges.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/simple-bridge.c | 41 ++++++++++++++++++--------
 1 file changed, 29 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
index da5479bd5878..bff240cf283d 100644
--- a/drivers/gpu/drm/bridge/simple-bridge.c
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -16,10 +16,17 @@
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 
+struct simple_bridge_info {
+	const struct drm_bridge_timings *timings;
+	unsigned int type;
+};
+
 struct simple_bridge {
 	struct drm_bridge	bridge;
 	struct drm_connector	connector;
 
+	const struct simple_bridge_info *info;
+
 	struct i2c_adapter	*ddc;
 	struct regulator	*vdd;
 };
@@ -113,7 +120,7 @@ static int simple_bridge_attach(struct drm_bridge *bridge)
 				 &simple_bridge_con_helper_funcs);
 	ret = drm_connector_init(bridge->dev, &sbridge->connector,
 				 &simple_bridge_con_funcs,
-				 DRM_MODE_CONNECTOR_VGA);
+				 sbridge->info->type);
 	if (ret) {
 		DRM_ERROR("Failed to initialize connector\n");
 		return ret;
@@ -182,6 +189,8 @@ static int simple_bridge_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	platform_set_drvdata(pdev, sbridge);
 
+	sbridge->info = of_device_get_match_data(&pdev->dev);
+
 	sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
 	if (IS_ERR(sbridge->vdd)) {
 		int ret = PTR_ERR(sbridge->vdd);
@@ -204,7 +213,7 @@ static int simple_bridge_probe(struct platform_device *pdev)
 
 	sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
 	sbridge->bridge.of_node = pdev->dev.of_node;
-	sbridge->bridge.timings = of_device_get_match_data(&pdev->dev);
+	sbridge->bridge.timings = sbridge->info->timings;
 
 	drm_bridge_add(&sbridge->bridge);
 
@@ -264,19 +273,27 @@ static const struct drm_bridge_timings ti_ths8135_bridge_timings = {
 static const struct of_device_id simple_bridge_match[] = {
 	{
 		.compatible = "dumb-vga-dac",
-		.data = NULL,
-	},
-	{
+		.data = &(const struct simple_bridge_info) {
+			.type = DRM_MODE_CONNECTOR_VGA,
+		},
+	}, {
 		.compatible = "adi,adv7123",
-		.data = &default_bridge_timings,
-	},
-	{
+		.data = &(const struct simple_bridge_info) {
+			.timings = &default_bridge_timings,
+			.type = DRM_MODE_CONNECTOR_VGA,
+		},
+	}, {
 		.compatible = "ti,ths8135",
-		.data = &ti_ths8135_bridge_timings,
-	},
-	{
+		.data = &(const struct simple_bridge_info) {
+			.timings = &ti_ths8135_bridge_timings,
+			.type = DRM_MODE_CONNECTOR_VGA,
+		},
+	}, {
 		.compatible = "ti,ths8134",
-		.data = &ti_ths8134_bridge_timings,
+		.data = &(const struct simple_bridge_info) {
+			.timings = &ti_ths8134_bridge_timings,
+			.type = DRM_MODE_CONNECTOR_VGA,
+		},
 	},
 	{},
 };
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-09 14:32     ` Andrzej Hajda
  2019-07-26 13:19     ` Stefan Agner
  2019-07-07 18:18   ` [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362 Laurent Pinchart
                     ` (54 subsequent siblings)
  56 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

If an enable GPIO is declared in the firmware, assert it when enabling
the bridge and deassert it when disabling it.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/simple-bridge.c | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
index bff240cf283d..a7edf3c39627 100644
--- a/drivers/gpu/drm/bridge/simple-bridge.c
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -6,6 +6,7 @@
  * Maxime Ripard <maxime.ripard@free-electrons.com>
  */
 
+#include <linux/gpio/consumer.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/of_graph.h>
@@ -29,6 +30,7 @@ struct simple_bridge {
 
 	struct i2c_adapter	*ddc;
 	struct regulator	*vdd;
+	struct gpio_desc	*enable;
 };
 
 static inline struct simple_bridge *
@@ -135,19 +137,23 @@ static int simple_bridge_attach(struct drm_bridge *bridge)
 static void simple_bridge_enable(struct drm_bridge *bridge)
 {
 	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
-	int ret = 0;
+	int ret;
 
-	if (sbridge->vdd)
+	if (sbridge->vdd) {
 		ret = regulator_enable(sbridge->vdd);
+		if (ret)
+			DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
+	}
 
-	if (ret)
-		DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
+	gpiod_set_value_cansleep(sbridge->enable, 1);
 }
 
 static void simple_bridge_disable(struct drm_bridge *bridge)
 {
 	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
 
+	gpiod_set_value_cansleep(sbridge->enable, 0);
+
 	if (sbridge->vdd)
 		regulator_disable(sbridge->vdd);
 }
@@ -200,6 +206,14 @@ static int simple_bridge_probe(struct platform_device *pdev)
 		dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
 	}
 
+	sbridge->enable = devm_gpiod_get_optional(&pdev->dev, "enable",
+						  GPIOD_OUT_LOW);
+	if (IS_ERR(sbridge->enable)) {
+		if (PTR_ERR(sbridge->enable) != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "Unable to retrieve enable GPIO\n");
+		return PTR_ERR(sbridge->enable);
+	}
+
 	sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
 	if (IS_ERR(sbridge->ddc)) {
 		if (PTR_ERR(sbridge->ddc) == -ENODEV) {
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-09 14:32     ` Andrzej Hajda
  2019-08-27  6:16     ` Tomi Valkeinen
  2019-07-07 18:18   ` [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation Laurent Pinchart
                     ` (53 subsequent siblings)
  56 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The TI OP362 is an analog video amplifier controlled through a GPIO. Add
support for it to the simple-bridge driver.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/simple-bridge.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
index a7edf3c39627..7495b9bef865 100644
--- a/drivers/gpu/drm/bridge/simple-bridge.c
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -296,6 +296,11 @@ static const struct of_device_id simple_bridge_match[] = {
 			.timings = &default_bridge_timings,
 			.type = DRM_MODE_CONNECTOR_VGA,
 		},
+	}, {
+		.compatible = "ti,opa362",
+		.data = &(const struct simple_bridge_info) {
+			.type = DRM_MODE_CONNECTOR_Composite,
+		},
 	}, {
 		.compatible = "ti,ths8135",
 		.data = &(const struct simple_bridge_info) {
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (2 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362 Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-17  6:39     ` Andrzej Hajda
  2019-07-07 18:18   ` [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data Laurent Pinchart
                     ` (52 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Most bridge drivers create a DRM connector to model the connector at the
output of the bridge. This model is historical and has worked pretty
well so far, but causes several issues:

- It prevents supporting more complex display pipelines where DRM
connector operations are split over multiple components. For instance a
pipeline with a bridge connected to the DDC signals to read EDID data,
and another one connected to the HPD signal to detect connection and
disconnection, will not be possible to support through this model.

- It requires every bridge driver to implement similar connector
handling code, resulting in code duplication.

- It assumes that a bridge will either be wired to a connector or to
another bridge, but doesn't support bridges that can be used in both
positions very well (although there is some ad-hoc support for this in
the analogix_dp bridge driver).

In order to solve these issues, ownership of the connector should be
moved to the display controller driver (where it can be implemented
using helpers provided by the core).

Extend the bridge API to allow disabling connector creation in bridge
drivers as a first step towards the new model. The new create_connector
argument to the bridge .attach() operation tells the bridge driver
whether to create a connector. Set the argument to true unconditionally,
and modify all existing bridge drivers to return an error when connector
creation is not requested as they don't support this feature yet.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
 drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
 drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
 drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
 drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
 drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
 drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
 drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
 drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
 drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
 drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
 drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
 drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
 drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
 drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
 drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
 drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
 drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
 drivers/gpu/drm/drm_bridge.c                             | 5 +++--
 drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
 drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
 drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
 drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
 drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
 drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
 drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
 drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
 drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
 drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
 drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
 drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
 drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
 drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
 drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
 drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
 drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
 drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
 drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
 drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
 drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
 drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
 drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
 drivers/gpu/drm/stm/ltdc.c                               | 2 +-
 drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
 drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
 drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
 drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
 drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
 include/drm/drm_bridge.h                                 | 4 ++--
 53 files changed, 140 insertions(+), 67 deletions(-)

diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
index 98aac743cc26..739f2358f1d5 100644
--- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
+++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
@@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
 		return ret;
 
 	/* Link drm_bridge to encoder */
-	ret = drm_bridge_attach(encoder, bridge, NULL);
+	ret = drm_bridge_attach(encoder, bridge, NULL, true);
 	if (ret)
 		drm_encoder_cleanup(encoder);
 
diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
index f73d8a92274e..606841d2c0b0 100644
--- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
+++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
@@ -123,7 +123,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
 	}
 
 	if (bridge) {
-		ret = drm_bridge_attach(&output->encoder, bridge, NULL);
+		ret = drm_bridge_attach(&output->encoder, bridge, NULL, true);
 		if (!ret)
 			return 0;
 
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index f6d2681f6927..c67ba30edec4 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -847,11 +847,15 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
 	adv7511_mode_set(adv, mode, adj_mode);
 }
 
-static int adv7511_bridge_attach(struct drm_bridge *bridge)
+static int adv7511_bridge_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct adv7511 *adv = bridge_to_adv7511(bridge);
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Parent encoder object not found");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix-anx78xx.c
index 3c7cc5af735c..f72755e59e12 100644
--- a/drivers/gpu/drm/bridge/analogix-anx78xx.c
+++ b/drivers/gpu/drm/bridge/analogix-anx78xx.c
@@ -998,11 +998,15 @@ static const struct drm_connector_funcs anx78xx_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int anx78xx_bridge_attach(struct drm_bridge *bridge)
+static int anx78xx_bridge_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
 	int err;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Parent encoder object not found");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 3f7f4880be09..f6a1bdcc09d6 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1179,13 +1179,17 @@ static const struct drm_connector_funcs analogix_dp_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int analogix_dp_bridge_attach(struct drm_bridge *bridge)
+static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
+				     bool create_connector)
 {
 	struct analogix_dp_device *dp = bridge->driver_private;
 	struct drm_encoder *encoder = dp->encoder;
 	struct drm_connector *connector = NULL;
 	int ret = 0;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Parent encoder object not found");
 		return -ENODEV;
@@ -1463,7 +1467,7 @@ static int analogix_dp_create_bridge(struct drm_device *drm_dev,
 	bridge->driver_private = dp;
 	bridge->funcs = &analogix_dp_bridge_funcs;
 
-	ret = drm_bridge_attach(dp->encoder, bridge, NULL);
+	ret = drm_bridge_attach(dp->encoder, bridge, NULL, true);
 	if (ret) {
 		DRM_ERROR("failed to attach drm bridge\n");
 		return -EINVAL;
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c
index 6166dca6be81..45f50852cfbb 100644
--- a/drivers/gpu/drm/bridge/cdns-dsi.c
+++ b/drivers/gpu/drm/bridge/cdns-dsi.c
@@ -645,7 +645,8 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
 	return 0;
 }
 
-static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
+static int cdns_dsi_bridge_attach(struct drm_bridge *bridge,
+				  bool create_connector)
 {
 	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
 	struct cdns_dsi *dsi = input_to_dsi(input);
@@ -657,7 +658,8 @@ static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
 		return -ENOTSUPP;
 	}
 
-	return drm_bridge_attach(bridge->encoder, output->bridge, bridge);
+	return drm_bridge_attach(bridge->encoder, output->bridge, bridge,
+				 create_connector);
 }
 
 static enum drm_mode_status
diff --git a/drivers/gpu/drm/bridge/lvds-encoder.c b/drivers/gpu/drm/bridge/lvds-encoder.c
index 2ab2c234f26c..bafab97521af 100644
--- a/drivers/gpu/drm/bridge/lvds-encoder.c
+++ b/drivers/gpu/drm/bridge/lvds-encoder.c
@@ -18,14 +18,14 @@ struct lvds_encoder {
 	struct gpio_desc *powerdown_gpio;
 };
 
-static int lvds_encoder_attach(struct drm_bridge *bridge)
+static int lvds_encoder_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct lvds_encoder *lvds_encoder = container_of(bridge,
 							 struct lvds_encoder,
 							 bridge);
 
 	return drm_bridge_attach(bridge->encoder, lvds_encoder->panel_bridge,
-				 bridge);
+				 bridge, create_connector);
 }
 
 static void lvds_encoder_enable(struct drm_bridge *bridge)
diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
index 79311f8354bd..4250e2235f50 100644
--- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
+++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
@@ -206,13 +206,17 @@ static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static int ge_b850v3_lvds_attach(struct drm_bridge *bridge)
+static int ge_b850v3_lvds_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector;
 	struct i2c_client *stdp4028_i2c
 			= ge_b850v3_lvds_ptr->stdp4028_i2c;
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Parent encoder object not found");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c
index 98bc650b8c95..6bef439261da 100644
--- a/drivers/gpu/drm/bridge/nxp-ptn3460.c
+++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c
@@ -238,11 +238,15 @@ static const struct drm_connector_funcs ptn3460_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int ptn3460_bridge_attach(struct drm_bridge *bridge)
+static int ptn3460_bridge_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge);
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Parent encoder object not found");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index b12ae3a4c5f1..98ad4abf2409 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -52,12 +52,15 @@ static const struct drm_connector_funcs panel_bridge_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int panel_bridge_attach(struct drm_bridge *bridge)
+static int panel_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
 	struct drm_connector *connector = &panel_bridge->connector;
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Missing encoder\n");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c
index 2d88146e4836..b9243d51489b 100644
--- a/drivers/gpu/drm/bridge/parade-ps8622.c
+++ b/drivers/gpu/drm/bridge/parade-ps8622.c
@@ -476,11 +476,14 @@ static const struct drm_connector_funcs ps8622_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int ps8622_attach(struct drm_bridge *bridge)
+static int ps8622_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Parent encoder object not found");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index dd7aa466b280..18904b1082b6 100644
--- a/drivers/gpu/drm/bridge/sii902x.c
+++ b/drivers/gpu/drm/bridge/sii902x.c
@@ -396,12 +396,16 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
 	mutex_unlock(&sii902x->mutex);
 }
 
-static int sii902x_bridge_attach(struct drm_bridge *bridge)
+static int sii902x_bridge_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct sii902x *sii902x = bridge_to_sii902x(bridge);
 	struct drm_device *drm = bridge->dev;
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	drm_connector_helper_add(&sii902x->connector,
 				 &sii902x_connector_helper_funcs);
 
diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
index 0cc293a6ac24..ea6529df7d9c 100644
--- a/drivers/gpu/drm/bridge/sil-sii8620.c
+++ b/drivers/gpu/drm/bridge/sil-sii8620.c
@@ -2203,7 +2203,7 @@ static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
 	return container_of(bridge, struct sii8620, bridge);
 }
 
-static int sii8620_attach(struct drm_bridge *bridge)
+static int sii8620_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct sii8620 *ctx = bridge_to_sii8620(bridge);
 
diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
index 7495b9bef865..86885eb6e28d 100644
--- a/drivers/gpu/drm/bridge/simple-bridge.c
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -108,11 +108,15 @@ static const struct drm_connector_funcs simple_bridge_con_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 };
 
-static int simple_bridge_attach(struct drm_bridge *bridge)
+static int simple_bridge_attach(struct drm_bridge *bridge,
+				bool create_connector)
 {
 	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
 	int ret;
 
+	if (!create_connector)
+		return 0;
+
 	if (!bridge->encoder) {
 		DRM_ERROR("Missing encoder\n");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index c6490949d9db..930d67c618dd 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2174,12 +2174,16 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
 	.get_modes = dw_hdmi_connector_get_modes,
 };
 
-static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
+static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct dw_hdmi *hdmi = bridge->driver_private;
 	struct drm_encoder *encoder = bridge->encoder;
 	struct drm_connector *connector = &hdmi->connector;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	connector->interlace_allowed = 1;
 	connector->polled = DRM_CONNECTOR_POLL_HPD;
 
@@ -2857,7 +2861,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
 	if (IS_ERR(hdmi))
 		return hdmi;
 
-	ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL);
+	ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, true);
 	if (ret) {
 		dw_hdmi_remove(hdmi);
 		DRM_ERROR("Failed to initialize bridge with drm\n");
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index 281c58bab1a1..05cf97ad524f 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -906,7 +906,8 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
 	return mode_status;
 }
 
-static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
+static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
+				     bool create_connector)
 {
 	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
 
@@ -919,7 +920,8 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
 	bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
 
 	/* Attach the panel-bridge to the dsi bridge */
-	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
+	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
+				 create_connector);
 }
 
 static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
@@ -1064,7 +1066,7 @@ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
 {
 	int ret;
 
-	ret = drm_bridge_attach(encoder, &dsi->bridge, NULL);
+	ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, true);
 	if (ret) {
 		DRM_ERROR("Failed to initialize bridge with drm\n");
 		return ret;
diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
index 170f162ffa55..6016f3aae42f 100644
--- a/drivers/gpu/drm/bridge/tc358764.c
+++ b/drivers/gpu/drm/bridge/tc358764.c
@@ -348,12 +348,15 @@ static void tc358764_enable(struct drm_bridge *bridge)
 		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
 }
 
-static int tc358764_attach(struct drm_bridge *bridge)
+static int tc358764_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct tc358764 *ctx = bridge_to_tc358764(bridge);
 	struct drm_device *drm = bridge->dev;
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
 	ret = drm_connector_init(drm, &ctx->connector,
 				 &tc358764_connector_funcs,
diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
index 13ade28a36a8..e2b2d2660adc 100644
--- a/drivers/gpu/drm/bridge/tc358767.c
+++ b/drivers/gpu/drm/bridge/tc358767.c
@@ -1273,13 +1273,16 @@ static const struct drm_connector_funcs tc_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int tc_bridge_attach(struct drm_bridge *bridge)
+static int tc_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
 	struct tc_data *tc = bridge_to_tc(bridge);
 	struct drm_device *drm = bridge->dev;
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	/* Create DP/eDP connector */
 	drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
 	ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,
diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c
index 3d74129b2995..86f3b96f95db 100644
--- a/drivers/gpu/drm/bridge/thc63lvd1024.c
+++ b/drivers/gpu/drm/bridge/thc63lvd1024.c
@@ -42,11 +42,12 @@ static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
 	return container_of(bridge, struct thc63_dev, bridge);
 }
 
-static int thc63_attach(struct drm_bridge *bridge)
+static int thc63_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct thc63_dev *thc63 = to_thc63(bridge);
 
-	return drm_bridge_attach(bridge->encoder, thc63->next, bridge);
+	return drm_bridge_attach(bridge->encoder, thc63->next, bridge,
+				 create_connector);
 }
 
 static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index b77a52d05061..dbe265f7112d 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -224,7 +224,7 @@ static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
 				       pdata->supplies);
 }
 
-static int ti_sn_bridge_attach(struct drm_bridge *bridge)
+static int ti_sn_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	int ret, val;
 	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
@@ -235,6 +235,9 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
 						   .node = NULL,
 						 };
 
+	if (!create_connector)
+		return -EINVAL;
+
 	ret = drm_connector_init(bridge->dev, &pdata->connector,
 				 &ti_sn_bridge_connector_funcs,
 				 DRM_MODE_CONNECTOR_eDP);
diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index 4e76b2b27374..8d4690e436c3 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -121,11 +121,14 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 };
 
-static int tfp410_attach(struct drm_bridge *bridge)
+static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	if (!bridge->encoder) {
 		dev_err(dvi->dev, "Missing encoder\n");
 		return -ENODEV;
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index cba537c99e43..519577f363e3 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -95,6 +95,7 @@ EXPORT_SYMBOL(drm_bridge_remove);
  * @encoder: DRM encoder
  * @bridge: bridge to attach
  * @previous: previous bridge in the chain (optional)
+ * @create_connector: true if the bridge should create a drm_connector
  *
  * Called by a kms driver to link the bridge to an encoder's chain. The previous
  * argument specifies the previous bridge in the chain. If NULL, the bridge is
@@ -112,7 +113,7 @@ EXPORT_SYMBOL(drm_bridge_remove);
  * Zero on success, error code on failure
  */
 int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
-		      struct drm_bridge *previous)
+		      struct drm_bridge *previous, bool create_connector)
 {
 	int ret;
 
@@ -129,7 +130,7 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
 	bridge->encoder = encoder;
 
 	if (bridge->funcs->attach) {
-		ret = bridge->funcs->attach(bridge);
+		ret = bridge->funcs->attach(bridge, create_connector);
 		if (ret < 0) {
 			bridge->dev = NULL;
 			bridge->encoder = NULL;
diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c
index b11910f14c46..a367ef1e5081 100644
--- a/drivers/gpu/drm/drm_simple_kms_helper.c
+++ b/drivers/gpu/drm/drm_simple_kms_helper.c
@@ -228,7 +228,7 @@ static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
 int drm_simple_display_pipe_attach_bridge(struct drm_simple_display_pipe *pipe,
 					  struct drm_bridge *bridge)
 {
-	return drm_bridge_attach(&pipe->encoder, bridge, NULL);
+	return drm_bridge_attach(&pipe->encoder, bridge, NULL, true);
 }
 EXPORT_SYMBOL(drm_simple_display_pipe_attach_bridge);
 
diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index 3a0f0ba8c63a..02b98e6ca52d 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -105,7 +105,8 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
 
 	/* Pre-empt DP connector creation if there's a bridge */
 	if (dp->ptn_bridge) {
-		ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge);
+		ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge,
+					true);
 		if (ret) {
 			DRM_DEV_ERROR(dp->dev,
 				      "Failed to attach bridge to drm\n");
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
index 5f6f523821a2..768acc79d10c 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
@@ -1522,7 +1522,7 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
 
 	out_bridge  = of_drm_find_bridge(device->dev.of_node);
 	if (out_bridge) {
-		drm_bridge_attach(encoder, out_bridge, NULL);
+		drm_bridge_attach(encoder, out_bridge, NULL, true);
 		dsi->out_bridge = out_bridge;
 		encoder->bridge = NULL;
 	} else {
@@ -1698,7 +1698,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master,
 	if (dsi->in_bridge_node) {
 		in_bridge = of_drm_find_bridge(dsi->in_bridge_node);
 		if (in_bridge)
-			drm_bridge_attach(encoder, in_bridge, NULL);
+			drm_bridge_attach(encoder, in_bridge, NULL, true);
 	}
 
 	return mipi_dsi_host_register(&dsi->dsi_host);
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
index bc1565f1822a..808c98101d56 100644
--- a/drivers/gpu/drm/exynos/exynos_hdmi.c
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
@@ -952,7 +952,7 @@ static int hdmi_create_connector(struct drm_encoder *encoder)
 	drm_connector_attach_encoder(connector, encoder);
 
 	if (hdata->bridge) {
-		ret = drm_bridge_attach(encoder, hdata->bridge, NULL);
+		ret = drm_bridge_attach(encoder, hdata->bridge, NULL, true);
 		if (ret)
 			DRM_DEV_ERROR(hdata->dev, "Failed to attach bridge\n");
 	}
diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
index c49e9e3740f8..8d22618ccf2e 100644
--- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
@@ -159,5 +159,5 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
 		return fsl_dcu_attach_panel(fsl_dev, panel);
 	}
 
-	return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL);
+	return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, true);
 }
diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
index 3d6c45097f51..eac8ec1512ab 100644
--- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
+++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
@@ -780,7 +780,7 @@ static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
 	int ret;
 
 	/* associate the bridge to dsi encoder */
-	ret = drm_bridge_attach(encoder, bridge, NULL);
+	ret = drm_bridge_attach(encoder, bridge, NULL, true);
 	if (ret) {
 		DRM_ERROR("failed to attach external bridge\n");
 		return ret;
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index 3d368c43185f..6b2e648b6c4d 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -1366,10 +1366,14 @@ static int tda998x_connector_init(struct tda998x_priv *priv,
 
 /* DRM bridge functions */
 
-static int tda998x_bridge_attach(struct drm_bridge *bridge)
+static int tda998x_bridge_attach(struct drm_bridge *bridge,
+				 bool create_connector)
 {
 	struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge);
 
+	if (!create_connector)
+		return -EINVAL;
+
 	return tda998x_connector_init(priv, bridge->dev);
 }
 
@@ -2033,7 +2037,7 @@ static int tda998x_encoder_init(struct device *dev, struct drm_device *drm)
 	if (ret)
 		goto err_encoder;
 
-	ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL);
+	ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL, true);
 	if (ret)
 		goto err_bridge;
 
diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
index 383733302280..845ead25ade7 100644
--- a/drivers/gpu/drm/imx/imx-ldb.c
+++ b/drivers/gpu/drm/imx/imx-ldb.c
@@ -446,7 +446,7 @@ static int imx_ldb_register(struct drm_device *drm,
 
 	if (imx_ldb_ch->bridge) {
 		ret = drm_bridge_attach(&imx_ldb_ch->encoder,
-					imx_ldb_ch->bridge, NULL);
+					imx_ldb_ch->bridge, NULL, true);
 		if (ret) {
 			DRM_ERROR("Failed to initialize bridge with drm\n");
 			return ret;
diff --git a/drivers/gpu/drm/imx/parallel-display.c b/drivers/gpu/drm/imx/parallel-display.c
index 1a76de1e8e7b..cd746592d2a7 100644
--- a/drivers/gpu/drm/imx/parallel-display.c
+++ b/drivers/gpu/drm/imx/parallel-display.c
@@ -182,7 +182,7 @@ static int imx_pd_register(struct drm_device *drm,
 		drm_panel_attach(imxpd->panel, &imxpd->connector);
 
 	if (imxpd->bridge) {
-		ret = drm_bridge_attach(encoder, imxpd->bridge, NULL);
+		ret = drm_bridge_attach(encoder, imxpd->bridge, NULL, true);
 		if (ret < 0) {
 			dev_err(imxpd->dev, "failed to attach bridge: %d\n",
 				ret);
diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
index 07f7090d08b3..f2deadc980f8 100644
--- a/drivers/gpu/drm/mcde/mcde_dsi.c
+++ b/drivers/gpu/drm/mcde/mcde_dsi.c
@@ -817,12 +817,16 @@ mcde_dsi_connector_helper_funcs = {
 	.get_modes = mcde_dsi_get_modes,
 };
 
-static int mcde_dsi_bridge_attach(struct drm_bridge *bridge)
+static int mcde_dsi_bridge_attach(struct drm_bridge *bridge,
+				  bool create_connector)
 {
 	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
 	struct drm_device *drm = bridge->dev;
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	drm_connector_helper_add(&d->connector,
 				 &mcde_dsi_connector_helper_funcs);
 
diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c
index bacd989cc9aa..1ff27bb17016 100644
--- a/drivers/gpu/drm/mediatek/mtk_dpi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dpi.c
@@ -604,7 +604,7 @@ static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
 	/* Currently DPI0 is fixed to be driven by OVL1 */
 	dpi->encoder.possible_crtcs = BIT(1);
 
-	ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL);
+	ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL, true);
 	if (ret) {
 		dev_err(dev, "Failed to attach bridge: %d\n", ret);
 		goto err_cleanup;
diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index b91c4616644a..9c5bac48b44f 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -819,7 +819,7 @@ static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
 
 	/* If there's a bridge, attach to it and let it create the connector */
 	if (dsi->bridge) {
-		ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL);
+		ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL, true);
 		if (ret) {
 			DRM_ERROR("Failed to attach bridge to drm\n");
 			goto err_encoder_cleanup;
diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
index 5d6a9f094df5..d3248a881cf0 100644
--- a/drivers/gpu/drm/mediatek/mtk_hdmi.c
+++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
@@ -1290,11 +1290,15 @@ static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
  * Bridge callbacks
  */
 
-static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
+static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge,
+				  bool create_connector)
 {
 	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
 	int ret;
 
+	if (!create_connector)
+		return -EINVAL;
+
 	ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn,
 				 &mtk_hdmi_connector_funcs,
 				 DRM_MODE_CONNECTOR_HDMIA);
@@ -1318,7 +1322,7 @@ static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
 
 	if (hdmi->next_bridge) {
 		ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
-					bridge);
+					bridge, create_connector);
 		if (ret) {
 			dev_err(hdmi->dev,
 				"Failed to attach external bridge: %d\n", ret);
diff --git a/drivers/gpu/drm/msm/dsi/dsi_manager.c b/drivers/gpu/drm/msm/dsi/dsi_manager.c
index 271aa7bbca92..ca733086041a 100644
--- a/drivers/gpu/drm/msm/dsi/dsi_manager.c
+++ b/drivers/gpu/drm/msm/dsi/dsi_manager.c
@@ -664,7 +664,7 @@ struct drm_bridge *msm_dsi_manager_bridge_init(u8 id)
 	bridge = &dsi_bridge->base;
 	bridge->funcs = &dsi_mgr_bridge_funcs;
 
-	ret = drm_bridge_attach(encoder, bridge, NULL);
+	ret = drm_bridge_attach(encoder, bridge, NULL, true);
 	if (ret)
 		goto fail;
 
@@ -693,7 +693,7 @@ struct drm_connector *msm_dsi_manager_ext_bridge_init(u8 id)
 	encoder = msm_dsi->encoder;
 
 	/* link the internal dsi bridge to the external bridge */
-	drm_bridge_attach(encoder, ext_bridge, int_bridge);
+	drm_bridge_attach(encoder, ext_bridge, int_bridge, true);
 
 	/*
 	 * we need the drm_connector created by the external bridge
diff --git a/drivers/gpu/drm/msm/edp/edp_bridge.c b/drivers/gpu/drm/msm/edp/edp_bridge.c
index 2950bba4aca9..32a463c84cc1 100644
--- a/drivers/gpu/drm/msm/edp/edp_bridge.c
+++ b/drivers/gpu/drm/msm/edp/edp_bridge.c
@@ -91,7 +91,7 @@ struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp)
 	bridge = &edp_bridge->base;
 	bridge->funcs = &edp_bridge_funcs;
 
-	ret = drm_bridge_attach(edp->encoder, bridge, NULL);
+	ret = drm_bridge_attach(edp->encoder, bridge, NULL, true);
 	if (ret)
 		goto fail;
 
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
index 03197b8959ba..d7738aafcff8 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
@@ -296,7 +296,7 @@ struct drm_bridge *msm_hdmi_bridge_init(struct hdmi *hdmi)
 	bridge = &hdmi_bridge->base;
 	bridge->funcs = &msm_hdmi_bridge_funcs;
 
-	ret = drm_bridge_attach(hdmi->encoder, bridge, NULL);
+	ret = drm_bridge_attach(hdmi->encoder, bridge, NULL, true);
 	if (ret)
 		goto fail;
 
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 672e0f8ad11c..837d0cd20dd1 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -301,7 +301,8 @@ static int omap_modeset_init(struct drm_device *dev)
 
 		if (pipe->output->bridge) {
 			ret = drm_bridge_attach(pipe->encoder,
-						pipe->output->bridge, NULL);
+						pipe->output->bridge, NULL,
+						true);
 			if (ret < 0)
 				return ret;
 		}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
index 0f00bdfe2366..74c2ae5ce687 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
@@ -120,7 +120,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
 	 * Attach the bridge to the encoder. The bridge will create the
 	 * connector.
 	 */
-	ret = drm_bridge_attach(encoder, bridge, NULL);
+	ret = drm_bridge_attach(encoder, bridge, NULL, true);
 	if (ret) {
 		drm_encoder_cleanup(encoder);
 		return ret;
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
index 1c62578590f4..a8d8b05c4731 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
@@ -605,7 +605,7 @@ static void rcar_lvds_mode_set(struct drm_bridge *bridge,
 	rcar_lvds_get_lvds_mode(lvds);
 }
 
-static int rcar_lvds_attach(struct drm_bridge *bridge)
+static int rcar_lvds_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
 	struct drm_connector *connector = &lvds->connector;
@@ -615,7 +615,10 @@ static int rcar_lvds_attach(struct drm_bridge *bridge)
 	/* If we have a next bridge just attach it. */
 	if (lvds->next_bridge)
 		return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
-					 bridge);
+					 bridge, create_connector);
+
+	if (!create_connector)
+		return -EINVAL;
 
 	/* Otherwise if we have a panel, create a connector. */
 	if (!lvds->panel)
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 830858a809e5..7ca412d294b2 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -440,7 +440,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
 			goto err_free_connector;
 		}
 	} else {
-		ret = drm_bridge_attach(encoder, lvds->bridge, NULL);
+		ret = drm_bridge_attach(encoder, lvds->bridge, NULL, true);
 		if (ret) {
 			DRM_DEV_ERROR(drm_dev->dev,
 				      "failed to attach bridge: %d\n", ret);
diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
index ce4d82d293e4..8218bbd09a72 100644
--- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
@@ -143,7 +143,7 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
 
 	rgb->bridge = bridge;
 
-	ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
+	ret = drm_bridge_attach(encoder, rgb->bridge, NULL, true);
 	if (ret) {
 		DRM_DEV_ERROR(drm_dev->dev,
 			      "failed to attach bridge: %d\n", ret);
diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c
index 9e6d5d8b7030..f09209621568 100644
--- a/drivers/gpu/drm/sti/sti_dvo.c
+++ b/drivers/gpu/drm/sti/sti_dvo.c
@@ -468,7 +468,7 @@ static int sti_dvo_bind(struct device *dev, struct device *master, void *data)
 	bridge->of_node = dvo->dev.of_node;
 	drm_bridge_add(bridge);
 
-	err = drm_bridge_attach(encoder, bridge, NULL);
+	err = drm_bridge_attach(encoder, bridge, NULL, true);
 	if (err) {
 		DRM_ERROR("Failed to attach bridge\n");
 		return err;
diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c
index 94e404f13234..87e0fb742dc8 100644
--- a/drivers/gpu/drm/sti/sti_hda.c
+++ b/drivers/gpu/drm/sti/sti_hda.c
@@ -700,7 +700,7 @@ static int sti_hda_bind(struct device *dev, struct device *master, void *data)
 
 	bridge->driver_private = hda;
 	bridge->funcs = &sti_hda_bridge_funcs;
-	drm_bridge_attach(encoder, bridge, NULL);
+	drm_bridge_attach(encoder, bridge, NULL, true);
 
 	connector->encoder = encoder;
 
diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
index f03d617edc4c..8c0ffe6833f9 100644
--- a/drivers/gpu/drm/sti/sti_hdmi.c
+++ b/drivers/gpu/drm/sti/sti_hdmi.c
@@ -1276,7 +1276,7 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
 
 	bridge->driver_private = hdmi;
 	bridge->funcs = &sti_hdmi_bridge_funcs;
-	drm_bridge_attach(encoder, bridge, NULL);
+	drm_bridge_attach(encoder, bridge, NULL, true);
 
 	connector->encoder = encoder;
 
diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
index 2fe6c4a8d915..10a9f848c5f6 100644
--- a/drivers/gpu/drm/stm/ltdc.c
+++ b/drivers/gpu/drm/stm/ltdc.c
@@ -1053,7 +1053,7 @@ static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge)
 	drm_encoder_init(ddev, encoder, &ltdc_encoder_funcs,
 			 DRM_MODE_ENCODER_DPI, NULL);
 
-	ret = drm_bridge_attach(encoder, bridge, NULL);
+	ret = drm_bridge_attach(encoder, bridge, NULL, true);
 	if (ret) {
 		drm_encoder_cleanup(encoder);
 		return -EINVAL;
diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c
index 3a3ba99fed22..3e5170fa1e67 100644
--- a/drivers/gpu/drm/sun4i/sun4i_lvds.c
+++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c
@@ -155,7 +155,7 @@ int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
 	}
 
 	if (bridge) {
-		ret = drm_bridge_attach(encoder, bridge, NULL);
+		ret = drm_bridge_attach(encoder, bridge, NULL, true);
 		if (ret) {
 			dev_err(drm->dev, "Couldn't attach our bridge\n");
 			goto err_cleanup_connector;
diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
index a901ec689b62..3f8629445fbb 100644
--- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
+++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
@@ -252,7 +252,7 @@ int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon)
 	}
 
 	if (rgb->bridge) {
-		ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
+		ret = drm_bridge_attach(encoder, rgb->bridge, NULL, true);
 		if (ret) {
 			dev_err(drm->dev, "Couldn't attach our bridge\n");
 			goto err_cleanup_connector;
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c
index e9969cd36610..ec693c11e455 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_external.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c
@@ -168,7 +168,7 @@ int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge)
 
 	priv->external_encoder->possible_crtcs = BIT(0);
 
-	ret = drm_bridge_attach(priv->external_encoder, bridge, NULL);
+	ret = drm_bridge_attach(priv->external_encoder, bridge, NULL, true);
 	if (ret) {
 		dev_err(ddev->dev, "drm_bridge_attach() failed %d\n", ret);
 		return ret;
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c
index 34f90ca8f479..2d7c5cf0d468 100644
--- a/drivers/gpu/drm/vc4/vc4_dpi.c
+++ b/drivers/gpu/drm/vc4/vc4_dpi.c
@@ -262,7 +262,7 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
 	if (panel)
 		bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DPI);
 
-	return drm_bridge_attach(dpi->encoder, bridge, NULL);
+	return drm_bridge_attach(dpi->encoder, bridge, NULL, true);
 }
 
 static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c
index 2ea4e20b7b8a..3edd7ffc7383 100644
--- a/drivers/gpu/drm/vc4/vc4_dsi.c
+++ b/drivers/gpu/drm/vc4/vc4_dsi.c
@@ -1607,7 +1607,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
 			 DRM_MODE_ENCODER_DSI, NULL);
 	drm_encoder_helper_add(dsi->encoder, &vc4_dsi_encoder_helper_funcs);
 
-	ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL);
+	ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL, true);
 	if (ret) {
 		dev_err(dev, "bridge attach failed: %d\n", ret);
 		return ret;
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 7616f6562fe4..08dc15f93ded 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -48,7 +48,7 @@ struct drm_bridge_funcs {
 	 *
 	 * Zero on success, error code on failure.
 	 */
-	int (*attach)(struct drm_bridge *bridge);
+	int (*attach)(struct drm_bridge *bridge, bool create_connector);
 
 	/**
 	 * @detach:
@@ -404,7 +404,7 @@ void drm_bridge_add(struct drm_bridge *bridge);
 void drm_bridge_remove(struct drm_bridge *bridge);
 struct drm_bridge *of_drm_find_bridge(struct device_node *np);
 int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
-		      struct drm_bridge *previous);
+		      struct drm_bridge *previous, bool create_connector);
 
 bool drm_bridge_mode_fixup(struct drm_bridge *bridge,
 			   const struct drm_display_mode *mode,
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (3 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-10 12:12     ` Andrzej Hajda
  2019-07-07 18:18   ` [PATCH 10/60] drm/bridge: Add bridge driver for display connectors Laurent Pinchart
                     ` (51 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

To support implementation of DRM connectors on top of DRM bridges
instead of by bridges, the drm_bridge needs to expose new operations and
data:

- Output detection, hot-plug notification, mode retrieval and EDID
  retrieval operations
- Bitmask of supported operations
- Bridge output type

Add and document these.

Three new bridge helper functions are also added to handle hot plug
notification in a way that is as transparent as possible for the
bridges.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
 include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
 2 files changed, 261 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 519577f363e3..3c2a255df7af 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
  */
 void drm_bridge_add(struct drm_bridge *bridge)
 {
+	mutex_init(&bridge->hpd_mutex);
+
 	mutex_lock(&bridge_lock);
 	list_add_tail(&bridge->list, &bridge_list);
 	mutex_unlock(&bridge_lock);
@@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
 	mutex_lock(&bridge_lock);
 	list_del_init(&bridge->list);
 	mutex_unlock(&bridge_lock);
+
+	mutex_destroy(&bridge->hpd_mutex);
 }
 EXPORT_SYMBOL(drm_bridge_remove);
 
@@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
 }
 EXPORT_SYMBOL(drm_atomic_bridge_enable);
 
+/**
+ * drm_bridge_hpd_enable - enable hot plug detection for the bridge
+ * @bridge: bridge control structure
+ * @cb: hot-plug detection callback
+ * @data: data to be passed to the hot-plug detection callback
+ *
+ * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
+ * hot plug notification callback. From now on the @cb will be called with
+ * @data when an output status change is detected by the bridge, until hot plug
+ * notification gets disabled with drm_bridge_hpd_disable().
+ *
+ * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
+ * bridge->ops. This function shall not be called when the flag is not set.
+ *
+ * Only one hot plug detection callback can be registered at a time, it is an
+ * error to call this function when hot plug detection is already enabled for
+ * the bridge.
+ */
+void drm_bridge_hpd_enable(struct drm_bridge *bridge,
+			   void (*cb)(void *data,
+				      enum drm_connector_status status),
+			   void *data)
+{
+	if (!bridge || !bridge->funcs->hpd_enable)
+		return;
+
+	mutex_lock(&bridge->hpd_mutex);
+
+	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
+		goto unlock;
+
+	bridge->hpd_cb = cb;
+	bridge->hpd_data = data;
+
+	bridge->funcs->hpd_enable(bridge);
+
+unlock:
+	mutex_unlock(&bridge->hpd_mutex);
+}
+EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
+
+/**
+ * drm_bridge_hpd_disable - disable hot plug detection for the bridge
+ * @bridge: bridge control structure
+ *
+ * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
+ * callback previously registered with drm_bridge_hpd_enable(). Once this
+ * function returns the callback will not be called by the bridge when an
+ * output status change occurs.
+ *
+ * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
+ * bridge->ops. This function shall not be called when the flag is not set.
+ */
+void drm_bridge_hpd_disable(struct drm_bridge *bridge)
+{
+	if (!bridge || !bridge->funcs->hpd_disable)
+		return;
+
+	mutex_lock(&bridge->hpd_mutex);
+	bridge->funcs->hpd_disable(bridge);
+
+	bridge->hpd_cb = NULL;
+	bridge->hpd_data = NULL;
+	mutex_unlock(&bridge->hpd_mutex);
+}
+EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
+
+/**
+ * drm_bridge_hpd_notify - notify hot plug detection events
+ * @bridge: bridge control structure
+ * @status: output connection status
+ *
+ * Bridge drivers shall call this function to report hot plug events when they
+ * detect a change in the output status, when hot plug detection has been
+ * enabled by the &drm_bridge_funcs.hpd_enable callback.
+ *
+ * This function shall be called in a context that can sleep.
+ */
+void drm_bridge_hpd_notify(struct drm_bridge *bridge,
+			   enum drm_connector_status status)
+{
+	mutex_lock(&bridge->hpd_mutex);
+	if (bridge->hpd_cb)
+		bridge->hpd_cb(bridge->hpd_data, status);
+	mutex_unlock(&bridge->hpd_mutex);
+}
+EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
+
 #ifdef CONFIG_OF
 /**
  * of_drm_find_bridge - find the bridge corresponding to the device node in
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 08dc15f93ded..b9445aa5b1ef 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -23,8 +23,9 @@
 #ifndef __DRM_BRIDGE_H__
 #define __DRM_BRIDGE_H__
 
-#include <linux/list.h>
 #include <linux/ctype.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
 #include <drm/drm_mode_object.h>
 #include <drm/drm_modes.h>
 
@@ -334,6 +335,110 @@ struct drm_bridge_funcs {
 	 */
 	void (*atomic_post_disable)(struct drm_bridge *bridge,
 				    struct drm_atomic_state *state);
+
+	/**
+	 * @detect:
+	 *
+	 * Check if anything is attached to the bridge output.
+	 *
+	 * This callback is optional, if not implemented the bridge will be
+	 * considered as always having a component attached to its output.
+	 * Bridges that implement this callback shall set the
+	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
+	 *
+	 * RETURNS:
+	 *
+	 * drm_connector_status indicating the bridge output status.
+	 */
+	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
+
+	/**
+	 * @get_modes:
+	 *
+	 * Fill all modes currently valid for the sink into the &drm_connector
+	 * with drm_mode_probed_add().
+	 *
+	 * The @get_modes callback is mostly intended to support non-probable
+	 * displays such as many fixed panels. Bridges that support reading
+	 * EDID shall leave @get_modes unimplemented and implement the
+	 * &drm_bridge_funcs->get_edid callback instead.
+	 *
+	 * This callback is optional. Bridges that implement it shall set the
+	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
+	 *
+	 * RETURNS:
+	 *
+	 * The number of modes added by calling drm_mode_probed_add().
+	 */
+	int (*get_modes)(struct drm_bridge *bridge,
+			 struct drm_connector *connector);
+
+	/**
+	 * @get_edid:
+	 *
+	 * Read and parse the EDID data of the connected display.
+	 *
+	 * The @get_edid callback is the preferred way of reporting mode
+	 * information for a display connected to the bridge output. Bridges
+	 * that support readind EDID shall implement this callback and leave
+	 * the @get_modes callback unimplemented.
+	 *
+	 * The caller of this operation shall first verify the output
+	 * connection status and refrain from reading EDID from a disconnected
+	 * output.
+	 *
+	 * This callback is optional. Bridges that implement it shall set the
+	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
+	 *
+	 * RETURNS:
+	 *
+	 * An edid structure newly allocated with kmalloc() (or similar) on
+	 * success, or NULL otherwise. The caller is responsible for freeing
+	 * the returned edid structure with kfree().
+	 */
+	struct edid *(*get_edid)(struct drm_bridge *bridge,
+				 struct drm_connector *connector);
+
+	/**
+	 * @lost_hotplug:
+	 *
+	 * Notify the bridge of display disconnection.
+	 *
+	 * This callback is optional, it may be implemented by bridges that
+	 * need to be notified of display disconnection for internal reasons.
+	 * One use case is to reset the internal state of CEC controllers for
+	 * HDMI bridges.
+	 */
+	void (*lost_hotplug)(struct drm_bridge *bridge);
+
+	/**
+	 * @hpd_enable:
+	 *
+	 * Enable hot plug detection. From now on the bridge shall call
+	 * drm_bridge_hpd_notify() each time a change is detected in the output
+	 * connection status, until hot plug detection gets disabled with
+	 * @hpd_disable.
+	 *
+	 * This callback is optional and shall only be implemented by bridges
+	 * that support hot-plug notification without polling. Bridges that
+	 * implement it shall also implement the @hpd_disable callback and set
+	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
+	 */
+	void (*hpd_enable)(struct drm_bridge *bridge);
+
+	/**
+	 * @hpd_disable:
+	 *
+	 * Disable hot plug detection. Once this function returns the bridge
+	 * shall not call drm_bridge_hpd_notify() when a change in the output
+	 * connection status occurs.
+	 *
+	 * This callback is optional and shall only be implemented by bridges
+	 * that support hot-plug notification without polling. Bridges that
+	 * implement it shall also implement the @hpd_enable callback and set
+	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
+	 */
+	void (*hpd_disable)(struct drm_bridge *bridge);
 };
 
 /**
@@ -372,6 +477,38 @@ struct drm_bridge_timings {
 	bool dual_link;
 };
 
+/**
+ * enum drm_bridge_ops - Bitmask of operations supported by the bridge
+ */
+enum drm_bridge_ops {
+	/**
+	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
+	 * its output. Bridges that set this flag shall implement the
+	 * &drm_bridge_funcs->detect callback.
+	 */
+	DRM_BRIDGE_OP_DETECT = BIT(0),
+	/**
+	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
+	 * connected to its output. Bridges that set this flag shall implement
+	 * the &drm_bridge_funcs->get_edid callback.
+	 */
+	DRM_BRIDGE_OP_EDID = BIT(1),
+	/**
+	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
+	 * without requiring polling. Bridges that set this flag shall
+	 * implement the &drm_bridge_funcs->hpd_enable and
+	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
+	 */
+	DRM_BRIDGE_OP_HPD = BIT(2),
+	/**
+	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
+	 * by the display at its output. This does not include readind EDID
+	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
+	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
+	 */
+	DRM_BRIDGE_OP_MODES = BIT(3),
+};
+
 /**
  * struct drm_bridge - central DRM bridge control structure
  */
@@ -398,6 +535,29 @@ struct drm_bridge {
 	const struct drm_bridge_funcs *funcs;
 	/** @driver_private: pointer to the bridge driver's internal context */
 	void *driver_private;
+	/** @ops: bitmask of operations supported by the bridge */
+	enum drm_bridge_ops ops;
+	/**
+	 * @type: Type of the connection at the bridge output
+	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
+	 * identifies the type of connected display.
+	 */
+	int type;
+	/** private: */
+	/**
+	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
+	 */
+	struct mutex hpd_mutex;
+	/**
+	 * @hpd_cb: Hot plug detection callback, registered with
+	 * drm_bridge_hpd_enable().
+	 */
+	void (*hpd_cb)(void *data, enum drm_connector_status status);
+	/**
+	 * @hpd_data: Private data passed to the Hot plug detection callback
+	 * @hpd_cb.
+	 */
+	void *hpd_data;
 };
 
 void drm_bridge_add(struct drm_bridge *bridge);
@@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
 void drm_atomic_bridge_enable(struct drm_bridge *bridge,
 			      struct drm_atomic_state *state);
 
+void drm_bridge_hpd_enable(struct drm_bridge *bridge,
+			   void (*cb)(void *data,
+				      enum drm_connector_status status),
+			   void *data);
+void drm_bridge_hpd_disable(struct drm_bridge *bridge);
+void drm_bridge_hpd_notify(struct drm_bridge *bridge,
+			   enum drm_connector_status status);
+
 #ifdef CONFIG_DRM_PANEL_BRIDGE
 struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
 					u32 connector_type);
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 10/60] drm/bridge: Add bridge driver for display connectors
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (4 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-16  9:28     ` Sam Ravnborg
  2019-09-30 11:15     ` Tomi Valkeinen
  2019-07-07 18:18   ` [PATCH 11/60] drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter Laurent Pinchart
                     ` (50 subsequent siblings)
  56 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Display connectors are modelled in DT as a device node, but have so far
been handled manually in several bridge drivers. This resulted in
duplicate code in several bridge drivers, with slightly different (and
thus confusing) logics.

In order to fix this, implement a bridge driver for display connectors.
The driver centralises logic for the DVI, HDMI, VGAn composite and
S-video connectors and exposes corresponding bridge operations.

This driver in itself doesn't solve the issue completely, changes in
bridge and display controller drivers are needed to make use of the new
connector driver.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/Kconfig             |  11 +
 drivers/gpu/drm/bridge/Makefile            |   1 +
 drivers/gpu/drm/bridge/display-connector.c | 327 +++++++++++++++++++++
 3 files changed, 339 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/display-connector.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index a78392e2dbb9..295a62f65ef9 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -37,6 +37,17 @@ config DRM_CDNS_DSI
 	  Support Cadence DPI to DSI bridge. This is an internal
 	  bridge and is meant to be directly embedded in a SoC.
 
+config DRM_DISPLAY_CONNECTOR
+	tristate "Display connector support"
+	depends on OF
+	help
+	  Driver for display connectors with support for DDC and hot-plug
+	  detection. Most display controller handle display connectors
+	  internally and don't need this driver, but the DRM subsystem is
+	  moving towards separating connector handling from display controllers
+	  on ARM-based platforms. Saying Y here when this driver is not needed
+	  will not cause any issue.
+
 config DRM_LVDS_ENCODER
 	tristate "Transparent parallel to LVDS encoder support"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 6ff7f2adbb0e..e5987b3aaf62 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
+obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
 obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
 obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
new file mode 100644
index 000000000000..2e1e7ee89275
--- /dev/null
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
+
+struct display_connector {
+	struct drm_bridge	bridge;
+
+	const char		*label;
+	struct i2c_adapter	*ddc;
+	struct gpio_desc	*hpd_gpio;
+	int			hpd_irq;
+};
+
+static inline struct display_connector *
+to_display_connector(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct display_connector, bridge);
+}
+
+static int display_connector_attach(struct drm_bridge *bridge,
+				    bool create_connector)
+{
+	return create_connector ? -EINVAL : 0;
+}
+
+static enum drm_connector_status
+display_connector_detect(struct drm_bridge *bridge)
+{
+	struct display_connector *conn = to_display_connector(bridge);
+
+	if (conn->hpd_gpio) {
+		if (gpiod_get_value_cansleep(conn->hpd_gpio))
+			return connector_status_connected;
+		else
+			return connector_status_disconnected;
+	}
+
+	if (conn->ddc && drm_probe_ddc(conn->ddc))
+		return connector_status_connected;
+
+	switch (conn->bridge.type) {
+	case DRM_MODE_CONNECTOR_DVIA:
+	case DRM_MODE_CONNECTOR_DVID:
+	case DRM_MODE_CONNECTOR_DVII:
+	case DRM_MODE_CONNECTOR_HDMIA:
+	case DRM_MODE_CONNECTOR_HDMIB:
+		/*
+		 * For DVI and HDMI connectors a DDC probe failure indicates
+		 * that no cable is connected.
+		 */
+		return connector_status_disconnected;
+
+	case DRM_MODE_CONNECTOR_Composite:
+	case DRM_MODE_CONNECTOR_SVIDEO:
+	case DRM_MODE_CONNECTOR_VGA:
+	default:
+		/*
+		 * Composite and S-Video connectors have no other detection
+		 * mean than the HPD GPIO. For VGA connectors, even if we have
+		 * an I2C bus, we can't assume that the cable is disconnected
+		 * if drm_probe_ddc fails, as some cables don't wire the DDC
+		 * pins.
+		 */
+		return connector_status_unknown;
+	}
+}
+
+static struct edid *display_connector_get_edid(struct drm_bridge *bridge,
+					       struct drm_connector *connector)
+{
+	struct display_connector *conn = to_display_connector(bridge);
+
+	return drm_get_edid(connector, conn->ddc);
+}
+
+static void display_connector_hpd_enable(struct drm_bridge *bridge)
+{
+}
+
+static void display_connector_hpd_disable(struct drm_bridge *bridge)
+{
+}
+
+static const struct drm_bridge_funcs display_connector_bridge_funcs = {
+	.attach = display_connector_attach,
+	.detect = display_connector_detect,
+	.get_edid = display_connector_get_edid,
+	.hpd_enable = display_connector_hpd_enable,
+	.hpd_disable = display_connector_hpd_disable,
+};
+
+static irqreturn_t display_connector_hpd_irq(int irq, void *arg)
+{
+	struct display_connector *conn = arg;
+	struct drm_bridge *bridge = &conn->bridge;
+
+	drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
+
+	return IRQ_HANDLED;
+}
+
+static const char *display_connector_type_name(struct display_connector *conn)
+{
+	switch (conn->bridge.type) {
+	case DRM_MODE_CONNECTOR_Composite:
+		return "Composite";
+	case DRM_MODE_CONNECTOR_DVIA:
+		return "DVI-A";
+	case DRM_MODE_CONNECTOR_DVID:
+		return "DVI-D";
+	case DRM_MODE_CONNECTOR_DVII:
+		return "DVI-I";
+	case DRM_MODE_CONNECTOR_HDMIA:
+		return "HDMI-A";
+	case DRM_MODE_CONNECTOR_HDMIB:
+		return "HDMI-B";
+	case DRM_MODE_CONNECTOR_SVIDEO:
+		return "S-Video";
+	case DRM_MODE_CONNECTOR_VGA:
+		return "VGA";
+	default:
+		return "unknown";
+	}
+}
+
+static int display_connector_probe(struct platform_device *pdev)
+{
+	struct display_connector *conn;
+	unsigned int type;
+	int ret;
+
+	conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);
+	if (!conn)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, conn);
+
+	type = (uintptr_t)of_device_get_match_data(&pdev->dev);
+
+	/* Get the exact connector type. */
+	switch (type) {
+	case DRM_MODE_CONNECTOR_DVII: {
+		bool analog, digital;
+
+		analog = of_property_read_bool(pdev->dev.of_node, "analog");
+		digital = of_property_read_bool(pdev->dev.of_node, "digital");
+		if (analog && !digital) {
+			conn->bridge.type = DRM_MODE_CONNECTOR_DVIA;
+		} else if (!analog && digital) {
+			conn->bridge.type = DRM_MODE_CONNECTOR_DVID;
+		} else if (analog && digital) {
+			conn->bridge.type = DRM_MODE_CONNECTOR_DVII;
+		} else {
+			dev_err(&pdev->dev, "DVI connector with no type\n");
+			return -EINVAL;
+		}
+		break;
+	}
+
+	case DRM_MODE_CONNECTOR_HDMIA: {
+		const char *hdmi_type;
+
+		ret = of_property_read_string(pdev->dev.of_node, "type",
+					      &hdmi_type);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "HDMI connector with no type\n");
+			return -EINVAL;
+		}
+
+		if (!strcmp(hdmi_type, "a") || !strcmp(hdmi_type, "c") ||
+		    !strcmp(hdmi_type, "d") || !strcmp(hdmi_type, "e")) {
+			conn->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+		} else if (!strcmp(hdmi_type, "b")) {
+			conn->bridge.type = DRM_MODE_CONNECTOR_HDMIB;
+		} else {
+			dev_err(&pdev->dev,
+				"Unsupported HDMI connector type '%s'\n",
+				hdmi_type);
+			return -EINVAL;
+		}
+
+		break;
+	}
+
+	default:
+		conn->bridge.type = type;
+		break;
+	}
+
+	/* Get the optional connector label. */
+	of_property_read_string(pdev->dev.of_node, "label", &conn->label);
+
+	/*
+	 * Get the HPD GPIO for DVI and HDMI connectors. If the GPIO can provide
+	 * interrupts, register an interrupt handler.
+	 */
+	if (type == DRM_MODE_CONNECTOR_DVII ||
+	    type == DRM_MODE_CONNECTOR_HDMIA) {
+		conn->hpd_gpio = devm_gpiod_get_optional(&pdev->dev, "hpd",
+							 GPIOD_IN);
+		if (IS_ERR(conn->hpd_gpio)) {
+			if (PTR_ERR(conn->hpd_gpio) != -EPROBE_DEFER)
+				dev_err(&pdev->dev,
+					"Unable to retrieve HPD GPIO\n");
+			return PTR_ERR(conn->hpd_gpio);
+		}
+
+		conn->hpd_irq = gpiod_to_irq(conn->hpd_gpio);
+	} else {
+		conn->hpd_irq = -EINVAL;
+	}
+
+	if (conn->hpd_irq >= 0) {
+		ret = devm_request_threaded_irq(&pdev->dev, conn->hpd_irq,
+						NULL, display_connector_hpd_irq,
+						IRQF_TRIGGER_RISING |
+						IRQF_TRIGGER_FALLING |
+						IRQF_ONESHOT,
+						"HPD", conn);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"Failed to request HPD interrupt\n");
+			return ret;
+		}
+	}
+
+	/* Retrieve the DDC I2C adapter for DVI, HDMI and VGA connectors. */
+	if (type == DRM_MODE_CONNECTOR_DVII ||
+	    type == DRM_MODE_CONNECTOR_HDMIA ||
+	    type == DRM_MODE_CONNECTOR_VGA) {
+		struct device_node *phandle;
+
+		phandle = of_parse_phandle(pdev->dev.of_node, "ddc-i2c-bus", 0);
+		if (phandle) {
+			conn->ddc = of_get_i2c_adapter_by_node(phandle);
+			of_node_put(phandle);
+			if (!conn->ddc)
+				return -EPROBE_DEFER;
+		} else {
+			dev_dbg(&pdev->dev,
+				"No I2C bus specified, disabling EDID readout\n");
+		}
+	}
+
+	conn->bridge.funcs = &display_connector_bridge_funcs;
+	conn->bridge.of_node = pdev->dev.of_node;
+
+	if (conn->ddc)
+		conn->bridge.ops |= DRM_BRIDGE_OP_EDID
+				 |  DRM_BRIDGE_OP_DETECT;
+	if (conn->hpd_gpio)
+		conn->bridge.ops |= DRM_BRIDGE_OP_DETECT;
+	if (conn->hpd_irq >= 0)
+		conn->bridge.ops |= DRM_BRIDGE_OP_HPD;
+
+	dev_info(&pdev->dev,
+		 "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
+		 display_connector_type_name(conn),
+		 conn->label ? conn->label : "<unlabelled>",
+		 conn->ddc ? "with" : "without",
+		 conn->hpd_gpio ? "with" : "without",
+		 conn->bridge.ops);
+
+	drm_bridge_add(&conn->bridge);
+
+	return 0;
+}
+
+static int display_connector_remove(struct platform_device *pdev)
+{
+	struct display_connector *conn = platform_get_drvdata(pdev);
+
+	drm_bridge_remove(&conn->bridge);
+
+	if (!IS_ERR(conn->ddc))
+		i2c_put_adapter(conn->ddc);
+
+	return 0;
+}
+
+static const struct of_device_id display_connector_match[] = {
+	{
+		.compatible = "composite-video-connector",
+		.data = (void *)DRM_MODE_CONNECTOR_Composite,
+	}, {
+		.compatible = "dvi-connector",
+		.data = (void *)DRM_MODE_CONNECTOR_DVII,
+	}, {
+		.compatible = "hdmi-connector",
+		.data = (void *)DRM_MODE_CONNECTOR_HDMIA,
+	}, {
+		.compatible = "svideo-connector",
+		.data = (void *)DRM_MODE_CONNECTOR_SVIDEO,
+	}, {
+		.compatible = "vga-connector",
+		.data = (void *)DRM_MODE_CONNECTOR_VGA,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, display_connector_match);
+
+static struct platform_driver display_connector_driver = {
+	.probe	= display_connector_probe,
+	.remove	= display_connector_remove,
+	.driver		= {
+		.name		= "display-connector",
+		.of_match_table	= display_connector_match,
+	},
+};
+module_platform_driver(display_connector_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Display connector driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 11/60] drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (5 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 10/60] drm/bridge: Add bridge driver for display connectors Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
       [not found]     ` <3347b6c8-6f2d-17d6-3dc8-e62a3bac634b@ti.com>
  2019-07-07 18:18   ` [PATCH 12/60] drm/bridge: panel: Implement bridge connector operations Laurent Pinchart
                     ` (49 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The TI TPD12S015 is an HDMI level shifter and ESD protector controlled
through GPIOs. Add a DRM bridge driver for the device.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/Kconfig        |   8 +
 drivers/gpu/drm/bridge/Makefile       |   1 +
 drivers/gpu/drm/bridge/ti-tpd12s015.c | 204 ++++++++++++++++++++++++++
 3 files changed, 213 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ti-tpd12s015.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 295a62f65ef9..3928651a0819 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -159,6 +159,14 @@ config DRM_TI_SN65DSI86
 	help
 	  Texas Instruments SN65DSI86 DSI to eDP Bridge driver
 
+config DRM_TI_TPD12S015
+	tristate "TI TPD12S015 HDMI level shifter and ESD protection"
+	depends on OF
+	select DRM_KMS_HELPER
+	help
+	  Texas Instruments TPD12S015 HDMI level shifter and ESD protection
+	  driver.
+
 source "drivers/gpu/drm/bridge/analogix/Kconfig"
 
 source "drivers/gpu/drm/bridge/adv7511/Kconfig"
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index e5987b3aaf62..ce635651e31b 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -17,4 +17,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
 obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
 obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
 obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
+obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
 obj-y += synopsys/
diff --git a/drivers/gpu/drm/bridge/ti-tpd12s015.c b/drivers/gpu/drm/bridge/ti-tpd12s015.c
new file mode 100644
index 000000000000..d01f0c4133a2
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-tpd12s015.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TPD12S015 HDMI ESD protection & level shifter chip driver
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_bridge.h>
+
+struct tpd12s015_device {
+	struct drm_bridge bridge;
+
+	struct gpio_desc *ct_cp_hpd_gpio;
+	struct gpio_desc *ls_oe_gpio;
+	struct gpio_desc *hpd_gpio;
+	int hpd_irq;
+
+	struct drm_bridge *next_bridge;
+};
+
+static inline struct tpd12s015_device *to_tpd12s015(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct tpd12s015_device, bridge);
+}
+
+static int tpd12s015_attach(struct drm_bridge *bridge, bool create_connector)
+{
+	struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+	int ret;
+
+	if (create_connector)
+		return -EINVAL;
+
+	ret = drm_bridge_attach(bridge->encoder, tpd->next_bridge,
+				bridge, false);
+	if (ret < 0)
+		return ret;
+
+	gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 1);
+	gpiod_set_value_cansleep(tpd->ls_oe_gpio, 1);
+
+	/* DC-DC converter needs at max 300us to get to 90% of 5V. */
+	usleep_range(300, 1000);
+
+	return 0;
+}
+
+static void tpd12s015_detach(struct drm_bridge *bridge)
+{
+	struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+
+	gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 0);
+	gpiod_set_value_cansleep(tpd->ls_oe_gpio, 0);
+}
+
+static enum drm_connector_status tpd12s015_detect(struct drm_bridge *bridge)
+{
+	struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+
+	if (gpiod_get_value_cansleep(tpd->hpd_gpio))
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static void tpd12s015_hpd_enable(struct drm_bridge *bridge)
+{
+}
+
+static void tpd12s015_hpd_disable(struct drm_bridge *bridge)
+{
+}
+
+static const struct drm_bridge_funcs tpd12s015_bridge_funcs = {
+	.attach			= tpd12s015_attach,
+	.detach			= tpd12s015_detach,
+	.detect			= tpd12s015_detect,
+	.hpd_enable		= tpd12s015_hpd_enable,
+	.hpd_disable		= tpd12s015_hpd_disable,
+};
+
+static irqreturn_t tpd12s015_hpd_isr(int irq, void *data)
+{
+	struct tpd12s015_device *tpd = data;
+	struct drm_bridge *bridge = &tpd->bridge;
+
+	drm_bridge_hpd_notify(bridge, tpd12s015_detect(bridge));
+
+	return IRQ_HANDLED;
+}
+
+static int tpd12s015_probe(struct platform_device *pdev)
+{
+	struct tpd12s015_device *tpd;
+	struct device_node *node;
+	struct gpio_desc *gpio;
+	int ret;
+
+	tpd = devm_kzalloc(&pdev->dev, sizeof(*tpd), GFP_KERNEL);
+	if (!tpd)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, tpd);
+
+	tpd->bridge.funcs = &tpd12s015_bridge_funcs;
+	tpd->bridge.of_node = pdev->dev.of_node;
+	tpd->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+	tpd->bridge.ops = DRM_BRIDGE_OP_DETECT;
+
+	/* Get the next bridge, connected to port@1. */
+	node = of_graph_get_remote_node(pdev->dev.of_node, 1, -1);
+	if (!node)
+		return -ENODEV;
+
+	tpd->next_bridge = of_drm_find_bridge(node);
+	of_node_put(node);
+
+	if (!tpd->next_bridge)
+		return -EPROBE_DEFER;
+
+	/* Get the control and HPD GPIOs. */
+	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
+					     GPIOD_OUT_LOW);
+	if (IS_ERR(gpio))
+		return PTR_ERR(gpio);
+
+	tpd->ct_cp_hpd_gpio = gpio;
+
+	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
+					     GPIOD_OUT_LOW);
+	if (IS_ERR(gpio))
+		return PTR_ERR(gpio);
+
+	tpd->ls_oe_gpio = gpio;
+
+	gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, GPIOD_IN);
+	if (IS_ERR(gpio))
+		return PTR_ERR(gpio);
+
+	tpd->hpd_gpio = gpio;
+
+	/* Register the IRQ if the HPD GPIO is IRQ-capable. */
+	if (tpd->hpd_gpio)
+		tpd->hpd_irq = gpiod_to_irq(tpd->hpd_gpio);
+
+	if (tpd->hpd_irq) {
+		ret = devm_request_threaded_irq(&pdev->dev, tpd->hpd_irq, NULL,
+						tpd12s015_hpd_isr,
+						IRQF_TRIGGER_RISING |
+						IRQF_TRIGGER_FALLING |
+						IRQF_ONESHOT,
+						"tpd12s015 hpd", tpd);
+		if (ret)
+			return ret;
+
+		tpd->bridge.ops |= DRM_BRIDGE_OP_HPD;
+	}
+
+	/* Register the DRM bridge. */
+	drm_bridge_add(&tpd->bridge);
+
+	return 0;
+}
+
+static int __exit tpd12s015_remove(struct platform_device *pdev)
+{
+	struct tpd12s015_device *tpd = platform_get_drvdata(pdev);
+
+	drm_bridge_remove(&tpd->bridge);
+
+	return 0;
+}
+
+static const struct of_device_id tpd12s015_of_match[] = {
+	{ .compatible = "ti,tpd12s015", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, tpd12s015_of_match);
+
+static struct platform_driver tpd12s015_driver = {
+	.probe	= tpd12s015_probe,
+	.remove	= __exit_p(tpd12s015_remove),
+	.driver	= {
+		.name	= "tpd12s015",
+		.of_match_table = tpd12s015_of_match,
+	},
+};
+
+module_platform_driver(tpd12s015_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("TPD12S015 HDMI level shifter and ESD protection driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 12/60] drm/bridge: panel: Implement bridge connector operations
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (6 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 11/60] drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-16 11:08     ` drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...] Sam Ravnborg
  2019-07-07 18:18   ` [PATCH 13/60] drm/bridge: tfp410: Don't include drmP.h Laurent Pinchart
                     ` (48 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Implement the newly added bridge connector operations, allowing the
usage of drm_bridge_panel with drm_bridge_connector.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/panel.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 98ad4abf2409..628e37babb09 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -59,7 +59,7 @@ static int panel_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 	int ret;
 
 	if (!create_connector)
-		return -EINVAL;
+		return 0;
 
 	if (!bridge->encoder) {
 		DRM_ERROR("Missing encoder\n");
@@ -122,6 +122,18 @@ static void panel_bridge_post_disable(struct drm_bridge *bridge)
 	drm_panel_unprepare(panel_bridge->panel);
 }
 
+static int panel_bridge_get_modes(struct drm_bridge *bridge,
+				  struct drm_connector *connector)
+{
+	struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
+
+	/*
+	 * FIXME: drm_panel_get_modes() should take the connector as an
+	 * argument.
+	 */
+	return drm_panel_get_modes(panel_bridge->panel);
+}
+
 static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
 	.attach = panel_bridge_attach,
 	.detach = panel_bridge_detach,
@@ -129,6 +141,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
 	.enable = panel_bridge_enable,
 	.disable = panel_bridge_disable,
 	.post_disable = panel_bridge_post_disable,
+	.get_modes = panel_bridge_get_modes,
 };
 
 /**
@@ -174,6 +187,9 @@ struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
 #ifdef CONFIG_OF
 	panel_bridge->bridge.of_node = panel->dev->of_node;
 #endif
+	panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES;
+	/* FIXME: The panel should report its type. */
+	panel_bridge->bridge.type = DRM_MODE_CONNECTOR_DPI;
 
 	drm_bridge_add(&panel_bridge->bridge);
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 13/60] drm/bridge: tfp410: Don't include drmP.h
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (7 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 12/60] drm/bridge: panel: Implement bridge connector operations Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
       [not found]     ` <3bb82dc4-434a-aaac-8ea1-3aff0991e790@ti.com>
  2019-07-07 18:18   ` [PATCH 14/60] drm/bridge: tfp410: Replace manual connector handling with bridge Laurent Pinchart
                     ` (47 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The drmP.h header is deprecated, replace it with the headers
specifically needed by the tfp410 driver. While at it, replace the DRM
print macros with dev_info() and dev_err() instead of including
drm_print.h

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/ti-tfp410.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index 8d4690e436c3..a1cad777b057 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -18,6 +18,7 @@
 #include <linux/platform_device.h>
 
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
@@ -64,7 +65,8 @@ static int tfp410_get_modes(struct drm_connector *connector)
 
 	edid = drm_get_edid(connector, dvi->ddc);
 	if (!edid) {
-		DRM_INFO("EDID read failed. Fallback to standard modes\n");
+		dev_info(dvi->dev,
+			 "EDID read failed. Fallback to standard modes\n");
 		goto fallback;
 	}
 
@@ -365,7 +367,7 @@ static int tfp410_init(struct device *dev, bool i2c)
 			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
 			"hdmi-hpd", dvi);
 		if (ret) {
-			DRM_ERROR("failed to register hpd interrupt\n");
+			dev_err(dev, "failed to register hpd interrupt\n");
 			goto fail;
 		}
 	}
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 14/60] drm/bridge: tfp410: Replace manual connector handling with bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (8 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 13/60] drm/bridge: tfp410: Don't include drmP.h Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 15/60] drm/bridge: tfp410: Allow operation without drm_connector Laurent Pinchart
                     ` (46 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Now that a driver is available for display connectors, replace the
manual connector handling code with usage of the DRM bridge API. The
tfp410 driver doesn't deal with the display connector directly anymore,
but still delegates drm_connector operations to the next bridge. This
brings us one step closer to having the tfp410 driver handling the
TFP410 only.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/ti-tfp410.c | 193 ++++++++++-------------------
 1 file changed, 67 insertions(+), 126 deletions(-)

diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index a1cad777b057..e94c4956731d 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -8,14 +8,12 @@
  *
  */
 
-#include <linux/delay.h>
-#include <linux/fwnode.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
-#include <linux/irq.h>
 #include <linux/module.h>
 #include <linux/of_graph.h>
 #include <linux/platform_device.h>
+#include <linux/workqueue.h>
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
@@ -28,16 +26,13 @@
 struct tfp410 {
 	struct drm_bridge	bridge;
 	struct drm_connector	connector;
-	unsigned int		connector_type;
 
 	u32			bus_format;
-	struct i2c_adapter	*ddc;
-	struct gpio_desc	*hpd;
-	int			hpd_irq;
 	struct delayed_work	hpd_work;
 	struct gpio_desc	*powerdown;
 
 	struct drm_bridge_timings timings;
+	struct drm_bridge	*next_bridge;
 
 	struct device *dev;
 };
@@ -60,10 +55,10 @@ static int tfp410_get_modes(struct drm_connector *connector)
 	struct edid *edid;
 	int ret;
 
-	if (!dvi->ddc)
+	if (!(dvi->next_bridge->ops & DRM_BRIDGE_OP_EDID))
 		goto fallback;
 
-	edid = drm_get_edid(connector, dvi->ddc);
+	edid = dvi->next_bridge->funcs->get_edid(dvi->next_bridge, connector);
 	if (!edid) {
 		dev_info(dvi->dev,
 			 "EDID read failed. Fallback to standard modes\n");
@@ -97,21 +92,10 @@ tfp410_connector_detect(struct drm_connector *connector, bool force)
 {
 	struct tfp410 *dvi = drm_connector_to_tfp410(connector);
 
-	if (dvi->hpd) {
-		if (gpiod_get_value_cansleep(dvi->hpd))
-			return connector_status_connected;
-		else
-			return connector_status_disconnected;
-	}
+	if (!(dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT))
+		return connector_status_unknown;
 
-	if (dvi->ddc) {
-		if (drm_probe_ddc(dvi->ddc))
-			return connector_status_connected;
-		else
-			return connector_status_disconnected;
-	}
-
-	return connector_status_unknown;
+	return dvi->next_bridge->funcs->detect(dvi->next_bridge);
 }
 
 static const struct drm_connector_funcs tfp410_con_funcs = {
@@ -123,11 +107,34 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 };
 
+static void tfp410_hpd_work_func(struct work_struct *work)
+{
+	struct tfp410 *dvi;
+
+	dvi = container_of(work, struct tfp410, hpd_work.work);
+
+	if (dvi->bridge.dev)
+		drm_helper_hpd_irq_event(dvi->bridge.dev);
+}
+
+static void tfp410_hpd_callback(void *arg, enum drm_connector_status status)
+{
+	struct tfp410 *dvi = arg;
+
+	mod_delayed_work(system_wq, &dvi->hpd_work,
+			 msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
+}
+
 static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
 	int ret;
 
+	ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge,
+				false);
+	if (ret < 0)
+		return ret;
+
 	if (!create_connector)
 		return -EINVAL;
 
@@ -136,15 +143,21 @@ static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
 		return -ENODEV;
 	}
 
-	if (dvi->hpd_irq >= 0)
+	if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
 		dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;
 	else
 		dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
 
+	if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
+		INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
+		drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback,
+				      dvi);
+	}
+
 	drm_connector_helper_add(&dvi->connector,
 				 &tfp410_con_helper_funcs);
 	ret = drm_connector_init(bridge->dev, &dvi->connector,
-				 &tfp410_con_funcs, dvi->connector_type);
+				 &tfp410_con_funcs, dvi->next_bridge->type);
 	if (ret) {
 		dev_err(dvi->dev, "drm_connector_init() failed: %d\n", ret);
 		return ret;
@@ -153,12 +166,21 @@ static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
 	drm_display_info_set_bus_formats(&dvi->connector.display_info,
 					 &dvi->bus_format, 1);
 
-	drm_connector_attach_encoder(&dvi->connector,
-					  bridge->encoder);
+	drm_connector_attach_encoder(&dvi->connector, bridge->encoder);
 
 	return 0;
 }
 
+static void tfp410_detach(struct drm_bridge *bridge)
+{
+	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
+
+	if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
+		drm_bridge_hpd_disable(dvi->next_bridge);
+		cancel_delayed_work_sync(&dvi->hpd_work);
+	}
+}
+
 static void tfp410_enable(struct drm_bridge *bridge)
 {
 	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
@@ -175,30 +197,11 @@ static void tfp410_disable(struct drm_bridge *bridge)
 
 static const struct drm_bridge_funcs tfp410_bridge_funcs = {
 	.attach		= tfp410_attach,
+	.detach		= tfp410_detach,
 	.enable		= tfp410_enable,
 	.disable	= tfp410_disable,
 };
 
-static void tfp410_hpd_work_func(struct work_struct *work)
-{
-	struct tfp410 *dvi;
-
-	dvi = container_of(work, struct tfp410, hpd_work.work);
-
-	if (dvi->bridge.dev)
-		drm_helper_hpd_irq_event(dvi->bridge.dev);
-}
-
-static irqreturn_t tfp410_hpd_irq_thread(int irq, void *arg)
-{
-	struct tfp410 *dvi = arg;
-
-	mod_delayed_work(system_wq, &dvi->hpd_work,
-			msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
-
-	return IRQ_HANDLED;
-}
-
 static const struct drm_bridge_timings tfp410_default_timings = {
 	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
 			 | DRM_BUS_FLAG_DE_HIGH,
@@ -276,51 +279,9 @@ static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c)
 	return 0;
 }
 
-static int tfp410_get_connector_properties(struct tfp410 *dvi)
-{
-	struct device_node *connector_node, *ddc_phandle;
-	int ret = 0;
-
-	/* port@1 is the connector node */
-	connector_node = of_graph_get_remote_node(dvi->dev->of_node, 1, -1);
-	if (!connector_node)
-		return -ENODEV;
-
-	if (of_device_is_compatible(connector_node, "hdmi-connector"))
-		dvi->connector_type = DRM_MODE_CONNECTOR_HDMIA;
-	else
-		dvi->connector_type = DRM_MODE_CONNECTOR_DVID;
-
-	dvi->hpd = fwnode_get_named_gpiod(&connector_node->fwnode,
-					"hpd-gpios", 0, GPIOD_IN, "hpd");
-	if (IS_ERR(dvi->hpd)) {
-		ret = PTR_ERR(dvi->hpd);
-		dvi->hpd = NULL;
-		if (ret == -ENOENT)
-			ret = 0;
-		else
-			goto fail;
-	}
-
-	ddc_phandle = of_parse_phandle(connector_node, "ddc-i2c-bus", 0);
-	if (!ddc_phandle)
-		goto fail;
-
-	dvi->ddc = of_get_i2c_adapter_by_node(ddc_phandle);
-	if (dvi->ddc)
-		dev_info(dvi->dev, "Connector's ddc i2c bus found\n");
-	else
-		ret = -EPROBE_DEFER;
-
-	of_node_put(ddc_phandle);
-
-fail:
-	of_node_put(connector_node);
-	return ret;
-}
-
 static int tfp410_init(struct device *dev, bool i2c)
 {
+	struct device_node *node;
 	struct tfp410 *dvi;
 	int ret;
 
@@ -332,21 +293,31 @@ static int tfp410_init(struct device *dev, bool i2c)
 	dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);
 	if (!dvi)
 		return -ENOMEM;
+
+	dvi->dev = dev;
 	dev_set_drvdata(dev, dvi);
 
 	dvi->bridge.funcs = &tfp410_bridge_funcs;
 	dvi->bridge.of_node = dev->of_node;
 	dvi->bridge.timings = &dvi->timings;
-	dvi->dev = dev;
+	dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;
 
 	ret = tfp410_parse_timings(dvi, i2c);
 	if (ret)
-		goto fail;
+		return ret;
 
-	ret = tfp410_get_connector_properties(dvi);
-	if (ret)
-		goto fail;
+	/* Get the next bridge, connected to port@1. */
+	node = of_graph_get_remote_node(dev->of_node, 1, -1);
+	if (!node)
+		return -ENODEV;
 
+	dvi->next_bridge = of_drm_find_bridge(node);
+	of_node_put(node);
+
+	if (!dvi->next_bridge)
+		return -EPROBE_DEFER;
+
+	/* Get the powerdown GPIO. */
 	dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown",
 						 GPIOD_OUT_HIGH);
 	if (IS_ERR(dvi->powerdown)) {
@@ -354,48 +325,18 @@ static int tfp410_init(struct device *dev, bool i2c)
 		return PTR_ERR(dvi->powerdown);
 	}
 
-	if (dvi->hpd)
-		dvi->hpd_irq = gpiod_to_irq(dvi->hpd);
-	else
-		dvi->hpd_irq = -ENXIO;
-
-	if (dvi->hpd_irq >= 0) {
-		INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
-
-		ret = devm_request_threaded_irq(dev, dvi->hpd_irq,
-			NULL, tfp410_hpd_irq_thread, IRQF_TRIGGER_RISING |
-			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-			"hdmi-hpd", dvi);
-		if (ret) {
-			dev_err(dev, "failed to register hpd interrupt\n");
-			goto fail;
-		}
-	}
-
+	/*  Register the DRM bridge. */
 	drm_bridge_add(&dvi->bridge);
 
 	return 0;
-fail:
-	i2c_put_adapter(dvi->ddc);
-	if (dvi->hpd)
-		gpiod_put(dvi->hpd);
-	return ret;
 }
 
 static int tfp410_fini(struct device *dev)
 {
 	struct tfp410 *dvi = dev_get_drvdata(dev);
 
-	if (dvi->hpd_irq >= 0)
-		cancel_delayed_work_sync(&dvi->hpd_work);
-
 	drm_bridge_remove(&dvi->bridge);
 
-	if (dvi->ddc)
-		i2c_put_adapter(dvi->ddc);
-	if (dvi->hpd)
-		gpiod_put(dvi->hpd);
-
 	return 0;
 }
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 15/60] drm/bridge: tfp410: Allow operation without drm_connector
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (9 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 14/60] drm/bridge: tfp410: Replace manual connector handling with bridge Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display Laurent Pinchart
                     ` (45 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The tfp410 driver can operate as part of a pipeline where the
drm_connector is created by the display controller. Enable this mode of
operation by skipping creation of a drm_connector internally.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/ti-tfp410.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index e94c4956731d..38d2d2bde4a0 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -136,7 +136,7 @@ static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
 		return ret;
 
 	if (!create_connector)
-		return -EINVAL;
+		return 0;
 
 	if (!bridge->encoder) {
 		dev_err(dvi->dev, "Missing encoder\n");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (10 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 15/60] drm/bridge: tfp410: Allow operation without drm_connector Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-24 16:22     ` Rob Herring
  2019-07-07 18:18   ` [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix Laurent Pinchart
                     ` (44 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	Rob Herring, Tomi Valkeinen, Sean Paul

LG Display is an LCD display manufacturer. Originally formed as a joint
venture by LG Electronics and Philips Electronics, it was formerly known
as LG.Philips LCD, hence the DT vendor prefix lgphilips (which is
already in active use in the kernel).

More information is available at
https://en.wikipedia.org/wiki/LG_Display.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index f0bcff033ecc..2514463f2c63 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -471,6 +471,8 @@ patternProperties:
     description: Lenovo Group Ltd.
   "^lg,.*":
     description: LG Corporation
+  "^lgphilips,.*":
+    description: LG Display
   "^libretech,.*":
     description: Shenzhen Libre Technology Co., Ltd
   "^licheepi,.*":
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (11 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-08 19:00     ` Rob Herring
  2019-07-07 18:18   ` [PATCH 18/60] dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel Laurent Pinchart
                     ` (43 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	Rob Herring, Tomi Valkeinen, Sean Paul

The 'toppoly' vendor prefix is in use and refers to TPO, whose DT vendor
prefix is already defined as 'tpo'. Add 'toppoly' as an alternative and
document it as legacy.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 2514463f2c63..d78527eb8254 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -867,6 +867,8 @@ patternProperties:
     description: Tecon Microprocessor Technologies, LLC.
   "^topeet,.*":
     description: Topeet
+  "^toppoly,.*":
+    description: TPO (legacy prefix, see 'tpo')
   "^toradex,.*":
     description: Toradex AG
   "^toshiba,.*":
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 18/60] dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (12 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-07 18:18   ` [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel Laurent Pinchart
                     ` (42 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	Rob Herring, Tomi Valkeinen, Sean Paul

The NEC NL8048HL11 is a 10.4cm WVGA (800x480) panel with a 24-bit RGB
parallel data interface and an SPI control interface.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 .../bindings/display/panel/nec,nl8048hl11.txt | 38 +++++++++++++++++++
 1 file changed, 38 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt

diff --git a/Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt b/Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt
new file mode 100644
index 000000000000..a2559c74a45b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt
@@ -0,0 +1,38 @@
+NEC NL8048HL11 Panel
+====================
+
+The NEC NL8048HL11 is a 10.4cm WVGA (800x480) panel with a 24-bit RGB parallel
+data interface and an SPI control interface.
+
+Required properties:
+- compatible: Shall contain "nec,nl8048hl11".
+- reset-gpios: The panel reset GPIO specifier.
+
+Optional properties:
+- label: A symbolic name for the panel.
+
+Required nodes:
+- Video port for DPI input
+
+The device node shall contain one 'port' child node corresponding to the DPI
+input, with one child 'endpoint' node, according to the bindings defined in
+[1].
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example
+-------
+
+lcd-panel: panel@0 {
+	compatible = "nec,nl8048hl11";
+	reg = <0>;
+	spi-max-frequency = <10000000>;
+
+	reset-gpios = <&gpio7 7 GPIO_ACTIVE_LOW>;
+
+	port {
+		lcd_in: endpoint {
+			remote-endpoint = <&dpi_out>;
+		};
+	};
+};
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (13 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 18/60] dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-08 18:51     ` Sam Ravnborg
  2019-07-07 18:18   ` [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel Laurent Pinchart
                     ` (41 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Sam Ravnborg, Sebastian Reichel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

This panel is used on the Gumstix Overo Palo35.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/panel/Kconfig             |   7 +
 drivers/gpu/drm/panel/Makefile            |   1 +
 drivers/gpu/drm/panel/panel-lg-lb035q02.c | 235 ++++++++++++++++++++++
 3 files changed, 243 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-lg-lb035q02.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d9d931aa6e26..1843135cbeb1 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -103,6 +103,13 @@ config DRM_PANEL_SAMSUNG_LD9040
 	depends on OF && SPI
 	select VIDEOMODE_HELPERS
 
+config DRM_PANEL_LG_LB035Q02
+	tristate "LG LB035Q024573 RGB panel"
+	depends on GPIOLIB && OF && SPI
+	help
+	  Say Y here if you want to enable support for the LB035Q02 RGB panel.
+	  To compile this driver as a module, choose M here.
+
 config DRM_PANEL_LG_LG4573
 	tristate "LG4573 RGB/SPI panel"
 	depends on OF && SPI
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index fb0cb3aaa9e6..675b5696c685 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
 obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
 obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
 obj-$(CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04) += panel-kingdisplay-kd097d04.o
+obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
 obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
 obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
diff --git a/drivers/gpu/drm/panel/panel-lg-lb035q02.c b/drivers/gpu/drm/panel/panel-lg-lb035q02.c
new file mode 100644
index 000000000000..d8a8c3a3a8c5
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-lg-lb035q02.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LG.Philips LB035Q02 LCD Panel Driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific panel-lg-lb035q02 driver
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ *
+ * Based on a driver by: Steve Sakoman <steve@sakoman.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+struct lb035q02_device {
+	struct drm_panel panel;
+
+	struct spi_device *spi;
+	struct gpio_desc *enable_gpio;
+};
+
+#define to_lb035q02_device(p) container_of(p, struct lb035q02_device, panel)
+
+static int lb035q02_write(struct lb035q02_device *lcd, u16 reg, u16 val)
+{
+	struct spi_message msg;
+	struct spi_transfer index_xfer = {
+		.len		= 3,
+		.cs_change	= 1,
+	};
+	struct spi_transfer value_xfer = {
+		.len		= 3,
+	};
+	u8	buffer[16];
+
+	spi_message_init(&msg);
+
+	/* register index */
+	buffer[0] = 0x70;
+	buffer[1] = 0x00;
+	buffer[2] = reg & 0x7f;
+	index_xfer.tx_buf = buffer;
+	spi_message_add_tail(&index_xfer, &msg);
+
+	/* register value */
+	buffer[4] = 0x72;
+	buffer[5] = val >> 8;
+	buffer[6] = val;
+	value_xfer.tx_buf = buffer + 4;
+	spi_message_add_tail(&value_xfer, &msg);
+
+	return spi_sync(lcd->spi, &msg);
+}
+
+static int lb035q02_init(struct lb035q02_device *lcd)
+{
+	/* Init sequence from page 28 of the lb035q02 spec. */
+	static const struct {
+		u16 index;
+		u16 value;
+	} init_data[] = {
+		{ 0x01, 0x6300 },
+		{ 0x02, 0x0200 },
+		{ 0x03, 0x0177 },
+		{ 0x04, 0x04c7 },
+		{ 0x05, 0xffc0 },
+		{ 0x06, 0xe806 },
+		{ 0x0a, 0x4008 },
+		{ 0x0b, 0x0000 },
+		{ 0x0d, 0x0030 },
+		{ 0x0e, 0x2800 },
+		{ 0x0f, 0x0000 },
+		{ 0x16, 0x9f80 },
+		{ 0x17, 0x0a0f },
+		{ 0x1e, 0x00c1 },
+		{ 0x30, 0x0300 },
+		{ 0x31, 0x0007 },
+		{ 0x32, 0x0000 },
+		{ 0x33, 0x0000 },
+		{ 0x34, 0x0707 },
+		{ 0x35, 0x0004 },
+		{ 0x36, 0x0302 },
+		{ 0x37, 0x0202 },
+		{ 0x3a, 0x0a0d },
+		{ 0x3b, 0x0806 },
+	};
+
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(init_data); ++i) {
+		ret = lb035q02_write(lcd, init_data[i].index,
+				     init_data[i].value);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lb035q02_disable(struct drm_panel *panel)
+{
+	struct lb035q02_device *lcd = to_lb035q02_device(panel);
+
+	gpiod_set_value_cansleep(lcd->enable_gpio, 0);
+
+	return 0;
+}
+
+static int lb035q02_enable(struct drm_panel *panel)
+{
+	struct lb035q02_device *lcd = to_lb035q02_device(panel);
+
+	gpiod_set_value_cansleep(lcd->enable_gpio, 1);
+
+	return 0;
+}
+
+static const struct drm_display_mode lb035q02_mode = {
+	.clock = 6500,
+	.hdisplay = 320,
+	.hsync_start = 320 + 20,
+	.hsync_end = 320 + 20 + 2,
+	.htotal = 320 + 20 + 2 + 68,
+	.vdisplay = 240,
+	.vsync_start = 240 + 4,
+	.vsync_end = 240 + 4 + 2,
+	.vtotal = 240 + 4 + 2 + 18,
+	.vrefresh = 60,
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int lb035q02_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(panel->drm, &lb035q02_mode);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 70;
+	connector->display_info.height_mm = 53;
+	/*
+	 * FIXME: According to the datasheet pixel data is sampled on the
+	 * rising edge of the clock, but the code running on the Gumstix Overo
+	 * Palo35 indicates sampling on the negative edge. This should be
+	 * tested on a real device.
+	 */
+	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
+					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs lb035q02_funcs = {
+	.disable = lb035q02_disable,
+	.enable = lb035q02_enable,
+	.get_modes = lb035q02_get_modes,
+};
+
+static int lb035q02_probe(struct spi_device *spi)
+{
+	struct lb035q02_device *lcd;
+	int ret;
+
+	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+	if (lcd == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, lcd);
+	lcd->spi = spi;
+
+	lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->enable_gpio)) {
+		dev_err(&spi->dev, "failed to parse enable gpio\n");
+		return PTR_ERR(lcd->enable_gpio);
+	}
+
+	ret = lb035q02_init(lcd);
+	if (ret < 0)
+		return ret;
+
+	drm_panel_init(&lcd->panel);
+	lcd->panel.dev = &lcd->spi->dev;
+	lcd->panel.funcs = &lb035q02_funcs;
+
+	return drm_panel_add(&lcd->panel);
+}
+
+static int lb035q02_remove(struct spi_device *spi)
+{
+	struct lb035q02_device *lcd = spi_get_drvdata(spi);
+
+	drm_panel_remove(&lcd->panel);
+	lb035q02_disable(&lcd->panel);
+
+	return 0;
+}
+
+static const struct of_device_id lb035q02_of_match[] = {
+	{ .compatible = "lgphilips,lb035q02", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, lb035q02_of_match);
+
+static struct spi_driver lb035q02_driver = {
+	.probe		= lb035q02_probe,
+	.remove		= lb035q02_remove,
+	.driver		= {
+		.name	= "panel-lg-lb035q02",
+		.of_match_table = lb035q02_of_match,
+	},
+};
+
+module_spi_driver(lb035q02_driver);
+
+MODULE_ALIAS("spi:lgphilips,lb035q02");
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (14 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-08 19:10     ` Sam Ravnborg
  2019-07-07 18:18   ` [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel Laurent Pinchart
                     ` (40 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Sam Ravnborg, Sebastian Reichel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

This panel is used on the Zoom2/3/3630 SDP boards.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/panel/Kconfig                |   7 +
 drivers/gpu/drm/panel/Makefile               |   1 +
 drivers/gpu/drm/panel/panel-nec-nl8048hl11.c | 249 +++++++++++++++++++
 3 files changed, 257 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-nec-nl8048hl11.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 1843135cbeb1..da613c04b835 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -118,6 +118,13 @@ config DRM_PANEL_LG_LG4573
 	  Say Y here if you want to enable support for LG4573 RGB panel.
 	  To compile this driver as a module, choose M here.
 
+config DRM_PANEL_NEC_NL8048HL11
+	tristate "NEC NL8048HL11 RGB panel"
+	depends on GPIOLIB && OF && SPI
+	help
+	  Say Y here if you want to enable support for the NEC NL8048HL11 RGB
+	  panel. To compile this driver as a module, choose M here.
+
 config DRM_PANEL_OLIMEX_LCD_OLINUXINO
 	tristate "Olimex LCD-OLinuXino panel"
 	depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 675b5696c685..e81ed1535024 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
 obj-$(CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04) += panel-kingdisplay-kd097d04.o
 obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o
 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
+obj-$(CONFIG_DRM_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
 obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
 obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
 obj-$(CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS) += panel-osd-osd101t2587-53ts.o
diff --git a/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
new file mode 100644
index 000000000000..99da665d9b4b
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NEC NL8048HL11 Panel Driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific panel-nec-nl8048hl11 driver
+ *
+ * Copyright (C) 2010 Texas Instruments Incorporated
+ * Author: Erik Gilling <konkers@android.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+struct nl8048_device {
+	struct drm_panel panel;
+
+	struct spi_device *spi;
+	struct gpio_desc *reset_gpio;
+};
+
+#define to_nl8048_device(p) container_of(p, struct nl8048_device, panel)
+
+static int nl8048_write(struct nl8048_device *lcd, unsigned char addr,
+			unsigned char value)
+{
+	u8 data[4] = { value, 0x01, addr, 0x00 };
+	int ret;
+
+	ret = spi_write(lcd->spi, data, sizeof(data));
+	if (ret)
+		dev_err(&lcd->spi->dev, "SPI write to %u failed: %d\n",
+			addr, ret);
+
+	return ret;
+}
+
+static int nl8048_init(struct nl8048_device *lcd)
+{
+	static const struct {
+		unsigned char addr;
+		unsigned char data;
+	} nl8048_init_seq[] = {
+		{   3, 0x01 }, {   0, 0x00 }, {   1, 0x01 }, {   4, 0x00 },
+		{   5, 0x14 }, {   6, 0x24 }, {  16, 0xd7 }, {  17, 0x00 },
+		{  18, 0x00 }, {  19, 0x55 }, {  20, 0x01 }, {  21, 0x70 },
+		{  22, 0x1e }, {  23, 0x25 }, {  24, 0x25 }, {  25, 0x02 },
+		{  26, 0x02 }, {  27, 0xa0 }, {  32, 0x2f }, {  33, 0x0f },
+		{  34, 0x0f }, {  35, 0x0f }, {  36, 0x0f }, {  37, 0x0f },
+		{  38, 0x0f }, {  39, 0x00 }, {  40, 0x02 }, {  41, 0x02 },
+		{  42, 0x02 }, {  43, 0x0f }, {  44, 0x0f }, {  45, 0x0f },
+		{  46, 0x0f }, {  47, 0x0f }, {  48, 0x0f }, {  49, 0x0f },
+		{  50, 0x00 }, {  51, 0x02 }, {  52, 0x02 }, {  53, 0x02 },
+		{  80, 0x0c }, {  83, 0x42 }, {  84, 0x42 }, {  85, 0x41 },
+		{  86, 0x14 }, {  89, 0x88 }, {  90, 0x01 }, {  91, 0x00 },
+		{  92, 0x02 }, {  93, 0x0c }, {  94, 0x1c }, {  95, 0x27 },
+		{  98, 0x49 }, {  99, 0x27 }, { 102, 0x76 }, { 103, 0x27 },
+		{ 112, 0x01 }, { 113, 0x0e }, { 114, 0x02 }, { 115, 0x0c },
+		{ 118, 0x0c }, { 121, 0x30 }, { 130, 0x00 }, { 131, 0x00 },
+		{ 132, 0xfc }, { 134, 0x00 }, { 136, 0x00 }, { 138, 0x00 },
+		{ 139, 0x00 }, { 140, 0x00 }, { 141, 0xfc }, { 143, 0x00 },
+		{ 145, 0x00 }, { 147, 0x00 }, { 148, 0x00 }, { 149, 0x00 },
+		{ 150, 0xfc }, { 152, 0x00 }, { 154, 0x00 }, { 156, 0x00 },
+		{ 157, 0x00 },
+	};
+
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(nl8048_init_seq); ++i) {
+		ret = nl8048_write(lcd, nl8048_init_seq[i].addr,
+				   nl8048_init_seq[i].data);
+		if (ret < 0)
+			return ret;
+	}
+
+	udelay(20);
+
+	return nl8048_write(lcd, 2, 0x00);
+}
+
+static int nl8048_disable(struct drm_panel *panel)
+{
+	struct nl8048_device *lcd = to_nl8048_device(panel);
+
+	gpiod_set_value_cansleep(lcd->reset_gpio, 0);
+
+	return 0;
+}
+
+static int nl8048_enable(struct drm_panel *panel)
+{
+	struct nl8048_device *lcd = to_nl8048_device(panel);
+
+	gpiod_set_value_cansleep(lcd->reset_gpio, 1);
+
+	return 0;
+}
+
+static const struct drm_display_mode nl8048_mode = {
+	/*  NEC PIX Clock Ratings MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz */
+	.clock	= 23800,
+	.hdisplay = 800,
+	.hsync_start = 800 + 6,
+	.hsync_end = 800 + 6 + 1,
+	.htotal = 800 + 6 + 1 + 4,
+	.vdisplay = 480,
+	.vsync_start = 480 + 3,
+	.vsync_end = 480 + 3 + 1,
+	.vtotal = 480 + 3 + 1 + 4,
+	.vrefresh = 60,
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int nl8048_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(panel->drm, &nl8048_mode);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 89;
+	connector->display_info.height_mm = 53;
+	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
+					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs nl8048_funcs = {
+	.disable = nl8048_disable,
+	.enable = nl8048_enable,
+	.get_modes = nl8048_get_modes,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int nl8048_suspend(struct device *dev)
+{
+	struct nl8048_device *lcd = dev_get_drvdata(dev);
+
+	nl8048_write(lcd, 2, 0x01);
+	msleep(40);
+
+	return 0;
+}
+
+static int nl8048_resume(struct device *dev)
+{
+	struct nl8048_device *lcd = dev_get_drvdata(dev);
+
+	/* Reinitialize the panel. */
+	spi_setup(lcd->spi);
+	nl8048_write(lcd, 2, 0x00);
+	nl8048_init(lcd);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(nl8048_pm_ops, nl8048_suspend, nl8048_resume);
+#endif
+
+static int nl8048_probe(struct spi_device *spi)
+{
+	struct nl8048_device *lcd;
+	int ret;
+
+	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+	if (lcd == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, lcd);
+	lcd->spi = spi;
+
+	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->reset_gpio)) {
+		dev_err(&spi->dev, "failed to parse reset gpio\n");
+		return PTR_ERR(lcd->reset_gpio);
+	}
+
+	spi->mode = SPI_MODE_0;
+	spi->bits_per_word = 32;
+
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+		return ret;
+	}
+
+	ret = nl8048_init(lcd);
+	if (ret < 0)
+		return ret;
+
+	drm_panel_init(&lcd->panel);
+	lcd->panel.dev = &lcd->spi->dev;
+	lcd->panel.funcs = &nl8048_funcs;
+
+	return drm_panel_add(&lcd->panel);
+}
+
+static int nl8048_remove(struct spi_device *spi)
+{
+	struct nl8048_device *lcd = spi_get_drvdata(spi);
+
+	drm_panel_remove(&lcd->panel);
+	nl8048_disable(&lcd->panel);
+
+	return 0;
+}
+
+static const struct of_device_id nl8048_of_match[] = {
+	{ .compatible = "nec,nl8048hl11", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, nl8048_of_match);
+
+static struct spi_driver nl8048_driver = {
+	.probe		= nl8048_probe,
+	.remove		= nl8048_remove,
+	.driver		= {
+		.name	= "panel-nec-nl8048hl11",
+#ifdef CONFIG_PM_SLEEP
+		.pm	= &nl8048_pm_ops,
+#endif
+		.of_match_table = nl8048_of_match,
+	},
+};
+
+module_spi_driver(nl8048_driver);
+
+MODULE_ALIAS("spi:nec,nl8048hl11");
+MODULE_AUTHOR("Erik Gilling <konkers@android.com>");
+MODULE_DESCRIPTION("NEC-NL8048HL11 Driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (15 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-08 19:44     ` Sam Ravnborg
  2019-07-07 18:18   ` [PATCH 22/60] drm/panel: Add driver for the Sony ACX565AKM panel Laurent Pinchart
                     ` (39 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Sam Ravnborg, Sebastian Reichel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

This panel is used on the SDP3430.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/panel/Kconfig                 |   7 +
 drivers/gpu/drm/panel/Makefile                |   1 +
 .../gpu/drm/panel/panel-sharp-ls037v7dw01.c   | 231 ++++++++++++++++++
 3 files changed, 239 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index da613c04b835..04fd152efe4c 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -271,6 +271,13 @@ config DRM_PANEL_SHARP_LS043T1LE01
 	  Say Y here if you want to enable support for Sharp LS043T1LE01 qHD
 	  (540x960) DSI panel as found on the Qualcomm APQ8074 Dragonboard
 
+config DRM_PANEL_SHARP_LS037V7DW01
+	tristate "Sharp LS037V7DW01 VGA LCD panel"
+	depends on GPIOLIB && OF && REGULATOR
+	help
+	  Say Y here if you want to enable support for Sharp LS037V7DW01 VGA
+	  (480x640) LCD panel.
+
 config DRM_PANEL_SITRONIX_ST7701
 	tristate "Sitronix ST7701 panel driver"
 	depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index e81ed1535024..12dcd76eb87c 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o
 obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o
 obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o
 obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
+obj-$(CONFIG_DRM_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
 obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
diff --git a/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
new file mode 100644
index 000000000000..4532455b4161
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sharp LS037V7DW01 LCD Panel Driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific panel-sharp-ls037v7dw01 driver
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+struct ls037v7dw01_device {
+	struct drm_panel panel;
+	struct platform_device *pdev;
+
+	struct regulator *vcc;
+	struct gpio_desc *resb_gpio;	/* low = reset active min 20 us */
+	struct gpio_desc *ini_gpio;	/* high = power on */
+	struct gpio_desc *mo_gpio;	/* low = 480x640, high = 240x320 */
+	struct gpio_desc *lr_gpio;	/* high = conventional horizontal scanning */
+	struct gpio_desc *ud_gpio;	/* high = conventional vertical scanning */
+};
+
+#define to_ls037v7dw01_device(p) \
+	container_of(p, struct ls037v7dw01_device, panel)
+
+static int ls037v7dw01_disable(struct drm_panel *panel)
+{
+	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
+
+	gpiod_set_value_cansleep(lcd->ini_gpio, 0);
+	gpiod_set_value_cansleep(lcd->resb_gpio, 0);
+
+	/* Wait at least 5 vsyncs after disabling the LCD. */
+	msleep(100);
+
+	return 0;
+}
+
+static int ls037v7dw01_unprepare(struct drm_panel *panel)
+{
+	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
+
+	if (lcd->vcc)
+		regulator_disable(lcd->vcc);
+
+	return 0;
+}
+
+static int ls037v7dw01_prepare(struct drm_panel *panel)
+{
+	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
+	int ret;
+
+	if (!lcd->vcc)
+		return 0;
+
+	ret = regulator_enable(lcd->vcc);
+	if (ret < 0)
+		dev_err(&lcd->pdev->dev, "%s: failed to enable regulator\n",
+			__func__);
+
+	return ret;
+}
+
+static int ls037v7dw01_enable(struct drm_panel *panel)
+{
+	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
+
+	/* Wait couple of vsyncs before enabling the LCD. */
+	msleep(50);
+
+	gpiod_set_value_cansleep(lcd->resb_gpio, 1);
+	gpiod_set_value_cansleep(lcd->ini_gpio, 1);
+
+	return 0;
+}
+
+static const struct drm_display_mode ls037v7dw01_mode = {
+	.clock = 19200,
+	.hdisplay = 480,
+	.hsync_start = 480 + 1,
+	.hsync_end = 480 + 1 + 2,
+	.htotal = 480 + 1 + 2 + 28,
+	.vdisplay = 640,
+	.vsync_start = 640 + 1,
+	.vsync_end = 640 + 1 + 1,
+	.vtotal = 640 + 1 + 1 + 1,
+	.vrefresh = 58,
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int ls037v7dw01_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(panel->drm, &ls037v7dw01_mode);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 56;
+	connector->display_info.height_mm = 75;
+	/*
+	 * FIXME: According to the datasheet pixel data is sampled on the
+	 * rising edge of the clock, but the code running on the SDP3430
+	 * indicates sampling on the negative edge. This should be tested on a
+	 * real device.
+	 */
+	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
+					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs ls037v7dw01_funcs = {
+	.disable = ls037v7dw01_disable,
+	.unprepare = ls037v7dw01_unprepare,
+	.prepare = ls037v7dw01_prepare,
+	.enable = ls037v7dw01_enable,
+	.get_modes = ls037v7dw01_get_modes,
+};
+
+static int ls037v7dw01_probe(struct platform_device *pdev)
+{
+	struct ls037v7dw01_device *lcd;
+
+	lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL);
+	if (lcd == NULL)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, lcd);
+	lcd->pdev = pdev;
+
+	lcd->vcc = devm_regulator_get(&pdev->dev, "envdd");
+	if (IS_ERR(lcd->vcc)) {
+		dev_err(&pdev->dev, "failed to get regulator\n");
+		return PTR_ERR(lcd->vcc);
+	}
+
+	lcd->ini_gpio = devm_gpiod_get_index(&pdev->dev, "enable", 0,
+					    GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->ini_gpio)) {
+		dev_err(&pdev->dev, "failed to get enable gpio\n");
+		return PTR_ERR(lcd->ini_gpio);
+	}
+
+	lcd->resb_gpio = devm_gpiod_get_index(&pdev->dev, "reset", 0,
+					     GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->resb_gpio)) {
+		dev_err(&pdev->dev, "failed to get reset gpio\n");
+		return PTR_ERR(lcd->resb_gpio);
+	}
+
+	lcd->mo_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 0,
+					   GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->mo_gpio)) {
+		dev_err(&pdev->dev, "failed to get mode[0] gpio\n");
+		return PTR_ERR(lcd->mo_gpio);
+	}
+
+	lcd->lr_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 1,
+					   GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->lr_gpio)) {
+		dev_err(&pdev->dev, "failed to get mode[1] gpio\n");
+		return PTR_ERR(lcd->lr_gpio);
+	}
+
+	lcd->ud_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 2,
+					   GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->ud_gpio)) {
+		dev_err(&pdev->dev, "failed to get mode[2] gpio\n");
+		return PTR_ERR(lcd->ud_gpio);
+	}
+
+	drm_panel_init(&lcd->panel);
+	lcd->panel.dev = &pdev->dev;
+	lcd->panel.funcs = &ls037v7dw01_funcs;
+
+	return drm_panel_add(&lcd->panel);
+}
+
+static int ls037v7dw01_remove(struct platform_device *pdev)
+{
+	struct ls037v7dw01_device *lcd = platform_get_drvdata(pdev);
+
+	drm_panel_remove(&lcd->panel);
+	ls037v7dw01_disable(&lcd->panel);
+	ls037v7dw01_unprepare(&lcd->panel);
+
+	return 0;
+}
+
+static const struct of_device_id ls037v7dw01_of_match[] = {
+	{ .compatible = "sharp,ls037v7dw01", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match);
+
+static struct platform_driver ls037v7dw01_driver = {
+	.probe		= ls037v7dw01_probe,
+	.remove		= __exit_p(ls037v7dw01_remove),
+	.driver		= {
+		.name = "panel-sharp-ls037v7dw01",
+		.of_match_table = ls037v7dw01_of_match,
+	},
+};
+
+module_platform_driver(ls037v7dw01_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 22/60] drm/panel: Add driver for the Sony ACX565AKM panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (16 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel Laurent Pinchart
@ 2019-07-07 18:18   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel Laurent Pinchart
                     ` (38 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:18 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Sam Ravnborg, Sebastian Reichel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

This panel is used on the Nokia N900.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/panel/Kconfig                |   8 +
 drivers/gpu/drm/panel/Makefile               |   1 +
 drivers/gpu/drm/panel/panel-sony-acx565akm.c | 691 +++++++++++++++++++
 3 files changed, 700 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-sony-acx565akm.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 04fd152efe4c..dc10f727689a 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -296,6 +296,14 @@ config DRM_PANEL_SITRONIX_ST7789V
 	  Say Y here if you want to enable support for the Sitronix
 	  ST7789V controller for 240x320 LCD panels
 
+config DRM_PANEL_SONY_ACX565AKM
+	tristate "Sony ACX565AKM panel"
+	depends on GPIOLIB && OF && SPI
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y here if you want to enable support for the Sony ACX565AKM
+	  800x600 3.5" panel.
+
 config DRM_PANEL_TPO_TPG110
 	tristate "TPO TPG 800x400 panel"
 	depends on OF && SPI && GPIOLIB
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 12dcd76eb87c..84cbc069f6cd 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -31,5 +31,6 @@ obj-$(CONFIG_DRM_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
 obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
+obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
 obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
 obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
diff --git a/drivers/gpu/drm/panel/panel-sony-acx565akm.c b/drivers/gpu/drm/panel/panel-sony-acx565akm.c
new file mode 100644
index 000000000000..e7292648ac06
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-sony-acx565akm.c
@@ -0,0 +1,691 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sony ACX565AKM LCD Panel driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific panel-sony-acx565akm driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Author: Imre Deak <imre.deak@nokia.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define CTRL_DISP_BRIGHTNESS_CTRL_ON		(1 << 5)
+#define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON		(1 << 4)
+#define CTRL_DISP_BACKLIGHT_ON			(1 << 2)
+#define CTRL_DISP_AUTO_BRIGHTNESS_ON		(1 << 1)
+
+#define MIPID_CMD_WRITE_CABC		0x55
+#define MIPID_CMD_READ_CABC		0x56
+
+#define MIPID_VER_LPH8923		3
+#define MIPID_VER_LS041Y3		4
+#define MIPID_VER_L4F00311		8
+#define MIPID_VER_ACX565AKM		9
+
+struct acx565akm_device {
+	struct drm_panel panel;
+
+	struct spi_device *spi;
+	struct gpio_desc *reset_gpio;
+	struct backlight_device *backlight;
+
+	struct mutex mutex;
+
+	const char *name;
+	u8 display_id[3];
+	int model;
+	int revision;
+	bool has_bc;
+	bool has_cabc;
+
+	bool enabled;
+	unsigned int cabc_mode;
+	/*
+	 * Next value of jiffies when we can issue the next sleep in/out
+	 * command.
+	 */
+	unsigned long hw_guard_end;
+	unsigned long hw_guard_wait;		/* max guard time in jiffies */
+};
+
+#define to_acx565akm_device(p) container_of(p, struct acx565akm_device, panel)
+
+static void acx565akm_transfer(struct acx565akm_device *lcd, int cmd,
+			      const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct spi_message	m;
+	struct spi_transfer	*x, xfer[5];
+	int			ret;
+
+	spi_message_init(&m);
+
+	memset(xfer, 0, sizeof(xfer));
+	x = &xfer[0];
+
+	cmd &=  0xff;
+	x->tx_buf = &cmd;
+	x->bits_per_word = 9;
+	x->len = 2;
+
+	if (rlen > 1 && wlen == 0) {
+		/*
+		 * Between the command and the response data there is a
+		 * dummy clock cycle. Add an extra bit after the command
+		 * word to account for this.
+		 */
+		x->bits_per_word = 10;
+		cmd <<= 1;
+	}
+	spi_message_add_tail(x, &m);
+
+	if (wlen) {
+		x++;
+		x->tx_buf = wbuf;
+		x->len = wlen;
+		x->bits_per_word = 9;
+		spi_message_add_tail(x, &m);
+	}
+
+	if (rlen) {
+		x++;
+		x->rx_buf	= rbuf;
+		x->len		= rlen;
+		spi_message_add_tail(x, &m);
+	}
+
+	ret = spi_sync(lcd->spi, &m);
+	if (ret < 0)
+		dev_dbg(&lcd->spi->dev, "spi_sync %d\n", ret);
+}
+
+static inline void acx565akm_cmd(struct acx565akm_device *lcd, int cmd)
+{
+	acx565akm_transfer(lcd, cmd, NULL, 0, NULL, 0);
+}
+
+static inline void acx565akm_write(struct acx565akm_device *lcd,
+			       int reg, const u8 *buf, int len)
+{
+	acx565akm_transfer(lcd, reg, buf, len, NULL, 0);
+}
+
+static inline void acx565akm_read(struct acx565akm_device *lcd,
+			      int reg, u8 *buf, int len)
+{
+	acx565akm_transfer(lcd, reg, NULL, 0, buf, len);
+}
+
+/* -----------------------------------------------------------------------------
+ * Auto Brightness Control Via sysfs
+ */
+
+static unsigned int acx565akm_get_cabc_mode(struct acx565akm_device *lcd)
+{
+	return lcd->cabc_mode;
+}
+
+static void acx565akm_set_cabc_mode(struct acx565akm_device *lcd,
+				    unsigned int mode)
+{
+	u16 cabc_ctrl;
+
+	lcd->cabc_mode = mode;
+	if (!lcd->enabled)
+		return;
+	cabc_ctrl = 0;
+	acx565akm_read(lcd, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1);
+	cabc_ctrl &= ~3;
+	cabc_ctrl |= (1 << 8) | (mode & 3);
+	acx565akm_write(lcd, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2);
+}
+
+static unsigned int acx565akm_get_hw_cabc_mode(struct acx565akm_device *lcd)
+{
+	u8 cabc_ctrl;
+
+	acx565akm_read(lcd, MIPID_CMD_READ_CABC, &cabc_ctrl, 1);
+	return cabc_ctrl & 3;
+}
+
+static const char * const acx565akm_cabc_modes[] = {
+	"off",		/* always used when CABC is not supported */
+	"ui",
+	"still-image",
+	"moving-image",
+};
+
+static ssize_t cabc_mode_show(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct acx565akm_device *lcd = dev_get_drvdata(dev);
+	const char *mode_str;
+	int mode;
+
+	if (!lcd->has_cabc)
+		mode = 0;
+	else
+		mode = acx565akm_get_cabc_mode(lcd);
+
+	mode_str = "unknown";
+	if (mode >= 0 && mode < ARRAY_SIZE(acx565akm_cabc_modes))
+		mode_str = acx565akm_cabc_modes[mode];
+
+	return sprintf(buf, "%s\n", mode_str);
+}
+
+static ssize_t cabc_mode_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct acx565akm_device *lcd = dev_get_drvdata(dev);
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++) {
+		const char *mode_str = acx565akm_cabc_modes[i];
+		int cmp_len = strlen(mode_str);
+
+		if (count > 0 && buf[count - 1] == '\n')
+			count--;
+		if (count != cmp_len)
+			continue;
+
+		if (strncmp(buf, mode_str, cmp_len) == 0)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(acx565akm_cabc_modes))
+		return -EINVAL;
+
+	if (!lcd->has_cabc && i != 0)
+		return -EINVAL;
+
+	mutex_lock(&lcd->mutex);
+	acx565akm_set_cabc_mode(lcd, i);
+	mutex_unlock(&lcd->mutex);
+
+	return count;
+}
+
+static ssize_t cabc_available_modes_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct acx565akm_device *lcd = dev_get_drvdata(dev);
+	unsigned int i;
+	size_t len = 0;
+
+	if (!lcd->has_cabc)
+		return sprintf(buf, "%s\n", acx565akm_cabc_modes[0]);
+
+	for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++)
+		len += sprintf(&buf[len], "%s%s", i ? " " : "",
+			       acx565akm_cabc_modes[i]);
+
+	buf[len++] = '\n';
+
+	return len;
+}
+
+static DEVICE_ATTR_RW(cabc_mode);
+static DEVICE_ATTR_RO(cabc_available_modes);
+
+static struct attribute *acx565akm_cabc_attrs[] = {
+	&dev_attr_cabc_mode.attr,
+	&dev_attr_cabc_available_modes.attr,
+	NULL,
+};
+
+static const struct attribute_group acx565akm_cabc_attr_group = {
+	.attrs = acx565akm_cabc_attrs,
+};
+
+/* -----------------------------------------------------------------------------
+ * Backlight Device
+ */
+
+static int acx565akm_get_actual_brightness(struct acx565akm_device *lcd)
+{
+	u8 bv;
+
+	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_BRIGHTNESS, &bv, 1);
+
+	return bv;
+}
+
+static void acx565akm_set_brightness(struct acx565akm_device *lcd, int level)
+{
+	u16 ctrl;
+	int bv;
+
+	bv = level | (1 << 8);
+	acx565akm_write(lcd, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, (u8 *)&bv, 2);
+
+	acx565akm_read(lcd, MIPI_DCS_GET_CONTROL_DISPLAY, (u8 *)&ctrl, 1);
+	if (level)
+		ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON |
+			CTRL_DISP_BACKLIGHT_ON;
+	else
+		ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON |
+			  CTRL_DISP_BACKLIGHT_ON);
+
+	ctrl |= 1 << 8;
+	acx565akm_write(lcd, MIPI_DCS_WRITE_CONTROL_DISPLAY, (u8 *)&ctrl, 2);
+}
+
+static int acx565akm_bl_update_status_locked(struct backlight_device *dev)
+{
+	struct acx565akm_device *lcd = dev_get_drvdata(&dev->dev);
+	int level;
+
+	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
+	    dev->props.power == FB_BLANK_UNBLANK)
+		level = dev->props.brightness;
+	else
+		level = 0;
+
+	acx565akm_set_brightness(lcd, level);
+
+	return 0;
+}
+
+static int acx565akm_bl_update_status(struct backlight_device *dev)
+{
+	struct acx565akm_device *lcd = dev_get_drvdata(&dev->dev);
+	int ret;
+
+	mutex_lock(&lcd->mutex);
+	ret = acx565akm_bl_update_status_locked(dev);
+	mutex_unlock(&lcd->mutex);
+
+	return ret;
+}
+
+static int acx565akm_bl_get_intensity(struct backlight_device *dev)
+{
+	struct acx565akm_device *lcd = dev_get_drvdata(&dev->dev);
+	unsigned int intensity;
+
+	mutex_lock(&lcd->mutex);
+
+	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
+	    dev->props.power == FB_BLANK_UNBLANK)
+		intensity = acx565akm_get_actual_brightness(lcd);
+	else
+		intensity = 0;
+
+	mutex_unlock(&lcd->mutex);
+
+	return intensity;
+}
+
+static const struct backlight_ops acx565akm_bl_ops = {
+	.get_brightness = acx565akm_bl_get_intensity,
+	.update_status  = acx565akm_bl_update_status,
+};
+
+static int acx565akm_backlight_init(struct acx565akm_device *lcd)
+{
+	struct backlight_properties props = {
+		.fb_blank = FB_BLANK_UNBLANK,
+		.power = FB_BLANK_UNBLANK,
+		.type = BACKLIGHT_RAW,
+	};
+	int ret;
+
+	lcd->backlight = backlight_device_register(lcd->name, &lcd->spi->dev,
+						   lcd, &acx565akm_bl_ops,
+						   &props);
+	if (IS_ERR(lcd->backlight)) {
+		ret = PTR_ERR(lcd->backlight);
+		lcd->backlight = NULL;
+		return ret;
+	}
+
+	if (lcd->has_cabc) {
+		ret = sysfs_create_group(&lcd->backlight->dev.kobj,
+					 &acx565akm_cabc_attr_group);
+		if (ret < 0) {
+			dev_err(&lcd->spi->dev,
+				"%s failed to create sysfs files\n", __func__);
+			backlight_device_unregister(lcd->backlight);
+			return ret;
+		}
+
+		lcd->cabc_mode = acx565akm_get_hw_cabc_mode(lcd);
+	}
+
+	lcd->backlight->props.max_brightness = 255;
+	lcd->backlight->props.brightness = acx565akm_get_actual_brightness(lcd);
+
+	acx565akm_bl_update_status_locked(lcd->backlight);
+
+	return 0;
+}
+
+static void acx565akm_backlight_cleanup(struct acx565akm_device *lcd)
+{
+	if (lcd->has_cabc)
+		sysfs_remove_group(&lcd->backlight->dev.kobj,
+				   &acx565akm_cabc_attr_group);
+
+	backlight_device_unregister(lcd->backlight);
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static void acx565akm_set_sleep_mode(struct acx565akm_device *lcd, int on)
+{
+	int cmd = on ? MIPI_DCS_ENTER_SLEEP_MODE : MIPI_DCS_EXIT_SLEEP_MODE;
+	unsigned long wait;
+
+	/*
+	 * We have to keep 120msec between sleep in/out commands.
+	 * (8.2.15, 8.2.16).
+	 */
+	wait = lcd->hw_guard_end - jiffies;
+	if ((long)wait > 0 && wait <= lcd->hw_guard_wait) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(wait);
+	}
+
+	acx565akm_cmd(lcd, cmd);
+
+	lcd->hw_guard_wait = msecs_to_jiffies(120);
+	lcd->hw_guard_end = jiffies + lcd->hw_guard_wait;
+}
+
+static void acx565akm_set_display_state(struct acx565akm_device *lcd,
+					int enabled)
+{
+	int cmd = enabled ? MIPI_DCS_SET_DISPLAY_ON : MIPI_DCS_SET_DISPLAY_OFF;
+
+	acx565akm_cmd(lcd, cmd);
+}
+
+static int acx565akm_power_on(struct acx565akm_device *lcd)
+{
+	/*FIXME tweak me */
+	msleep(50);
+
+	gpiod_set_value(lcd->reset_gpio, 1);
+
+	if (lcd->enabled) {
+		dev_dbg(&lcd->spi->dev, "panel already enabled\n");
+		return 0;
+	}
+
+	/*
+	 * We have to meet all the following delay requirements:
+	 * 1. tRW: reset pulse width 10usec (7.12.1)
+	 * 2. tRT: reset cancel time 5msec (7.12.1)
+	 * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst
+	 *    case (7.6.2)
+	 * 4. 120msec before the sleep out command (7.12.1)
+	 */
+	msleep(120);
+
+	acx565akm_set_sleep_mode(lcd, 0);
+	lcd->enabled = true;
+
+	/* 5msec between sleep out and the next command. (8.2.16) */
+	usleep_range(5000, 10000);
+	acx565akm_set_display_state(lcd, 1);
+	acx565akm_set_cabc_mode(lcd, lcd->cabc_mode);
+
+	return acx565akm_bl_update_status_locked(lcd->backlight);
+}
+
+static void acx565akm_power_off(struct acx565akm_device *lcd)
+{
+	if (!lcd->enabled)
+		return;
+
+	acx565akm_set_display_state(lcd, 0);
+	acx565akm_set_sleep_mode(lcd, 1);
+	lcd->enabled = false;
+	/*
+	 * We have to provide PCLK,HS,VS signals for 2 frames (worst case
+	 * ~50msec) after sending the sleep in command and asserting the
+	 * reset signal. We probably could assert the reset w/o the delay
+	 * but we still delay to avoid possible artifacts. (7.6.1)
+	 */
+	msleep(50);
+
+	gpiod_set_value(lcd->reset_gpio, 0);
+
+	/* FIXME need to tweak this delay */
+	msleep(100);
+}
+
+static int acx565akm_disable(struct drm_panel *panel)
+{
+	struct acx565akm_device *lcd = to_acx565akm_device(panel);
+
+	mutex_lock(&lcd->mutex);
+	acx565akm_power_off(lcd);
+	mutex_unlock(&lcd->mutex);
+
+	return 0;
+}
+
+static int acx565akm_enable(struct drm_panel *panel)
+{
+	struct acx565akm_device *lcd = to_acx565akm_device(panel);
+
+	mutex_lock(&lcd->mutex);
+	acx565akm_power_on(lcd);
+	mutex_unlock(&lcd->mutex);
+
+	return 0;
+}
+
+static const struct drm_display_mode acx565akm_mode = {
+	.clock = 24000,
+	.hdisplay = 800,
+	.hsync_start = 800 + 28,
+	.hsync_end = 800 + 28 + 4,
+	.htotal = 800 + 28 + 4 + 24,
+	.vdisplay = 480,
+	.vsync_start = 480 + 3,
+	.vsync_end = 480 + 3 + 3,
+	.vtotal = 480 + 3 + 3 + 4,
+	.vrefresh = 57,
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int acx565akm_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(panel->drm, &acx565akm_mode);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 77;
+	connector->display_info.height_mm = 46;
+	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
+					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs acx565akm_funcs = {
+	.disable = acx565akm_disable,
+	.enable = acx565akm_enable,
+	.get_modes = acx565akm_get_modes,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe, Detect and Remove
+ */
+
+static int acx565akm_detect(struct acx565akm_device *lcd)
+{
+	__be32 value;
+	u32 status;
+	int ret = 0;
+
+	/*
+	 * After being taken out of reset the panel needs 5ms before the first
+	 * command can be sent.
+	 */
+	gpiod_set_value(lcd->reset_gpio, 1);
+	usleep_range(5000, 10000);
+
+	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_STATUS, (u8 *)&value, 4);
+	status = __be32_to_cpu(value);
+	lcd->enabled = (status & (1 << 17)) && (status & (1 << 10));
+
+	dev_dbg(&lcd->spi->dev,
+		"LCD panel %s by bootloader (status 0x%04x)\n",
+		lcd->enabled ? "enabled" : "disabled ", status);
+
+	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_ID, lcd->display_id, 3);
+	dev_dbg(&lcd->spi->dev, "MIPI display ID: %02x%02x%02x\n",
+		lcd->display_id[0], lcd->display_id[1], lcd->display_id[2]);
+
+	switch (lcd->display_id[0]) {
+	case 0x10:
+		lcd->model = MIPID_VER_ACX565AKM;
+		lcd->name = "acx565akm";
+		lcd->has_bc = 1;
+		lcd->has_cabc = 1;
+		break;
+	case 0x29:
+		lcd->model = MIPID_VER_L4F00311;
+		lcd->name = "l4f00311";
+		break;
+	case 0x45:
+		lcd->model = MIPID_VER_LPH8923;
+		lcd->name = "lph8923";
+		break;
+	case 0x83:
+		lcd->model = MIPID_VER_LS041Y3;
+		lcd->name = "ls041y3";
+		break;
+	default:
+		lcd->name = "unknown";
+		dev_err(&lcd->spi->dev, "unknown display ID\n");
+		ret = -ENODEV;
+		goto done;
+	}
+
+	lcd->revision = lcd->display_id[1];
+
+	dev_info(&lcd->spi->dev, "%s rev %02x panel detected\n",
+		 lcd->name, lcd->revision);
+
+done:
+	if (!lcd->enabled)
+		gpiod_set_value(lcd->reset_gpio, 0);
+
+	return ret;
+}
+
+static int acx565akm_probe(struct spi_device *spi)
+{
+	struct acx565akm_device *lcd;
+	int ret;
+
+	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+	if (lcd == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, lcd);
+	spi->mode = SPI_MODE_3;
+
+	lcd->spi = spi;
+	mutex_init(&lcd->mutex);
+
+	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(lcd->reset_gpio)) {
+		dev_err(&spi->dev, "failed to get reset GPIO\n");
+		return PTR_ERR(lcd->reset_gpio);
+	}
+
+	ret = acx565akm_detect(lcd);
+	if (ret < 0) {
+		dev_err(&spi->dev, "panel detection failed\n");
+		return ret;
+	}
+
+	if (lcd->has_bc) {
+		ret = acx565akm_backlight_init(lcd);
+		if (ret < 0)
+			return ret;
+	}
+
+	drm_panel_init(&lcd->panel);
+	lcd->panel.dev = &lcd->spi->dev;
+	lcd->panel.funcs = &acx565akm_funcs;
+
+	ret = drm_panel_add(&lcd->panel);
+	if (ret < 0) {
+		if (lcd->has_bc)
+			acx565akm_backlight_cleanup(lcd);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int acx565akm_remove(struct spi_device *spi)
+{
+	struct acx565akm_device *lcd = spi_get_drvdata(spi);
+
+	drm_panel_remove(&lcd->panel);
+
+	if (lcd->has_bc)
+		acx565akm_backlight_cleanup(lcd);
+
+	acx565akm_disable(&lcd->panel);
+
+	return 0;
+}
+
+static const struct of_device_id acx565akm_of_match[] = {
+	{ .compatible = "sony,acx565akm", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, acx565akm_of_match);
+
+static struct spi_driver acx565akm_driver = {
+	.probe		= acx565akm_probe,
+	.remove		= acx565akm_remove,
+	.driver		= {
+		.name	= "panel-sony-acx565akm",
+		.of_match_table = acx565akm_of_match,
+	},
+};
+
+module_spi_driver(acx565akm_driver);
+
+MODULE_ALIAS("spi:sony,acx565akm");
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("Sony ACX565AKM LCD Panel Driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (17 preceding siblings ...)
  2019-07-07 18:18   ` [PATCH 22/60] drm/panel: Add driver for the Sony ACX565AKM panel Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-10  7:48     ` Sam Ravnborg
  2019-07-07 18:19   ` [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel Laurent Pinchart
                     ` (37 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Sam Ravnborg, Sebastian Reichel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

This panel is used on the OpenMoko Neo FreeRunner and Neo 1973.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/panel/Kconfig                |   8 +
 drivers/gpu/drm/panel/Makefile               |   1 +
 drivers/gpu/drm/panel/panel-tpo-td028ttec1.c | 382 +++++++++++++++++++
 3 files changed, 391 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-tpo-td028ttec1.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index dc10f727689a..b7099d211061 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -304,6 +304,14 @@ config DRM_PANEL_SONY_ACX565AKM
 	  Say Y here if you want to enable support for the Sony ACX565AKM
 	  800x600 3.5" panel.
 
+config DRM_PANEL_TPO_TD028TTEC1
+	tristate "TPO TD028TTEC1 panel driver"
+	depends on OF && SPI
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y here if you want to enable support for TPO TD028TTEC1 480x640
+	  2.8" panel.
+
 config DRM_PANEL_TPO_TPG110
 	tristate "TPO TPG 800x400 panel"
 	depends on OF && SPI && GPIOLIB
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 84cbc069f6cd..a1d4de64c0b1 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -32,5 +32,6 @@ obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
+obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o
 obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
 obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
diff --git a/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
new file mode 100644
index 000000000000..05af9ea6339c
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Toppoly TD028TTEC1 Panel Driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific panel-tpo-td028ttec1 driver
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ *
+ * Neo 1973 code (jbt6k74.c):
+ * Copyright (C) 2006-2007 OpenMoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ *
+ * Ported and adapted from Neo 1973 U-Boot by:
+ * H. Nikolaus Schaller <hns@goldelico.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define JBT_COMMAND			0x000
+#define JBT_DATA			0x100
+
+#define JBT_REG_SLEEP_IN		0x10
+#define JBT_REG_SLEEP_OUT		0x11
+
+#define JBT_REG_DISPLAY_OFF		0x28
+#define JBT_REG_DISPLAY_ON		0x29
+
+#define JBT_REG_RGB_FORMAT		0x3a
+#define JBT_REG_QUAD_RATE		0x3b
+
+#define JBT_REG_POWER_ON_OFF		0xb0
+#define JBT_REG_BOOSTER_OP		0xb1
+#define JBT_REG_BOOSTER_MODE		0xb2
+#define JBT_REG_BOOSTER_FREQ		0xb3
+#define JBT_REG_OPAMP_SYSCLK		0xb4
+#define JBT_REG_VSC_VOLTAGE		0xb5
+#define JBT_REG_VCOM_VOLTAGE		0xb6
+#define JBT_REG_EXT_DISPL		0xb7
+#define JBT_REG_OUTPUT_CONTROL		0xb8
+#define JBT_REG_DCCLK_DCEV		0xb9
+#define JBT_REG_DISPLAY_MODE1		0xba
+#define JBT_REG_DISPLAY_MODE2		0xbb
+#define JBT_REG_DISPLAY_MODE		0xbc
+#define JBT_REG_ASW_SLEW		0xbd
+#define JBT_REG_DUMMY_DISPLAY		0xbe
+#define JBT_REG_DRIVE_SYSTEM		0xbf
+
+#define JBT_REG_SLEEP_OUT_FR_A		0xc0
+#define JBT_REG_SLEEP_OUT_FR_B		0xc1
+#define JBT_REG_SLEEP_OUT_FR_C		0xc2
+#define JBT_REG_SLEEP_IN_LCCNT_D	0xc3
+#define JBT_REG_SLEEP_IN_LCCNT_E	0xc4
+#define JBT_REG_SLEEP_IN_LCCNT_F	0xc5
+#define JBT_REG_SLEEP_IN_LCCNT_G	0xc6
+
+#define JBT_REG_GAMMA1_FINE_1		0xc7
+#define JBT_REG_GAMMA1_FINE_2		0xc8
+#define JBT_REG_GAMMA1_INCLINATION	0xc9
+#define JBT_REG_GAMMA1_BLUE_OFFSET	0xca
+
+#define JBT_REG_BLANK_CONTROL		0xcf
+#define JBT_REG_BLANK_TH_TV		0xd0
+#define JBT_REG_CKV_ON_OFF		0xd1
+#define JBT_REG_CKV_1_2			0xd2
+#define JBT_REG_OEV_TIMING		0xd3
+#define JBT_REG_ASW_TIMING_1		0xd4
+#define JBT_REG_ASW_TIMING_2		0xd5
+
+#define JBT_REG_HCLOCK_VGA		0xec
+#define JBT_REG_HCLOCK_QVGA		0xed
+
+struct td028ttec1_device {
+	struct drm_panel panel;
+
+	struct spi_device *spi;
+	struct backlight_device *backlight;
+};
+
+#define to_td028ttec1_device(p) container_of(p, struct td028ttec1_device, panel)
+
+static int jbt_ret_write_0(struct td028ttec1_device *lcd, u8 reg, int *err)
+{
+	struct spi_device *spi = lcd->spi;
+	u16 tx_buf = JBT_COMMAND | reg;
+	int ret;
+
+	if (err && *err)
+		return *err;
+
+	ret = spi_write(spi, (u8 *)&tx_buf, sizeof(tx_buf));
+	if (ret < 0) {
+		dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret);
+		if (err)
+			*err = ret;
+	}
+
+	return ret;
+}
+
+static int jbt_reg_write_1(struct td028ttec1_device *lcd,
+			   u8 reg, u8 data, int *err)
+{
+	struct spi_device *spi = lcd->spi;
+	u16 tx_buf[2];
+	int ret;
+
+	if (err && *err)
+		return *err;
+
+	tx_buf[0] = JBT_COMMAND | reg;
+	tx_buf[1] = JBT_DATA | data;
+
+	ret = spi_write(spi, (u8 *)tx_buf, sizeof(tx_buf));
+	if (ret < 0) {
+		dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret);
+		if (err)
+			*err = ret;
+	}
+
+	return ret;
+}
+
+static int jbt_reg_write_2(struct td028ttec1_device *lcd,
+			   u8 reg, u16 data, int *err)
+{
+	struct spi_device *spi = lcd->spi;
+	u16 tx_buf[3];
+	int ret;
+
+	if (err && *err)
+		return *err;
+
+	tx_buf[0] = JBT_COMMAND | reg;
+	tx_buf[1] = JBT_DATA | (data >> 8);
+	tx_buf[2] = JBT_DATA | (data & 0xff);
+
+	ret = spi_write(spi, (u8 *)tx_buf, sizeof(tx_buf));
+	if (ret < 0) {
+		dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret);
+		if (err)
+			*err = ret;
+	}
+
+	return ret;
+}
+
+static int td028ttec1_disable(struct drm_panel *panel)
+{
+	struct td028ttec1_device *lcd = to_td028ttec1_device(panel);
+
+	backlight_disable(lcd->backlight);
+
+	jbt_ret_write_0(lcd, JBT_REG_DISPLAY_OFF, NULL);
+	jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, 0x8002, NULL);
+	jbt_ret_write_0(lcd, JBT_REG_SLEEP_IN, NULL);
+	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x00, NULL);
+
+	return 0;
+}
+
+static int td028ttec1_enable(struct drm_panel *panel)
+{
+	struct td028ttec1_device *lcd = to_td028ttec1_device(panel);
+	unsigned int i;
+	int ret = 0;
+
+	/* Three times command zero */
+	for (i = 0; i < 3; ++i) {
+		jbt_ret_write_0(lcd, 0x00, &ret);
+		usleep_range(1000, 2000);
+	}
+
+	if (ret)
+		return ret;
+
+	/* deep standby out */
+	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x17, &ret);
+
+	/* RGB I/F on, RAM write off, QVGA through, SIGCON enable */
+	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE, 0x80, &ret);
+
+	/* Quad mode off */
+	jbt_reg_write_1(lcd, JBT_REG_QUAD_RATE, 0x00, &ret);
+
+	/* AVDD on, XVDD on */
+	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x16, &ret);
+
+	/* Output control */
+	jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, 0xfff9, &ret);
+
+	/* Sleep mode off */
+	jbt_ret_write_0(lcd, JBT_REG_SLEEP_OUT, &ret);
+
+	/* at this point we have like 50% grey */
+
+	/* initialize register set */
+	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE1, 0x01, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE2, 0x00, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_RGB_FORMAT, 0x60, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_DRIVE_SYSTEM, 0x10, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_OP, 0x56, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_MODE, 0x33, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_OPAMP_SYSCLK, 0x02, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_VSC_VOLTAGE, 0x2b, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_VCOM_VOLTAGE, 0x40, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_EXT_DISPL, 0x03, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_DCCLK_DCEV, 0x04, &ret);
+	/*
+	 * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement
+	 * to avoid red / blue flicker
+	 */
+	jbt_reg_write_1(lcd, JBT_REG_ASW_SLEW, 0x04, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_DUMMY_DISPLAY, 0x00, &ret);
+
+	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_A, 0x11, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_B, 0x11, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_C, 0x11, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0, &ret);
+
+	jbt_reg_write_2(lcd, JBT_REG_GAMMA1_FINE_1, 0x5533, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_FINE_2, 0x00, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_INCLINATION, 0x00, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00, &ret);
+
+	jbt_reg_write_2(lcd, JBT_REG_HCLOCK_VGA, 0x1f0, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_BLANK_CONTROL, 0x02, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_BLANK_TH_TV, 0x0804, &ret);
+
+	jbt_reg_write_1(lcd, JBT_REG_CKV_ON_OFF, 0x01, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_CKV_1_2, 0x0000, &ret);
+
+	jbt_reg_write_2(lcd, JBT_REG_OEV_TIMING, 0x0d0e, &ret);
+	jbt_reg_write_2(lcd, JBT_REG_ASW_TIMING_1, 0x11a4, &ret);
+	jbt_reg_write_1(lcd, JBT_REG_ASW_TIMING_2, 0x0e, &ret);
+
+	jbt_ret_write_0(lcd, JBT_REG_DISPLAY_ON, &ret);
+
+	if (ret)
+		return ret;
+
+	backlight_enable(lcd->backlight);
+
+	return 0;
+}
+
+static const struct drm_display_mode td028ttec1_mode = {
+	.clock = 22153,
+	.hdisplay = 480,
+	.hsync_start = 480 + 24,
+	.hsync_end = 480 + 24 + 8,
+	.htotal = 480 + 24 + 8 + 8,
+	.vdisplay = 640,
+	.vsync_start = 640 + 4,
+	.vsync_end = 640 + 4 + 2,
+	.vtotal = 640 + 4 + 2 + 2,
+	.vrefresh = 66,
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int td028ttec1_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(panel->drm, &td028ttec1_mode);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 43;
+	connector->display_info.height_mm = 58;
+	/*
+	 * FIXME: According to the datasheet sync signals are sampled on the
+	 * rising edge of the clock, but the code running on the OpenMoko Neo
+	 * FreeRunner and Neo 1973 indicates sampling on the falling edge. This
+	 * should be tested on a real device.
+	 */
+	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
+					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs td028ttec1_funcs = {
+	.disable = td028ttec1_disable,
+	.enable = td028ttec1_enable,
+	.get_modes = td028ttec1_get_modes,
+};
+
+static int td028ttec1_probe(struct spi_device *spi)
+{
+	struct td028ttec1_device *lcd;
+	int ret;
+
+	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+	if (lcd == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, lcd);
+	lcd->spi = spi;
+
+	lcd->backlight = devm_of_find_backlight(&spi->dev);
+	if (IS_ERR(lcd->backlight))
+		return PTR_ERR(lcd->backlight);
+
+	spi->mode = SPI_MODE_3;
+	spi->bits_per_word = 9;
+
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+		return ret;
+	}
+
+	drm_panel_init(&lcd->panel);
+	lcd->panel.dev = &lcd->spi->dev;
+	lcd->panel.funcs = &td028ttec1_funcs;
+
+	return drm_panel_add(&lcd->panel);
+}
+
+static int td028ttec1_remove(struct spi_device *spi)
+{
+	struct td028ttec1_device *lcd = spi_get_drvdata(spi);
+
+	drm_panel_remove(&lcd->panel);
+	td028ttec1_disable(&lcd->panel);
+
+	return 0;
+}
+
+static const struct of_device_id td028ttec1_of_match[] = {
+	{ .compatible = "tpo,td028ttec1", },
+	/* DT backward compatibility. */
+	{ .compatible = "toppoly,td028ttec1", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, td028ttec1_of_match);
+
+static const struct spi_device_id td028ttec1_ids[] = {
+	{ "tpo,td028ttec1", 0},
+	{ "toppoly,td028ttec1", 0 },
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(spi, td028ttec1_ids);
+
+static struct spi_driver td028ttec1_driver = {
+	.probe		= td028ttec1_probe,
+	.remove		= td028ttec1_remove,
+	.id_table	= td028ttec1_ids,
+	.driver		= {
+		.name   = "panel-tpo-td028ttec1",
+		.of_match_table = td028ttec1_of_match,
+	},
+};
+
+module_spi_driver(td028ttec1_driver);
+
+MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
+MODULE_DESCRIPTION("Toppoly TD028TTEC1 panel driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (18 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-10 13:09     ` Sam Ravnborg
  2019-07-07 18:19   ` [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges Laurent Pinchart
                     ` (36 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel
  Cc: Maxime Ripard, Sam Ravnborg, Sebastian Reichel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

This panel is used on the OMAP3 Pandora.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/panel/Kconfig                |   7 +
 drivers/gpu/drm/panel/Makefile               |   1 +
 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c | 510 +++++++++++++++++++
 3 files changed, 518 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c

diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index b7099d211061..8f3660c73044 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -312,6 +312,13 @@ config DRM_PANEL_TPO_TD028TTEC1
 	  Say Y here if you want to enable support for TPO TD028TTEC1 480x640
 	  2.8" panel.
 
+config DRM_PANEL_TPO_TD043MTEA1
+	tristate "TPO TD043MTEA1 panel driver"
+	depends on GPIOLIB && OF && REGULATOR && SPI
+	help
+	  Say Y here if you want to enable support for TPO TD043MTEA1 800x480
+	  4.3" panel.
+
 config DRM_PANEL_TPO_TPG110
 	tristate "TPO TPG 800x400 panel"
 	depends on OF && SPI && GPIOLIB
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index a1d4de64c0b1..bf76fcea7d22 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -33,5 +33,6 @@ obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
 obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
 obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o
+obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o
 obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
 obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
diff --git a/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
new file mode 100644
index 000000000000..6b17e47582b8
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Toppology TD043MTEA1 Panel Driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific panel-tpo-td043mtea1 driver
+ *
+ * Author: Gražvydas Ignotas <notasas@gmail.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define TPO_R02_MODE(x)			((x) & 7)
+#define TPO_R02_MODE_800x480		7
+#define TPO_R02_NCLK_RISING		BIT(3)
+#define TPO_R02_HSYNC_HIGH		BIT(4)
+#define TPO_R02_VSYNC_HIGH		BIT(5)
+
+#define TPO_R03_NSTANDBY		BIT(0)
+#define TPO_R03_EN_CP_CLK		BIT(1)
+#define TPO_R03_EN_VGL_PUMP		BIT(2)
+#define TPO_R03_EN_PWM			BIT(3)
+#define TPO_R03_DRIVING_CAP_100		BIT(4)
+#define TPO_R03_EN_PRE_CHARGE		BIT(6)
+#define TPO_R03_SOFTWARE_CTL		BIT(7)
+
+#define TPO_R04_NFLIP_H			BIT(0)
+#define TPO_R04_NFLIP_V			BIT(1)
+#define TPO_R04_CP_CLK_FREQ_1H		BIT(2)
+#define TPO_R04_VGL_FREQ_1H		BIT(4)
+
+#define TPO_R03_VAL_NORMAL \
+	(TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | TPO_R03_EN_VGL_PUMP | \
+	 TPO_R03_EN_PWM | TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \
+	 TPO_R03_SOFTWARE_CTL)
+
+#define TPO_R03_VAL_STANDBY \
+	(TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \
+	 TPO_R03_SOFTWARE_CTL)
+
+static const u16 td043mtea1_def_gamma[12] = {
+	105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023
+};
+
+struct td043mtea1_device {
+	struct drm_panel panel;
+
+	struct spi_device *spi;
+	struct regulator *vcc_reg;
+	struct gpio_desc *reset_gpio;
+
+	unsigned int mode;
+	u16 gamma[12];
+	bool vmirror;
+	bool powered_on;
+	bool spi_suspended;
+	bool power_on_resume;
+};
+
+#define to_td043mtea1_device(p) container_of(p, struct td043mtea1_device, panel)
+
+/* -----------------------------------------------------------------------------
+ * Hardware Access
+ */
+
+static int td043mtea1_write(struct td043mtea1_device *lcd, u8 addr, u8 value)
+{
+	struct spi_message msg;
+	struct spi_transfer xfer;
+	u16 data;
+	int ret;
+
+	spi_message_init(&msg);
+
+	memset(&xfer, 0, sizeof(xfer));
+
+	data = ((u16)addr << 10) | (1 << 8) | value;
+	xfer.tx_buf = &data;
+	xfer.bits_per_word = 16;
+	xfer.len = 2;
+	spi_message_add_tail(&xfer, &msg);
+
+	ret = spi_sync(lcd->spi, &msg);
+	if (ret < 0)
+		dev_warn(&lcd->spi->dev, "failed to write to LCD reg (%d)\n",
+			 ret);
+
+	return ret;
+}
+
+static void td043mtea1_write_gamma(struct td043mtea1_device *lcd)
+{
+	const u16 *gamma = lcd->gamma;
+	unsigned int i;
+	u8 val;
+
+	/* gamma bits [9:8] */
+	for (val = i = 0; i < 4; i++)
+		val |= (gamma[i] & 0x300) >> ((i + 1) * 2);
+	td043mtea1_write(lcd, 0x11, val);
+
+	for (val = i = 0; i < 4; i++)
+		val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2);
+	td043mtea1_write(lcd, 0x12, val);
+
+	for (val = i = 0; i < 4; i++)
+		val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2);
+	td043mtea1_write(lcd, 0x13, val);
+
+	/* gamma bits [7:0] */
+	for (val = i = 0; i < 12; i++)
+		td043mtea1_write(lcd, 0x14 + i, gamma[i] & 0xff);
+}
+
+static int td043mtea1_write_mirror(struct td043mtea1_device *lcd)
+{
+	u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V |
+		TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H;
+	if (lcd->vmirror)
+		reg4 &= ~TPO_R04_NFLIP_V;
+
+	return td043mtea1_write(lcd, 4, reg4);
+}
+
+static int td043mtea1_power_on(struct td043mtea1_device *lcd)
+{
+	int ret;
+
+	if (lcd->powered_on)
+		return 0;
+
+	ret = regulator_enable(lcd->vcc_reg);
+	if (ret < 0)
+		return ret;
+
+	/* Wait for the panel to stabilize. */
+	msleep(160);
+
+	gpiod_set_value(lcd->reset_gpio, 0);
+
+	td043mtea1_write(lcd, 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING);
+	td043mtea1_write(lcd, 3, TPO_R03_VAL_NORMAL);
+	td043mtea1_write(lcd, 0x20, 0xf0);
+	td043mtea1_write(lcd, 0x21, 0xf0);
+	td043mtea1_write_mirror(lcd);
+	td043mtea1_write_gamma(lcd);
+
+	lcd->powered_on = true;
+
+	return 0;
+}
+
+static void td043mtea1_power_off(struct td043mtea1_device *lcd)
+{
+	if (!lcd->powered_on)
+		return;
+
+	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
+
+	gpiod_set_value(lcd->reset_gpio, 1);
+
+	/* wait for at least 2 vsyncs before cutting off power */
+	msleep(50);
+
+	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY);
+
+	regulator_disable(lcd->vcc_reg);
+
+	lcd->powered_on = false;
+}
+
+/* -----------------------------------------------------------------------------
+ * sysfs
+ */
+
+static ssize_t vmirror_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", lcd->vmirror);
+}
+
+static ssize_t vmirror_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+	int val;
+	int ret;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret < 0)
+		return ret;
+
+	lcd->vmirror = !!val;
+
+	ret = td043mtea1_write_mirror(lcd);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", lcd->mode);
+}
+
+static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+	long val;
+	int ret;
+
+	ret = kstrtol(buf, 0, &val);
+	if (ret != 0 || val & ~7)
+		return -EINVAL;
+
+	lcd->mode = val;
+
+	val |= TPO_R02_NCLK_RISING;
+	td043mtea1_write(lcd, 2, val);
+
+	return count;
+}
+
+static ssize_t gamma_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+	ssize_t len = 0;
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) {
+		ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
+				lcd->gamma[i]);
+		if (ret < 0)
+			return ret;
+		len += ret;
+	}
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static ssize_t gamma_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+	unsigned int g[12];
+	unsigned int i;
+	int ret;
+
+	ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u",
+			&g[0], &g[1], &g[2], &g[3], &g[4], &g[5],
+			&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
+	if (ret != 12)
+		return -EINVAL;
+
+	for (i = 0; i < 12; i++)
+		lcd->gamma[i] = g[i];
+
+	td043mtea1_write_gamma(lcd);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(vmirror);
+static DEVICE_ATTR_RW(mode);
+static DEVICE_ATTR_RW(gamma);
+
+static struct attribute *td043mtea1_attrs[] = {
+	&dev_attr_vmirror.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_gamma.attr,
+	NULL,
+};
+
+static const struct attribute_group td043mtea1_attr_group = {
+	.attrs = td043mtea1_attrs,
+};
+
+/* -----------------------------------------------------------------------------
+ * Bridge Operations
+ */
+
+static int td043mtea1_disable(struct drm_panel *panel)
+{
+	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
+
+	if (!lcd->spi_suspended)
+		td043mtea1_power_off(lcd);
+
+	return 0;
+}
+
+static int td043mtea1_enable(struct drm_panel *panel)
+{
+	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
+	int ret;
+
+	/*
+	 * If we are resuming from system suspend, SPI might not be enabled
+	 * yet, so we'll program the LCD from SPI PM resume callback.
+	 */
+	if (lcd->spi_suspended)
+		return 0;
+
+	ret = td043mtea1_power_on(lcd);
+	if (ret) {
+		dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n",
+			__func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct drm_display_mode td043mtea1_mode = {
+	.clock = 36000,
+	.hdisplay = 800,
+	.hsync_start = 800 + 68,
+	.hsync_end = 800 + 68 + 1,
+	.htotal = 800 + 68 + 1 + 214,
+	.vdisplay = 480,
+	.vsync_start = 480 + 39,
+	.vsync_end = 480 + 39 + 1,
+	.vtotal = 480 + 39 + 1 + 34,
+	.vrefresh = 60,
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int td043mtea1_get_modes(struct drm_panel *panel)
+{
+	struct drm_connector *connector = panel->connector;
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(panel->drm, &td043mtea1_mode);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 94;
+	connector->display_info.height_mm = 56;
+	/*
+	 * FIXME: According to the datasheet sync signals are sampled on the
+	 * rising edge of the clock, but the code running on the OMAP3 Pandora
+	 * indicates sampling on the falling edge. This should be tested on a
+	 * real device.
+	 */
+	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
+					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
+					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs td043mtea1_funcs = {
+	.disable = td043mtea1_disable,
+	.enable = td043mtea1_enable,
+	.get_modes = td043mtea1_get_modes,
+};
+
+/* -----------------------------------------------------------------------------
+ * Power Management, Probe and Remove
+ */
+
+#ifdef CONFIG_PM_SLEEP
+static int td043mtea1_suspend(struct device *dev)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+
+	if (lcd->powered_on) {
+		td043mtea1_power_off(lcd);
+		lcd->powered_on = true;
+	}
+
+	lcd->spi_suspended = true;
+
+	return 0;
+}
+
+static int td043mtea1_resume(struct device *dev)
+{
+	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
+	int ret;
+
+	lcd->spi_suspended = false;
+
+	if (lcd->powered_on) {
+		lcd->powered_on = false;
+		ret = td043mtea1_power_on(lcd);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend,
+			 td043mtea1_resume);
+#endif
+
+static int td043mtea1_probe(struct spi_device *spi)
+{
+	struct td043mtea1_device *lcd;
+	int ret;
+
+	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
+	if (lcd == NULL)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, lcd);
+	lcd->spi = spi;
+	lcd->mode = TPO_R02_MODE_800x480;
+	memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma));
+
+	lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
+	if (IS_ERR(lcd->vcc_reg)) {
+		dev_err(&spi->dev, "failed to get VCC regulator\n");
+		return PTR_ERR(lcd->vcc_reg);
+	}
+
+	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(lcd->reset_gpio)) {
+		dev_err(&spi->dev, "failed to get reset GPIO\n");
+		return PTR_ERR(lcd->reset_gpio);
+	}
+
+	spi->bits_per_word = 16;
+	spi->mode = SPI_MODE_0;
+
+	ret = spi_setup(spi);
+	if (ret < 0) {
+		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
+		return ret;
+	}
+
+	ret = sysfs_create_group(&spi->dev.kobj, &td043mtea1_attr_group);
+	if (ret < 0) {
+		dev_err(&spi->dev, "failed to create sysfs files\n");
+		return ret;
+	}
+
+	drm_panel_init(&lcd->panel);
+	lcd->panel.dev = &lcd->spi->dev;
+	lcd->panel.funcs = &td043mtea1_funcs;
+
+	ret = drm_panel_add(&lcd->panel);
+	if (ret < 0) {
+		sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int td043mtea1_remove(struct spi_device *spi)
+{
+	struct td043mtea1_device *lcd = spi_get_drvdata(spi);
+
+	drm_panel_remove(&lcd->panel);
+	td043mtea1_disable(&lcd->panel);
+
+	sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
+
+	return 0;
+}
+
+static const struct of_device_id td043mtea1_of_match[] = {
+	{ .compatible = "tpo,td043mtea1", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, td043mtea1_of_match);
+
+static struct spi_driver td043mtea1_driver = {
+	.probe		= td043mtea1_probe,
+	.remove		= td043mtea1_remove,
+	.driver		= {
+		.name	= "panel-tpo-td043mtea1",
+#ifdef CONFIG_PM_SLEEP
+		.pm	= &td043mtea1_pm_ops,
+#endif
+		.of_match_table = td043mtea1_of_match,
+	},
+};
+
+module_spi_driver(td043mtea1_driver);
+
+MODULE_ALIAS("spi:tpo,td043mtea1");
+MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
+MODULE_DESCRIPTION("TPO TD043MTEA1 Panel Driver");
+MODULE_LICENSE("GPL");
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (19 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-18 17:01     ` Daniel Vetter
  2019-07-07 18:19   ` [PATCH 26/60] drm/omap: Detach from panels at remove time Laurent Pinchart
                     ` (35 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Most bridge drivers create a DRM connector to model the connector at the
output of the bridge. This model is historical and has worked pretty
well so far, but causes several issues:

- It prevents supporting more complex display pipelines where DRM
connector operations are split over multiple components. For instance a
pipeline with a bridge connected to the DDC signals to read EDID data,
and another one connected to the HPD signal to detect connection and
disconnection, will not be possible to support through this model.

- It requires every bridge driver to implement similar connector
handling code, resulting in code duplication.

- It assumes that a bridge will either be wired to a connector or to
another bridge, but doesn't support bridges that can be used in both
positions very well (although there is some ad-hoc support for this in
the analogix_dp bridge driver).

In order to solve these issues, ownership of the connector needs to be
moved to the display controller driver.

To avoid code duplication in display controller drivers, add a new
helper to create and manage a DRM connector backed by a chain of
bridges. All connector operations are delegating to the appropriate
bridge in the chain.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/Makefile               |   3 +-
 drivers/gpu/drm/drm_bridge_connector.c | 385 +++++++++++++++++++++++++
 include/drm/drm_bridge_connector.h     |  18 ++
 3 files changed, 405 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
 create mode 100644 include/drm/drm_bridge_connector.h

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 9f0d2ee35794..1b74653c9db9 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -37,7 +37,8 @@ drm_vram_helper-y := drm_gem_vram_helper.o \
 		     drm_vram_mm_helper.o
 obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
 
-drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper.o \
+drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
+		drm_dsc.o drm_probe_helper.o \
 		drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
 		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
 		drm_simple_kms_helper.o drm_modeset_helper.o \
diff --git a/drivers/gpu/drm/drm_bridge_connector.c b/drivers/gpu/drm/drm_bridge_connector.c
new file mode 100644
index 000000000000..09f2d6bfb561
--- /dev/null
+++ b/drivers/gpu/drm/drm_bridge_connector.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_device.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_probe_helper.h>
+
+/**
+ * DOC: overview
+ *
+ * The DRM bridge connector helper object provides a DRM connector
+ * implementation that wraps a chain of &struct drm_bridge. The connector
+ * operations are fully implemented based on the operations of the bridges in
+ * the chain, and don't require any intervention from the display controller
+ * driver at runtime.
+ *
+ * To use the helper, display controller drivers create a bridge connector with
+ * a call to drm_bridge_connector_init(). This associates the newly created
+ * connector with the chain of bridges passed to the function and registers it
+ * with the DRM device. At that point the connector becomes fully usable, no
+ * further operation is needed.
+ *
+ * The DRM bridge connector operations are implemented based on the operations
+ * provided by the bridges in the chain. Each connector operation is delegated
+ * to the bridge closest to the connector (at the end of the chain) that
+ * provides the relevant functionality.
+ *
+ * To make use of this helper, all bridges in the chain shall report bridge
+ * operation flags (&drm_bridge->ops) and bridge output type
+ * (&drm_bridge->type), and none of them may create a DRM connector directly.
+ */
+
+/**
+ * struct drm_bridge_connector - A connector backed by a chain of bridges
+ */
+struct drm_bridge_connector {
+	/**
+	 * @base: The base DRM connector
+	 */
+	struct drm_connector base;
+	/**
+	 * @bridge:
+	 *
+	 * The first bridge in the chain (connected to the output of the CRTC).
+	 */
+	struct drm_bridge *bridge;
+	/**
+	 * @bridge_edid:
+	 *
+	 * The last bridge in the chain (closest to the connector) that provides
+	 * EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
+	 */
+	struct drm_bridge *bridge_edid;
+	/**
+	 * @bridge_hpd:
+	 *
+	 * The last bridge in the chain (closest to the connector) that provides
+	 * hot-plug detection notification, if any (see &DRM_BRIDGE_OP_HPD).
+	 */
+	struct drm_bridge *bridge_hpd;
+	/**
+	 * @bridge_detect:
+	 *
+	 * The last bridge in the chain (closest to the connector) that provides
+	 * connector detection, if any (see &DRM_BRIDGE_OP_DETECT).
+	 */
+	struct drm_bridge *bridge_detect;
+	/**
+	 * @bridge_detect:
+	 *
+	 * The last bridge in the chain (closest to the connector) that provides
+	 * connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
+	 */
+	struct drm_bridge *bridge_modes;
+	/**
+	 * @hdmi_mode: Valid for HDMI connectors only.
+	 */
+	bool hdmi_mode;
+};
+
+#define to_drm_bridge_connector(x) \
+	container_of(x, struct drm_bridge_connector, base)
+
+/* -----------------------------------------------------------------------------
+ * Bridge Connector Hot-Plug Handling
+ */
+
+static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
+					    enum drm_connector_status status)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *bridge;
+
+	if (status != connector_status_disconnected)
+		return;
+
+	/*
+	 * Notify all bridges in the pipeline of disconnection. This is required
+	 * to let the HDMI encoders reset their internal state related to
+	 * connection status, such as the CEC address.
+	 */
+	for (bridge = bridge_connector->bridge; bridge; bridge = bridge->next) {
+		if (bridge->funcs->lost_hotplug)
+			bridge->funcs->lost_hotplug(bridge);
+	}
+}
+
+static void drm_bridge_connector_hpd_cb(void *cb_data,
+					enum drm_connector_status status)
+{
+	struct drm_bridge_connector *drm_bridge_connector = cb_data;
+	struct drm_connector *connector = &drm_bridge_connector->base;
+	struct drm_device *dev = connector->dev;
+	enum drm_connector_status old_status;
+
+	mutex_lock(&dev->mode_config.mutex);
+	old_status = connector->status;
+	connector->status = status;
+	mutex_unlock(&dev->mode_config.mutex);
+
+	if (old_status == status)
+		return;
+
+	drm_bridge_connector_hpd_notify(connector, status);
+
+	drm_kms_helper_hotplug_event(dev);
+}
+
+/**
+ * drm_bridge_connector_enable_hpd - Enable hot-plug detection for the connector
+ * @connector: The DRM bridge connector
+ *
+ * This function enables hot-plug detection for the given bridge connector.
+ * This is typically used by display drivers in their resume handler.
+ */
+void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
+
+	if (hpd)
+		drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
+				      bridge_connector);
+}
+EXPORT_SYMBOL_GPL(drm_bridge_connector_enable_hpd);
+
+/**
+ * drm_bridge_connector_disable_hpd - Disable hot-plug detection for the
+ * connector
+ * @connector: The DRM bridge connector
+ *
+ * This function disables hot-plug detection for the given bridge connector.
+ * This is typically used by display drivers in their suspend handler.
+ */
+void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
+
+	if (hpd)
+		drm_bridge_hpd_disable(hpd);
+}
+EXPORT_SYMBOL_GPL(drm_bridge_connector_disable_hpd);
+
+/* -----------------------------------------------------------------------------
+ * Bridge Connector Functions
+ */
+
+static enum drm_connector_status
+drm_bridge_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *detect = bridge_connector->bridge_detect;
+	enum drm_connector_status status;
+
+	if (detect) {
+		status = detect->funcs->detect(detect);
+
+		drm_bridge_connector_hpd_notify(connector, status);
+	} else {
+		switch (connector->connector_type) {
+		case DRM_MODE_CONNECTOR_DPI:
+		case DRM_MODE_CONNECTOR_LVDS:
+		case DRM_MODE_CONNECTOR_DSI:
+			status = connector_status_connected;
+			break;
+		default:
+			status = connector_status_unknown;
+			break;
+		}
+	}
+
+	return status;
+}
+
+static void drm_bridge_connector_destroy(struct drm_connector *connector)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+
+	if (bridge_connector->bridge_hpd) {
+		struct drm_bridge *hpd = bridge_connector->bridge_hpd;
+
+		drm_bridge_hpd_disable(hpd);
+	}
+
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+
+	kfree(bridge_connector);
+}
+
+static const struct drm_connector_funcs drm_bridge_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = drm_bridge_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_bridge_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/* -----------------------------------------------------------------------------
+ * Bridge Connector Helper Functions
+ */
+
+#define MAX_EDID  512
+
+static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
+					       struct drm_bridge *bridge)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	enum drm_connector_status status;
+	struct edid *edid;
+	int n;
+
+	status = drm_bridge_connector_detect(connector, false);
+	if (status != connector_status_connected)
+		goto no_edid;
+
+	edid = bridge->funcs->get_edid(bridge, connector);
+	if (!edid || !drm_edid_is_valid(edid)) {
+		kfree(edid);
+		goto no_edid;
+	}
+
+	drm_connector_update_edid_property(connector, edid);
+	n = drm_add_edid_modes(connector, edid);
+
+	bridge_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
+
+	kfree(edid);
+	return n;
+
+no_edid:
+	drm_connector_update_edid_property(connector, NULL);
+	return 0;
+}
+
+static int drm_bridge_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_bridge_connector *bridge_connector =
+		to_drm_bridge_connector(connector);
+	struct drm_bridge *bridge;
+
+	/*
+	 * If display exposes EDID, then we parse that in the normal way to
+	 * build table of supported modes.
+	 */
+	bridge = bridge_connector->bridge_edid;
+	if (bridge)
+		return drm_bridge_connector_get_modes_edid(connector, bridge);
+
+	/*
+	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
+	 * resolution panel or an analog TV output), query it.
+	 */
+	bridge = bridge_connector->bridge_modes;
+	if (bridge)
+		return bridge->funcs->get_modes(bridge, connector);
+
+	/*
+	 * We can't retrieve modes, which can happen for instance for a DVI or
+	 * VGA output with the DDC bus unconnected. The KMS core will add the
+	 * default modes.
+	 */
+	return 0;
+}
+
+static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
+	.get_modes = drm_bridge_connector_get_modes,
+	/* No need for .mode_valid(), the bridges are checked by the core. */
+};
+
+/* -----------------------------------------------------------------------------
+ * Bridge Connector Initialisation
+ */
+
+/**
+ * drm_bridge_connector_init - Initialise a connector for a chain of bridges
+ * @drm: the DRM device
+ * @bridge: the bridge closest to the CRTC output
+ *
+ * Allocate, initialise and register a &drm_bridge_connector with the @drm
+ * device. The connector is associated with a chain of bridges that starts at
+ * the CRTC output with @bridge. All bridges in the chain shall report bridge
+ * operation flags (&drm_bridge->ops) and bridge output type
+ * (&drm_bridge->type), and none of them may create a DRM connector directly.
+ *
+ * Returns a pointer to the new connector on success, or a negative error
+ * pointer otherwise.
+ */
+struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
+						struct drm_bridge *bridge)
+{
+	struct drm_bridge_connector *bridge_connector;
+	struct drm_connector *connector;
+	int connector_type;
+
+	bridge_connector = kzalloc(sizeof(*bridge_connector), GFP_KERNEL);
+	if (!bridge_connector)
+		return ERR_PTR(-ENOMEM);
+
+	bridge_connector->bridge = bridge;
+
+	/*
+	 * Initialise connector status handling. First locate the furthest
+	 * bridges in the pipeline that support HPD and output detection. Then
+	 * initialise the connector polling mode, using HPD if available and
+	 * falling back to polling if supported. If neither HPD nor output
+	 * detection are available, we don't support hotplug detection at all.
+	 */
+	connector_type = DRM_MODE_CONNECTOR_Unknown;
+	for ( ; bridge; bridge = bridge->next) {
+		if (bridge->ops & DRM_BRIDGE_OP_EDID)
+			bridge_connector->bridge_edid = bridge;
+		if (bridge->ops & DRM_BRIDGE_OP_HPD)
+			bridge_connector->bridge_hpd = bridge;
+		if (bridge->ops & DRM_BRIDGE_OP_DETECT)
+			bridge_connector->bridge_detect = bridge;
+		if (bridge->ops & DRM_BRIDGE_OP_MODES)
+			bridge_connector->bridge_modes = bridge;
+
+		if (!bridge->next)
+			connector_type = bridge->type;
+	}
+
+	if (connector_type == DRM_MODE_CONNECTOR_Unknown) {
+		kfree(bridge_connector);
+		return ERR_PTR(-EINVAL);
+	}
+
+	/*
+	 * TODO: Handle interlace_allowed, doublescan_allowed, stereo_allowed
+	 * and ycbcr_420_allowed.
+	 */
+	connector = &bridge_connector->base;
+	drm_connector_init(drm, connector, &drm_bridge_connector_funcs,
+			   connector_type);
+	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
+
+	if (bridge_connector->bridge_hpd)
+		connector->polled = DRM_CONNECTOR_POLL_HPD;
+	else if (bridge_connector->bridge_detect)
+		connector->polled = DRM_CONNECTOR_POLL_CONNECT
+				  | DRM_CONNECTOR_POLL_DISCONNECT;
+
+	return connector;
+}
+EXPORT_SYMBOL_GPL(drm_bridge_connector_init);
diff --git a/include/drm/drm_bridge_connector.h b/include/drm/drm_bridge_connector.h
new file mode 100644
index 000000000000..ec33b44954b8
--- /dev/null
+++ b/include/drm/drm_bridge_connector.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#ifndef __DRM_BRIDGE_CONNECTOR_H__
+#define __DRM_BRIDGE_CONNECTOR_H__
+
+struct drm_bridge;
+struct drm_connector;
+struct drm_device;
+
+void drm_bridge_connector_enable_hpd(struct drm_connector *connector);
+void drm_bridge_connector_disable_hpd(struct drm_connector *connector);
+struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
+						struct drm_bridge *bridge);
+
+#endif /* __DRM_BRIDGE_CONNECTOR_H__ */
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 26/60] drm/omap: Detach from panels at remove time
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (20 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:28     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 27/60] drm/omap: Simplify HDMI mode and infoframe configuration Laurent Pinchart
                     ` (34 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The omapdrm driver attaches to panels with drm_panel_attach() at probe
time but never calls drm_panel_detach(). Fix it.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/omap_drv.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 837d0cd20dd1..97512d4cf664 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -387,6 +387,23 @@ static int omap_modeset_init(struct drm_device *dev)
 	return 0;
 }
 
+static void omap_modeset_fini(struct drm_device *ddev)
+{
+	struct omap_drm_private *priv = ddev->dev_private;
+	unsigned int i;
+
+	omap_drm_irq_uninstall(ddev);
+
+	for (i = 0; i < priv->num_pipes; i++) {
+		struct omap_drm_pipeline *pipe = &priv->pipes[i];
+
+		if (pipe->output->panel)
+			drm_panel_detach(pipe->output->panel);
+	}
+
+	drm_mode_config_cleanup(ddev);
+}
+
 /*
  * Enable the HPD in external components if supported
  */
@@ -634,8 +651,7 @@ static int omapdrm_init(struct omap_drm_private *priv, struct device *dev)
 
 	omap_fbdev_fini(ddev);
 err_cleanup_modeset:
-	drm_mode_config_cleanup(ddev);
-	omap_drm_irq_uninstall(ddev);
+	omap_modeset_fini(ddev);
 err_gem_deinit:
 	omap_gem_deinit(ddev);
 	destroy_workqueue(priv->wq);
@@ -660,9 +676,7 @@ static void omapdrm_cleanup(struct omap_drm_private *priv)
 
 	drm_atomic_helper_shutdown(ddev);
 
-	drm_mode_config_cleanup(ddev);
-
-	omap_drm_irq_uninstall(ddev);
+	omap_modeset_fini(ddev);
 	omap_gem_deinit(ddev);
 
 	destroy_workqueue(priv->wq);
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 27/60] drm/omap: Simplify HDMI mode and infoframe configuration
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (21 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 26/60] drm/omap: Detach from panels at remove time Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:29     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 28/60] drm/omap: Factor out display type to connector type conversion Laurent Pinchart
                     ` (33 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Remove the omap_connector_get_hdmi_mode() function as the HDMI mode can
be accessed directly from the connector's display info.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/omap_connector.c | 11 -----------
 drivers/gpu/drm/omapdrm/omap_connector.h |  1 -
 drivers/gpu/drm/omapdrm/omap_encoder.c   |  4 +---
 3 files changed, 1 insertion(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index 5967283934e1..a59cca2883b1 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -32,7 +32,6 @@ struct omap_connector {
 	struct drm_connector base;
 	struct omap_dss_device *output;
 	struct omap_dss_device *hpd;
-	bool hdmi_mode;
 };
 
 static void omap_connector_hpd_notify(struct drm_connector *connector,
@@ -95,13 +94,6 @@ void omap_connector_disable_hpd(struct drm_connector *connector)
 		hpd->ops->unregister_hpd_cb(hpd);
 }
 
-bool omap_connector_get_hdmi_mode(struct drm_connector *connector)
-{
-	struct omap_connector *omap_connector = to_omap_connector(connector);
-
-	return omap_connector->hdmi_mode;
-}
-
 static struct omap_dss_device *
 omap_connector_find_device(struct drm_connector *connector,
 			   enum omap_dss_device_ops_flag op)
@@ -178,7 +170,6 @@ static void omap_connector_destroy(struct drm_connector *connector)
 static int omap_connector_get_modes_edid(struct drm_connector *connector,
 					 struct omap_dss_device *dssdev)
 {
-	struct omap_connector *omap_connector = to_omap_connector(connector);
 	enum drm_connector_status status;
 	void *edid;
 	int n;
@@ -200,8 +191,6 @@ static int omap_connector_get_modes_edid(struct drm_connector *connector,
 	drm_connector_update_edid_property(connector, edid);
 	n = drm_add_edid_modes(connector, edid);
 
-	omap_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
-
 	kfree(edid);
 	return n;
 
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.h b/drivers/gpu/drm/omapdrm/omap_connector.h
index 608085219336..a23b3a80657b 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.h
+++ b/drivers/gpu/drm/omapdrm/omap_connector.h
@@ -32,7 +32,6 @@ struct omap_dss_device;
 struct drm_connector *omap_connector_init(struct drm_device *dev,
 					  struct omap_dss_device *output,
 					  struct drm_encoder *encoder);
-bool omap_connector_get_hdmi_mode(struct drm_connector *connector);
 void omap_connector_enable_hpd(struct drm_connector *connector);
 void omap_connector_disable_hpd(struct drm_connector *connector);
 enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c
index 40512419642b..8cae2af1438f 100644
--- a/drivers/gpu/drm/omapdrm/omap_encoder.c
+++ b/drivers/gpu/drm/omapdrm/omap_encoder.c
@@ -86,9 +86,7 @@ static void omap_encoder_hdmi_mode_set(struct drm_connector *connector,
 {
 	struct omap_encoder *omap_encoder = to_omap_encoder(encoder);
 	struct omap_dss_device *dssdev = omap_encoder->output;
-	bool hdmi_mode;
-
-	hdmi_mode = omap_connector_get_hdmi_mode(connector);
+	bool hdmi_mode = connector->display_info.is_hdmi;
 
 	if (dssdev->ops->hdmi.set_hdmi_mode)
 		dssdev->ops->hdmi.set_hdmi_mode(dssdev, hdmi_mode);
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 28/60] drm/omap: Factor out display type to connector type conversion
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (22 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 27/60] drm/omap: Simplify HDMI mode and infoframe configuration Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:32     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 29/60] drm/omap: Use the drm_panel_bridge API Laurent Pinchart
                     ` (32 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Move the code that computes the DRM connector type for the
omapdss_device display type to a new omapdss_device_connector_type()
function for later reuse.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/base.c       | 23 +++++++++++++++++++++++
 drivers/gpu/drm/omapdrm/dss/omapdss.h    |  1 +
 drivers/gpu/drm/omapdrm/omap_connector.c | 19 +------------------
 3 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c
index a1970b9db6ab..cae5687822e2 100644
--- a/drivers/gpu/drm/omapdrm/dss/base.c
+++ b/drivers/gpu/drm/omapdrm/dss/base.c
@@ -285,6 +285,29 @@ void omapdss_device_post_disable(struct omap_dss_device *dssdev)
 }
 EXPORT_SYMBOL_GPL(omapdss_device_post_disable);
 
+unsigned int omapdss_device_connector_type(enum omap_display_type type)
+{
+	switch (type) {
+	case OMAP_DISPLAY_TYPE_HDMI:
+		return DRM_MODE_CONNECTOR_HDMIA;
+	case OMAP_DISPLAY_TYPE_DVI:
+		return DRM_MODE_CONNECTOR_DVID;
+	case OMAP_DISPLAY_TYPE_DSI:
+		return DRM_MODE_CONNECTOR_DSI;
+	case OMAP_DISPLAY_TYPE_DPI:
+	case OMAP_DISPLAY_TYPE_DBI:
+		return DRM_MODE_CONNECTOR_DPI;
+	case OMAP_DISPLAY_TYPE_VENC:
+		/* TODO: This could also be composite */
+		return DRM_MODE_CONNECTOR_SVIDEO;
+	case OMAP_DISPLAY_TYPE_SDI:
+		return DRM_MODE_CONNECTOR_LVDS;
+	default:
+		return DRM_MODE_CONNECTOR_Unknown;
+	}
+}
+EXPORT_SYMBOL_GPL(omapdss_device_connector_type);
+
 /* -----------------------------------------------------------------------------
  * Components Handling
  */
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 0c734d1f89e1..0063477b670f 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -490,6 +490,7 @@ void omapdss_device_pre_enable(struct omap_dss_device *dssdev);
 void omapdss_device_enable(struct omap_dss_device *dssdev);
 void omapdss_device_disable(struct omap_dss_device *dssdev);
 void omapdss_device_post_disable(struct omap_dss_device *dssdev);
+unsigned int omapdss_device_connector_type(enum omap_display_type type);
 
 int omap_dss_get_num_overlay_managers(void);
 
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index a59cca2883b1..322158ef15f6 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -306,24 +306,7 @@ static int omap_connector_get_type(struct omap_dss_device *output)
 	type = display->type;
 	omapdss_device_put(display);
 
-	switch (type) {
-	case OMAP_DISPLAY_TYPE_HDMI:
-		return DRM_MODE_CONNECTOR_HDMIA;
-	case OMAP_DISPLAY_TYPE_DVI:
-		return DRM_MODE_CONNECTOR_DVID;
-	case OMAP_DISPLAY_TYPE_DSI:
-		return DRM_MODE_CONNECTOR_DSI;
-	case OMAP_DISPLAY_TYPE_DPI:
-	case OMAP_DISPLAY_TYPE_DBI:
-		return DRM_MODE_CONNECTOR_DPI;
-	case OMAP_DISPLAY_TYPE_VENC:
-		/* TODO: This could also be composite */
-		return DRM_MODE_CONNECTOR_SVIDEO;
-	case OMAP_DISPLAY_TYPE_SDI:
-		return DRM_MODE_CONNECTOR_LVDS;
-	default:
-		return DRM_MODE_CONNECTOR_Unknown;
-	}
+	return omapdss_device_connector_type(type);
 }
 
 /* initialize connector */
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 29/60] drm/omap: Use the drm_panel_bridge API
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (23 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 28/60] drm/omap: Factor out display type to connector type conversion Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:36     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 30/60] drm/omap: dss: Fix output next device lookup in DT Laurent Pinchart
                     ` (31 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Replace the manual panel handling code by a drm_panel_bridge. This
simplifies the driver and allows all components in the display pipeline
to be treated as bridges, paving the way to generic connector handling.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/base.c       | 12 ++++-----
 drivers/gpu/drm/omapdrm/dss/output.c     | 33 +++++++++++++++++++++---
 drivers/gpu/drm/omapdrm/omap_connector.c |  9 -------
 drivers/gpu/drm/omapdrm/omap_drv.c       | 13 ----------
 drivers/gpu/drm/omapdrm/omap_encoder.c   | 13 ----------
 5 files changed, 34 insertions(+), 46 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c
index cae5687822e2..80d48936d177 100644
--- a/drivers/gpu/drm/omapdrm/dss/base.c
+++ b/drivers/gpu/drm/omapdrm/dss/base.c
@@ -149,8 +149,7 @@ struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from)
 			goto done;
 		}
 
-		if (dssdev->id &&
-		    (dssdev->next || dssdev->bridge || dssdev->panel))
+		if (dssdev->id && (dssdev->next || dssdev->bridge))
 			goto done;
 	}
 
@@ -185,11 +184,10 @@ int omapdss_device_connect(struct dss_device *dss,
 	if (!dst) {
 		/*
 		 * The destination is NULL when the source is connected to a
-		 * bridge or panel instead of a DSS device. Stop here, we will
-		 * attach the bridge or panel later when we will have a DRM
-		 * encoder.
+		 * bridge instead of a DSS device. Stop here, we will attach
+		 * the bridge later when we will have a DRM encoder.
 		 */
-		return src && (src->bridge || src->panel) ? 0 : -EINVAL;
+		return src && src->bridge ? 0 : -EINVAL;
 	}
 
 	if (omapdss_device_is_connected(dst))
@@ -217,7 +215,7 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
 		dst ? dev_name(dst->dev) : "NULL");
 
 	if (!dst) {
-		WARN_ON(!src->bridge && !src->panel);
+		WARN_ON(!src->bridge);
 		return;
 	}
 
diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c
index 10a9ee5cdc61..4f71a20bc7c7 100644
--- a/drivers/gpu/drm/omapdrm/dss/output.c
+++ b/drivers/gpu/drm/omapdrm/dss/output.c
@@ -30,6 +30,7 @@
 int omapdss_device_init_output(struct omap_dss_device *out)
 {
 	struct device_node *remote_node;
+	int ret;
 
 	remote_node = of_graph_get_remote_node(out->dev->of_node, 0, 0);
 	if (!remote_node) {
@@ -47,17 +48,41 @@ int omapdss_device_init_output(struct omap_dss_device *out)
 
 	if (out->next && out->type != out->next->type) {
 		dev_err(out->dev, "output type and display type don't match\n");
-		omapdss_device_put(out->next);
-		out->next = NULL;
-		return -EINVAL;
+		ret = -EINVAL;
+		goto error;
 	}
 
-	return out->next || out->bridge || out->panel ? 0 : -EPROBE_DEFER;
+	if (out->panel) {
+		unsigned int connector_type;
+		struct drm_bridge *bridge;
+
+		connector_type = omapdss_device_connector_type(out->type);
+		bridge = drm_panel_bridge_add(out->panel, connector_type);
+		if (IS_ERR(bridge)) {
+			dev_err(out->dev,
+				"unable to create panel bridge (%ld)\n",
+				PTR_ERR(bridge));
+			ret = PTR_ERR(bridge);
+			goto error;
+		}
+
+		out->bridge = bridge;
+	}
+
+	return out->next || out->bridge ? 0 : -EPROBE_DEFER;
+
+error:
+	omapdss_device_put(out->next);
+	out->next = NULL;
+	return ret;
 }
 EXPORT_SYMBOL(omapdss_device_init_output);
 
 void omapdss_device_cleanup_output(struct omap_dss_device *out)
 {
+	if (out->bridge && out->panel)
+		drm_panel_bridge_remove(out->bridge);
+
 	if (out->next)
 		omapdss_device_put(out->next);
 }
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index 322158ef15f6..3d1f740037c8 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -17,7 +17,6 @@
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc.h>
-#include <drm/drm_panel.h>
 #include <drm/drm_probe_helper.h>
 
 #include "omap_drv.h"
@@ -201,7 +200,6 @@ static int omap_connector_get_modes_edid(struct drm_connector *connector,
 
 static int omap_connector_get_modes(struct drm_connector *connector)
 {
-	struct omap_connector *omap_connector = to_omap_connector(connector);
 	struct omap_dss_device *dssdev;
 
 	DBG("%s", connector->name);
@@ -224,13 +222,6 @@ static int omap_connector_get_modes(struct drm_connector *connector)
 	if (dssdev)
 		return dssdev->ops->get_modes(dssdev, connector);
 
-	/*
-	 * Otherwise if the display pipeline uses a drm_panel, we delegate the
-	 * operation to the panel API.
-	 */
-	if (omap_connector->output->panel)
-		return drm_panel_get_modes(omap_connector->output->panel);
-
 	/*
 	 * We can't retrieve modes, which can happen for instance for a DVI or
 	 * VGA output with the DDC bus unconnected. The KMS core will add the
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 97512d4cf664..b615ecef7719 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -23,7 +23,6 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_fb_helper.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_panel.h>
 
 #include "omap_dmm_tiler.h"
 #include "omap_drv.h"
@@ -138,9 +137,6 @@ static void omap_disconnect_pipelines(struct drm_device *ddev)
 	for (i = 0; i < priv->num_pipes; i++) {
 		struct omap_drm_pipeline *pipe = &priv->pipes[i];
 
-		if (pipe->output->panel)
-			drm_panel_detach(pipe->output->panel);
-
 		omapdss_device_disconnect(NULL, pipe->output);
 
 		omapdss_device_put(pipe->output);
@@ -225,8 +221,6 @@ static int omap_display_id(struct omap_dss_device *output)
 			bridge = bridge->next;
 
 		node = bridge->of_node;
-	} else if (output->panel) {
-		node = output->panel->dev->of_node;
 	}
 
 	return node ? of_alias_get_id(node, "display") : -ENODEV;
@@ -342,13 +336,6 @@ static int omap_modeset_init(struct drm_device *dev)
 				return -ENOMEM;
 
 			drm_connector_attach_encoder(pipe->connector, encoder);
-
-			if (pipe->output->panel) {
-				ret = drm_panel_attach(pipe->output->panel,
-						       pipe->connector);
-				if (ret < 0)
-					return ret;
-			}
 		}
 
 		crtc = omap_crtc_init(dev, pipe, priv->planes[i]);
diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c
index 8cae2af1438f..510888413068 100644
--- a/drivers/gpu/drm/omapdrm/omap_encoder.c
+++ b/drivers/gpu/drm/omapdrm/omap_encoder.c
@@ -20,7 +20,6 @@
 #include <drm/drm_crtc.h>
 #include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_edid.h>
-#include <drm/drm_panel.h>
 
 #include "omap_drv.h"
 
@@ -166,12 +165,6 @@ static void omap_encoder_disable(struct drm_encoder *encoder)
 
 	dev_dbg(dev->dev, "disable(%s)\n", dssdev->name);
 
-	/* Disable the panel if present. */
-	if (dssdev->panel) {
-		drm_panel_disable(dssdev->panel);
-		drm_panel_unprepare(dssdev->panel);
-	}
-
 	/*
 	 * Disable the chain of external devices, starting at the one at the
 	 * internal encoder's output.
@@ -221,12 +214,6 @@ static void omap_encoder_enable(struct drm_encoder *encoder)
 	 * internal encoder's output.
 	 */
 	omapdss_device_enable(dssdev->next);
-
-	/* Enable the panel if present. */
-	if (dssdev->panel) {
-		drm_panel_prepare(dssdev->panel);
-		drm_panel_enable(dssdev->panel);
-	}
 }
 
 static int omap_encoder_atomic_check(struct drm_encoder *encoder,
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 30/60] drm/omap: dss: Fix output next device lookup in DT
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (24 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 29/60] drm/omap: Use the drm_panel_bridge API Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:38     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 31/60] drm/omap: Add infrastructure to support drm_bridge local to DSS outputs Laurent Pinchart
                     ` (30 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The DSS core looks up the next device connected to an output by
traversing the OF graph. It currently hardcodes the local port number to
0, which breaks any output with a different port number (SDI on OMAP3
and any DPI output but the first one). Fix this by repurposing the
currently unused of_ports bitmask in omap_dss_device with an of_port
output port number, and use it to traverse the OF graph.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c      | 2 +-
 drivers/gpu/drm/omapdrm/displays/connector-hdmi.c           | 2 +-
 drivers/gpu/drm/omapdrm/displays/encoder-opa362.c           | 2 +-
 drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c        | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c             | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c     | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c  | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c     | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c     | 2 +-
 drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c     | 2 +-
 drivers/gpu/drm/omapdrm/dss/dpi.c                           | 2 +-
 drivers/gpu/drm/omapdrm/dss/dsi.c                           | 2 +-
 drivers/gpu/drm/omapdrm/dss/hdmi4.c                         | 2 +-
 drivers/gpu/drm/omapdrm/dss/hdmi5.c                         | 2 +-
 drivers/gpu/drm/omapdrm/dss/omapdss.h                       | 4 ++--
 drivers/gpu/drm/omapdrm/dss/output.c                        | 3 ++-
 drivers/gpu/drm/omapdrm/dss/sdi.c                           | 2 +-
 drivers/gpu/drm/omapdrm/dss/venc.c                          | 2 +-
 19 files changed, 21 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c b/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
index 6c0561101874..2ab18744a5d7 100644
--- a/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
+++ b/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
@@ -58,7 +58,7 @@ static int tvc_probe(struct platform_device *pdev)
 	dssdev->type = OMAP_DISPLAY_TYPE_VENC;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 
 	omapdss_display_init(dssdev);
 	omapdss_device_register(dssdev);
diff --git a/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c b/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
index 68d6f6e44b03..5d2dc103c6b1 100644
--- a/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
+++ b/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
@@ -142,7 +142,7 @@ static int hdmic_probe(struct platform_device *pdev)
 	dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = ddata->hpd_gpio
 			  ? OMAP_DSS_DEVICE_OP_DETECT | OMAP_DSS_DEVICE_OP_HPD
 			  : 0;
diff --git a/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c b/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
index 29a5a130ebd1..fa2ff7174259 100644
--- a/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
+++ b/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
@@ -89,7 +89,7 @@ static int opa362_probe(struct platform_device *pdev)
 	dssdev->dev = &pdev->dev;
 	dssdev->type = OMAP_DISPLAY_TYPE_VENC;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(1) | BIT(0);
+	dssdev->of_port = 1;
 
 	dssdev->next = omapdss_of_find_connected_device(pdev->dev.of_node, 1);
 	if (IS_ERR(dssdev->next)) {
diff --git a/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c b/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
index bc03752d2762..b0e62ed3fce4 100644
--- a/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
+++ b/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
@@ -168,7 +168,7 @@ static int tpd_probe(struct platform_device *pdev)
 	dssdev->dev = &pdev->dev;
 	dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(1) | BIT(0);
+	dssdev->of_port = 1;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_DETECT
 			  | OMAP_DSS_DEVICE_OP_HPD;
 
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c b/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c
index 913e8291a917..5a618e536a32 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-dsi-cm.c
@@ -1268,7 +1268,7 @@ static int dsicm_probe(struct platform_device *pdev)
 	dssdev->type = OMAP_DISPLAY_TYPE_DSI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
 	dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE |
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c b/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
index 99f2350d462c..51eb98e96cde 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
@@ -199,7 +199,7 @@ static int lb035q02_panel_spi_probe(struct spi_device *spi)
 	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
 	/*
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c b/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
index eba5bd1d702f..ebde3f6f670e 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
@@ -190,7 +190,7 @@ static int nec_8048_probe(struct spi_device *spi)
 	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
 			  | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c b/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
index 9c545de430f6..637eb64371d9 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
@@ -209,7 +209,7 @@ static int sharp_ls_probe(struct platform_device *pdev)
 	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
 	/*
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c b/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
index 2038def14ba1..67379f71bd88 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
@@ -708,7 +708,7 @@ static int acx565akm_probe(struct spi_device *spi)
 	dssdev->type = OMAP_DISPLAY_TYPE_SDI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
 			  | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c b/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
index 2ad161e33106..8993daa73e78 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
@@ -333,7 +333,7 @@ static int td028ttec1_panel_probe(struct spi_device *spi)
 	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
 	/*
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c b/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
index ce09217da597..c667f4287893 100644
--- a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
+++ b/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
@@ -418,7 +418,7 @@ static int tpo_td043_probe(struct spi_device *spi)
 	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
 	dssdev->display = true;
 	dssdev->owner = THIS_MODULE;
-	dssdev->of_ports = BIT(0);
+	dssdev->of_port = 0;
 	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
 	/*
diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c
index cc78dfa07f04..c28b84fb11f0 100644
--- a/drivers/gpu/drm/omapdrm/dss/dpi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dpi.c
@@ -636,7 +636,7 @@ static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
 	out->id = OMAP_DSS_OUTPUT_DPI;
 	out->type = OMAP_DISPLAY_TYPE_DPI;
 	out->dispc_channel = dpi_get_channel(dpi);
-	out->of_ports = BIT(port_num);
+	out->of_port = port_num;
 	out->ops = &dpi_ops;
 	out->owner = THIS_MODULE;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.c b/drivers/gpu/drm/omapdrm/dss/dsi.c
index 5202862d89b5..c5904935a519 100644
--- a/drivers/gpu/drm/omapdrm/dss/dsi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dsi.c
@@ -5128,7 +5128,7 @@ static int dsi_init_output(struct dsi_data *dsi)
 	out->dispc_channel = dsi_get_channel(dsi);
 	out->ops = &dsi_ops;
 	out->owner = THIS_MODULE;
-	out->of_ports = BIT(0);
+	out->of_port = 0;
 	out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE
 		       | DRM_BUS_FLAG_DE_HIGH
 		       | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE;
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index 6339e2756b34..b429677b47cd 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -684,7 +684,7 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
 	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
 	out->ops = &hdmi_ops;
 	out->owner = THIS_MODULE;
-	out->of_ports = BIT(0);
+	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
 	r = omapdss_device_init_output(out);
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 2955bbad13bb..9235273a0ff3 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -668,7 +668,7 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
 	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
 	out->ops = &hdmi_ops;
 	out->owner = THIS_MODULE;
-	out->of_ports = BIT(0);
+	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
 	r = omapdss_device_init_output(out);
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 0063477b670f..5bf6e0da6cd5 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -447,8 +447,8 @@ struct omap_dss_device {
 	/* output instance */
 	enum omap_dss_output_id id;
 
-	/* bitmask of port numbers in DT */
-	unsigned int of_ports;
+	/* port number in DT */
+	unsigned int of_port;
 };
 
 struct omap_dss_driver {
diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c
index 4f71a20bc7c7..70ccb10c11e9 100644
--- a/drivers/gpu/drm/omapdrm/dss/output.c
+++ b/drivers/gpu/drm/omapdrm/dss/output.c
@@ -32,7 +32,8 @@ int omapdss_device_init_output(struct omap_dss_device *out)
 	struct device_node *remote_node;
 	int ret;
 
-	remote_node = of_graph_get_remote_node(out->dev->of_node, 0, 0);
+	remote_node = of_graph_get_remote_node(out->dev->of_node,
+					       out->of_port, 0);
 	if (!remote_node) {
 		dev_dbg(out->dev, "failed to find video sink\n");
 		return 0;
diff --git a/drivers/gpu/drm/omapdrm/dss/sdi.c b/drivers/gpu/drm/omapdrm/dss/sdi.c
index 7aae52984fed..aa841cb461e9 100644
--- a/drivers/gpu/drm/omapdrm/dss/sdi.c
+++ b/drivers/gpu/drm/omapdrm/dss/sdi.c
@@ -276,7 +276,7 @@ static int sdi_init_output(struct sdi_device *sdi)
 	out->name = "sdi.0";
 	out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
 	/* We have SDI only on OMAP3, where it's on port 1 */
-	out->of_ports = BIT(1);
+	out->of_port = 1;
 	out->ops = &sdi_ops;
 	out->owner = THIS_MODULE;
 	out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE	/* 15.5.9.1.2 */
diff --git a/drivers/gpu/drm/omapdrm/dss/venc.c b/drivers/gpu/drm/omapdrm/dss/venc.c
index da43b865d973..ff7128a47bef 100644
--- a/drivers/gpu/drm/omapdrm/dss/venc.c
+++ b/drivers/gpu/drm/omapdrm/dss/venc.c
@@ -765,7 +765,7 @@ static int venc_init_output(struct venc_device *venc)
 	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
 	out->ops = &venc_ops;
 	out->owner = THIS_MODULE;
-	out->of_ports = BIT(0);
+	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
 	r = omapdss_device_init_output(out);
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 31/60] drm/omap: Add infrastructure to support drm_bridge local to DSS outputs
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (25 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 30/60] drm/omap: dss: Fix output next device lookup in DT Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional Laurent Pinchart
                     ` (29 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In order to support drm_bridge-based pipeline, the internal HDMI
encoders will need to expose the EDID read operation through the
drm_bridge API, and thus to expose a drm_bridge instance corresponding
to the encoder. The HDMI encoders are however handled as omap_dss_device
instances, which conflicts with this requirement.

In order to move forward with the drm_bridge transition, add support for
creating drm_bridge instances local to DSS outputs. If a local bridge is
passed to the omapdss_device_init_output() function, it is used as the
first bridge in the chain, and the omap_dss_device.next_bridge field is
set to the next bridge for the use of the internal encoders' bridges.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/dpi.c     |  2 +-
 drivers/gpu/drm/omapdrm/dss/dsi.c     |  2 +-
 drivers/gpu/drm/omapdrm/dss/hdmi4.c   |  2 +-
 drivers/gpu/drm/omapdrm/dss/hdmi5.c   |  2 +-
 drivers/gpu/drm/omapdrm/dss/omapdss.h |  4 +++-
 drivers/gpu/drm/omapdrm/dss/output.c  | 20 ++++++++++++++++----
 drivers/gpu/drm/omapdrm/dss/sdi.c     |  2 +-
 drivers/gpu/drm/omapdrm/dss/venc.c    |  2 +-
 drivers/gpu/drm/omapdrm/omap_drv.c    |  2 +-
 9 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c
index c28b84fb11f0..e1f94a84c9bd 100644
--- a/drivers/gpu/drm/omapdrm/dss/dpi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dpi.c
@@ -640,7 +640,7 @@ static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
 	out->ops = &dpi_ops;
 	out->owner = THIS_MODULE;
 
-	r = omapdss_device_init_output(out);
+	r = omapdss_device_init_output(out, NULL);
 	if (r < 0)
 		return r;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.c b/drivers/gpu/drm/omapdrm/dss/dsi.c
index c5904935a519..2b024666a2fd 100644
--- a/drivers/gpu/drm/omapdrm/dss/dsi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dsi.c
@@ -5133,7 +5133,7 @@ static int dsi_init_output(struct dsi_data *dsi)
 		       | DRM_BUS_FLAG_DE_HIGH
 		       | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE;
 
-	r = omapdss_device_init_output(out);
+	r = omapdss_device_init_output(out, NULL);
 	if (r < 0)
 		return r;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index b429677b47cd..0a0bda7f686f 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -687,7 +687,7 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
-	r = omapdss_device_init_output(out);
+	r = omapdss_device_init_output(out, NULL);
 	if (r < 0)
 		return r;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 9235273a0ff3..07e6d8347689 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -671,7 +671,7 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
-	r = omapdss_device_init_output(out);
+	r = omapdss_device_init_output(out, NULL);
 	if (r < 0)
 		return r;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 5bf6e0da6cd5..8cc08a3290de 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -411,6 +411,7 @@ struct omap_dss_device {
 	struct dss_device *dss;
 	struct omap_dss_device *next;
 	struct drm_bridge *bridge;
+	struct drm_bridge *next_bridge;
 	struct drm_panel *panel;
 
 	struct list_head list;
@@ -499,7 +500,8 @@ int omap_dss_get_num_overlays(void);
 #define for_each_dss_output(d) \
 	while ((d = omapdss_device_next_output(d)) != NULL)
 struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from);
-int omapdss_device_init_output(struct omap_dss_device *out);
+int omapdss_device_init_output(struct omap_dss_device *out,
+			       struct drm_bridge *local_bridge);
 void omapdss_device_cleanup_output(struct omap_dss_device *out);
 
 typedef void (*omap_dispc_isr_t) (void *arg, u32 mask);
diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c
index 70ccb10c11e9..71693d1abea8 100644
--- a/drivers/gpu/drm/omapdrm/dss/output.c
+++ b/drivers/gpu/drm/omapdrm/dss/output.c
@@ -27,7 +27,8 @@
 #include "dss.h"
 #include "omapdss.h"
 
-int omapdss_device_init_output(struct omap_dss_device *out)
+int omapdss_device_init_output(struct omap_dss_device *out,
+			       struct drm_bridge *local_bridge)
 {
 	struct device_node *remote_node;
 	int ret;
@@ -70,10 +71,20 @@ int omapdss_device_init_output(struct omap_dss_device *out)
 		out->bridge = bridge;
 	}
 
-	return out->next || out->bridge ? 0 : -EPROBE_DEFER;
+	if (local_bridge) {
+		out->next_bridge = out->bridge;
+		out->bridge = local_bridge;
+	}
+
+	if (!out->next && !out->bridge) {
+		ret = -EPROBE_DEFER;
+		goto error;
+	}
+
+	return 0;
 
 error:
-	omapdss_device_put(out->next);
+	omapdss_device_cleanup_output(out);
 	out->next = NULL;
 	return ret;
 }
@@ -82,7 +93,8 @@ EXPORT_SYMBOL(omapdss_device_init_output);
 void omapdss_device_cleanup_output(struct omap_dss_device *out)
 {
 	if (out->bridge && out->panel)
-		drm_panel_bridge_remove(out->bridge);
+		drm_panel_bridge_remove(out->next_bridge ?
+					out->next_bridge : out->bridge);
 
 	if (out->next)
 		omapdss_device_put(out->next);
diff --git a/drivers/gpu/drm/omapdrm/dss/sdi.c b/drivers/gpu/drm/omapdrm/dss/sdi.c
index aa841cb461e9..2c5eaac9193f 100644
--- a/drivers/gpu/drm/omapdrm/dss/sdi.c
+++ b/drivers/gpu/drm/omapdrm/dss/sdi.c
@@ -282,7 +282,7 @@ static int sdi_init_output(struct sdi_device *sdi)
 	out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE	/* 15.5.9.1.2 */
 		       | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
 
-	r = omapdss_device_init_output(out);
+	r = omapdss_device_init_output(out, NULL);
 	if (r < 0)
 		return r;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/venc.c b/drivers/gpu/drm/omapdrm/dss/venc.c
index ff7128a47bef..0a81dd5c93d6 100644
--- a/drivers/gpu/drm/omapdrm/dss/venc.c
+++ b/drivers/gpu/drm/omapdrm/dss/venc.c
@@ -768,7 +768,7 @@ static int venc_init_output(struct venc_device *venc)
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
-	r = omapdss_device_init_output(out);
+	r = omapdss_device_init_output(out, NULL);
 	if (r < 0)
 		return r;
 
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index b615ecef7719..790a2b5a1591 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -329,7 +329,7 @@ static int omap_modeset_init(struct drm_device *dev)
 		struct drm_encoder *encoder = pipe->encoder;
 		struct drm_crtc *crtc;
 
-		if (!pipe->output->bridge) {
+		if (pipe->output->next) {
 			pipe->connector = omap_connector_init(dev, pipe->output,
 							      encoder);
 			if (!pipe->connector)
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (26 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 31/60] drm/omap: Add infrastructure to support drm_bridge local to DSS outputs Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:48     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 33/60] drm/omap: hdmi: Allocate EDID in the .read_edid() operation Laurent Pinchart
                     ` (28 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

As part of the move to drm_bridge ops for some of the internal encoders
will be removed. Make them optional in the driver to ease the
transition.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/base.c       | 21 ++++++++++++---------
 drivers/gpu/drm/omapdrm/dss/dss.c        |  3 ++-
 drivers/gpu/drm/omapdrm/omap_connector.c |  2 +-
 drivers/gpu/drm/omapdrm/omap_encoder.c   | 12 +++++++-----
 4 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c
index 80d48936d177..2db3bd2f19db 100644
--- a/drivers/gpu/drm/omapdrm/dss/base.c
+++ b/drivers/gpu/drm/omapdrm/dss/base.c
@@ -195,10 +195,12 @@ int omapdss_device_connect(struct dss_device *dss,
 
 	dst->dss = dss;
 
-	ret = dst->ops->connect(src, dst);
-	if (ret < 0) {
-		dst->dss = NULL;
-		return ret;
+	if (dst->ops && dst->ops->connect) {
+		ret = dst->ops->connect(src, dst);
+		if (ret < 0) {
+			dst->dss = NULL;
+			return ret;
+		}
 	}
 
 	return 0;
@@ -226,7 +228,8 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
 
 	WARN_ON(dst->state != OMAP_DSS_DISPLAY_DISABLED);
 
-	dst->ops->disconnect(src, dst);
+	if (dst->ops && dst->ops->disconnect)
+		dst->ops->disconnect(src, dst);
 	dst->dss = NULL;
 }
 EXPORT_SYMBOL_GPL(omapdss_device_disconnect);
@@ -238,7 +241,7 @@ void omapdss_device_pre_enable(struct omap_dss_device *dssdev)
 
 	omapdss_device_pre_enable(dssdev->next);
 
-	if (dssdev->ops->pre_enable)
+	if (dssdev->ops && dssdev->ops->pre_enable)
 		dssdev->ops->pre_enable(dssdev);
 }
 EXPORT_SYMBOL_GPL(omapdss_device_pre_enable);
@@ -248,7 +251,7 @@ void omapdss_device_enable(struct omap_dss_device *dssdev)
 	if (!dssdev)
 		return;
 
-	if (dssdev->ops->enable)
+	if (dssdev->ops && dssdev->ops->enable)
 		dssdev->ops->enable(dssdev);
 
 	omapdss_device_enable(dssdev->next);
@@ -264,7 +267,7 @@ void omapdss_device_disable(struct omap_dss_device *dssdev)
 
 	omapdss_device_disable(dssdev->next);
 
-	if (dssdev->ops->disable)
+	if (dssdev->ops && dssdev->ops->disable)
 		dssdev->ops->disable(dssdev);
 }
 EXPORT_SYMBOL_GPL(omapdss_device_disable);
@@ -274,7 +277,7 @@ void omapdss_device_post_disable(struct omap_dss_device *dssdev)
 	if (!dssdev)
 		return;
 
-	if (dssdev->ops->post_disable)
+	if (dssdev->ops && dssdev->ops->post_disable)
 		dssdev->ops->post_disable(dssdev);
 
 	omapdss_device_post_disable(dssdev->next);
diff --git a/drivers/gpu/drm/omapdrm/dss/dss.c b/drivers/gpu/drm/omapdrm/dss/dss.c
index 55e68863ef15..a5cf15b7e630 100644
--- a/drivers/gpu/drm/omapdrm/dss/dss.c
+++ b/drivers/gpu/drm/omapdrm/dss/dss.c
@@ -1561,7 +1561,8 @@ static void dss_shutdown(struct platform_device *pdev)
 	DSSDBG("shutdown\n");
 
 	for_each_dss_output(dssdev) {
-		if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
+		if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE &&
+		    dssdev->ops && dssdev->ops->disable)
 			dssdev->ops->disable(dssdev);
 	}
 }
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index 3d1f740037c8..3f913c8cf344 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -239,7 +239,7 @@ enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
 	drm_mode_copy(adjusted_mode, mode);
 
 	for (; dssdev; dssdev = dssdev->next) {
-		if (!dssdev->ops->check_timings)
+		if (!dssdev->ops || !dssdev->ops->check_timings)
 			continue;
 
 		ret = dssdev->ops->check_timings(dssdev, adjusted_mode);
diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c
index 510888413068..fa4e00e65f9d 100644
--- a/drivers/gpu/drm/omapdrm/omap_encoder.c
+++ b/drivers/gpu/drm/omapdrm/omap_encoder.c
@@ -87,10 +87,10 @@ static void omap_encoder_hdmi_mode_set(struct drm_connector *connector,
 	struct omap_dss_device *dssdev = omap_encoder->output;
 	bool hdmi_mode = connector->display_info.is_hdmi;
 
-	if (dssdev->ops->hdmi.set_hdmi_mode)
+	if (dssdev->ops && dssdev->ops->hdmi.set_hdmi_mode)
 		dssdev->ops->hdmi.set_hdmi_mode(dssdev, hdmi_mode);
 
-	if (hdmi_mode && dssdev->ops->hdmi.set_infoframe) {
+	if (hdmi_mode && dssdev->ops && dssdev->ops->hdmi.set_infoframe) {
 		struct hdmi_avi_infoframe avi;
 		int r;
 
@@ -148,7 +148,7 @@ static void omap_encoder_mode_set(struct drm_encoder *encoder,
 	dss_mgr_set_timings(output, &vm);
 
 	for (dssdev = output; dssdev; dssdev = dssdev->next) {
-		if (dssdev->ops->set_timings)
+		if (dssdev->ops && dssdev->ops->set_timings)
 			dssdev->ops->set_timings(dssdev, adjusted_mode);
 	}
 
@@ -177,7 +177,8 @@ static void omap_encoder_disable(struct drm_encoder *encoder)
 	 * flow where the pipeline output controls the encoder.
 	 */
 	if (dssdev->type != OMAP_DISPLAY_TYPE_DSI) {
-		dssdev->ops->disable(dssdev);
+		if (dssdev->ops && dssdev->ops->disable)
+			dssdev->ops->disable(dssdev);
 		dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
 	}
 
@@ -205,7 +206,8 @@ static void omap_encoder_enable(struct drm_encoder *encoder)
 	 * flow where the pipeline output controls the encoder.
 	 */
 	if (dssdev->type != OMAP_DISPLAY_TYPE_DSI) {
-		dssdev->ops->enable(dssdev);
+		if (dssdev->ops && dssdev->ops->enable)
+			dssdev->ops->enable(dssdev);
 		dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
 	}
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 33/60] drm/omap: hdmi: Allocate EDID in the .read_edid() operation
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (27 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-08-13  7:52     ` Tomi Valkeinen
  2019-07-07 18:19   ` [PATCH 34/60] drm/omap: hdmi4: Rework EDID read to isolate data read Laurent Pinchart
                     ` (27 subsequent siblings)
  56 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Bring the omapdss-specific .read_edid() operation in sync with the
drm_bridge .get_edid() operation to ease code reuse.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi4.c      | 34 ++++++++++++++++--------
 drivers/gpu/drm/omapdrm/dss/hdmi5.c      | 22 ++++++++++-----
 drivers/gpu/drm/omapdrm/dss/omapdss.h    |  2 +-
 drivers/gpu/drm/omapdrm/omap_connector.c | 12 +++------
 4 files changed, 43 insertions(+), 27 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index 0a0bda7f686f..f0586108b41e 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -416,31 +416,43 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static int hdmi_read_edid(struct omap_dss_device *dssdev,
-		u8 *edid, int len)
+static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 {
 	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
 	bool need_enable;
+	u8 *edid;
 	int r;
 
+	edid = kzalloc(512, GFP_KERNEL);
+	if (!edid)
+		return NULL;
+
 	need_enable = hdmi->core_enabled == false;
 
 	if (need_enable) {
 		r = hdmi4_core_enable(&hdmi->core);
-		if (r)
-			return r;
+		if (r) {
+			kfree(edid);
+			return NULL;
+		}
+	}
+
+	r = read_edid(hdmi, edid, 512);
+	if (r < 0) {
+		kfree(edid);
+		edid = NULL;
+	} else {
+		unsigned int cec_addr;
+
+		cec_addr = r >= 256 ? cec_get_edid_phys_addr(edid, r, NULL)
+			 : CEC_PHYS_ADDR_INVALID;
+		hdmi4_cec_set_phys_addr(&hdmi->core, cec_addr);
 	}
 
-	r = read_edid(hdmi, edid, len);
-	if (r >= 256)
-		hdmi4_cec_set_phys_addr(&hdmi->core,
-					cec_get_edid_phys_addr(edid, r, NULL));
-	else
-		hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
 	if (need_enable)
 		hdmi4_core_disable(&hdmi->core);
 
-	return r;
+	return (struct edid *)edid;
 }
 
 static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 07e6d8347689..60d146ac8fd0 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -421,27 +421,37 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static int hdmi_read_edid(struct omap_dss_device *dssdev,
-		u8 *edid, int len)
+static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 {
 	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
 	bool need_enable;
+	u8 *edid;
 	int r;
 
+	edid = kzalloc(512, GFP_KERNEL);
+	if (!edid)
+		return NULL;
+
 	need_enable = hdmi->core_enabled == false;
 
 	if (need_enable) {
 		r = hdmi_core_enable(hdmi);
-		if (r)
-			return r;
+		if (r) {
+			kfree(edid);
+			return NULL;
+		}
 	}
 
-	r = read_edid(hdmi, edid, len);
+	r = read_edid(hdmi, edid, 512);
+	if (r < 0) {
+		kfree(edid);
+		edid = NULL;
+	}
 
 	if (need_enable)
 		hdmi_core_disable(hdmi);
 
-	return r;
+	return (struct edid *)edid;
 }
 
 static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 8cc08a3290de..ccdf42617613 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -378,7 +378,7 @@ struct omap_dss_device_ops {
 				void *cb_data);
 	void (*unregister_hpd_cb)(struct omap_dss_device *dssdev);
 
-	int (*read_edid)(struct omap_dss_device *dssdev, u8 *buf, int len);
+	struct edid *(*read_edid)(struct omap_dss_device *dssdev);
 
 	int (*get_modes)(struct omap_dss_device *dssdev,
 			 struct drm_connector *connector);
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index 3f913c8cf344..f8b2e63d9e74 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -164,25 +164,19 @@ static void omap_connector_destroy(struct drm_connector *connector)
 	kfree(omap_connector);
 }
 
-#define MAX_EDID  512
-
 static int omap_connector_get_modes_edid(struct drm_connector *connector,
 					 struct omap_dss_device *dssdev)
 {
 	enum drm_connector_status status;
-	void *edid;
+	struct edid *edid;
 	int n;
 
 	status = omap_connector_detect(connector, false);
 	if (status != connector_status_connected)
 		goto no_edid;
 
-	edid = kzalloc(MAX_EDID, GFP_KERNEL);
-	if (!edid)
-		goto no_edid;
-
-	if (dssdev->ops->read_edid(dssdev, edid, MAX_EDID) <= 0 ||
-	    !drm_edid_is_valid(edid)) {
+	edid = dssdev->ops->read_edid(dssdev);
+	if (!edid || !drm_edid_is_valid(edid)) {
 		kfree(edid);
 		goto no_edid;
 	}
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 34/60] drm/omap: hdmi4: Rework EDID read to isolate data read
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (28 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 33/60] drm/omap: hdmi: Allocate EDID in the .read_edid() operation Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 35/60] drm/omap: hdmi5: " Laurent Pinchart
                     ` (26 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In preparation of adding DRM bridge support to the hdmi4 encoder code,
rework the EDID read to isolate data read.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi4.c      | 94 +++++++++++++++---------
 drivers/gpu/drm/omapdrm/dss/hdmi4_core.c | 59 +++------------
 drivers/gpu/drm/omapdrm/dss/hdmi4_core.h |  4 +-
 3 files changed, 73 insertions(+), 84 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index f0586108b41e..5d7b17b52dc0 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -283,23 +283,6 @@ static int hdmi_dump_regs(struct seq_file *s, void *p)
 	return 0;
 }
 
-static int read_edid(struct omap_hdmi *hdmi, u8 *buf, int len)
-{
-	int r;
-
-	mutex_lock(&hdmi->lock);
-
-	r = hdmi_runtime_get(hdmi);
-	BUG_ON(r);
-
-	r = hdmi4_read_edid(&hdmi->core,  buf, len);
-
-	hdmi_runtime_put(hdmi);
-	mutex_unlock(&hdmi->lock);
-
-	return r;
-}
-
 static void hdmi_start_audio_stream(struct omap_hdmi *hd)
 {
 	hdmi_wp_audio_enable(&hd->wp, true);
@@ -416,10 +399,8 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi)
 {
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-	bool need_enable;
 	u8 *edid;
 	int r;
 
@@ -427,32 +408,79 @@ static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 	if (!edid)
 		return NULL;
 
+	r = hdmi4_core_ddc_read(&hdmi->core, edid, 0, EDID_LENGTH);
+	if (r)
+		goto error;
+
+	if (edid[0x7e] > 0) {
+		char checksum = 0;
+		unsigned int i;
+
+		r = hdmi4_core_ddc_read(&hdmi->core, edid + EDID_LENGTH, 1,
+					EDID_LENGTH);
+		if (r)
+			goto error;
+
+		for (i = 0; i < EDID_LENGTH; ++i)
+			checksum += edid[EDID_LENGTH + i];
+
+		if (checksum != 0) {
+			DSSERR("E-EDID checksum failed!!\n");
+			goto error;
+		}
+	}
+
+	return (struct edid *)edid;
+
+error:
+	kfree(edid);
+	return NULL;
+}
+
+static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+{
+	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
+	struct edid *edid = NULL;
+	unsigned int cec_addr;
+	bool need_enable;
+	int r;
+
 	need_enable = hdmi->core_enabled == false;
 
 	if (need_enable) {
 		r = hdmi4_core_enable(&hdmi->core);
-		if (r) {
-			kfree(edid);
+		if (r)
 			return NULL;
-		}
 	}
 
-	r = read_edid(hdmi, edid, 512);
-	if (r < 0) {
-		kfree(edid);
-		edid = NULL;
+	mutex_lock(&hdmi->lock);
+	r = hdmi_runtime_get(hdmi);
+	BUG_ON(r);
+
+	r = hdmi4_core_ddc_init(&hdmi->core);
+	if (r)
+		goto done;
+
+	edid = hdmi_read_edid_data(hdmi);
+
+done:
+	hdmi_runtime_put(hdmi);
+	mutex_unlock(&hdmi->lock);
+
+	if (edid && edid->extensions) {
+		unsigned int len = (edid->extensions + 1) * EDID_LENGTH;
+
+		cec_addr = cec_get_edid_phys_addr((u8 *)edid, len, NULL);
 	} else {
-		unsigned int cec_addr;
-
-		cec_addr = r >= 256 ? cec_get_edid_phys_addr(edid, r, NULL)
-			 : CEC_PHYS_ADDR_INVALID;
-		hdmi4_cec_set_phys_addr(&hdmi->core, cec_addr);
+		cec_addr = CEC_PHYS_ADDR_INVALID;
 	}
 
+	hdmi4_cec_set_phys_addr(&hdmi->core, cec_addr);
+
 	if (need_enable)
 		hdmi4_core_disable(&hdmi->core);
 
-	return (struct edid *)edid;
+	return edid;
 }
 
 static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4_core.c b/drivers/gpu/drm/omapdrm/dss/hdmi4_core.c
index e384b95ad857..b51697b25c70 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4_core.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4_core.c
@@ -43,7 +43,7 @@ static inline void __iomem *hdmi_av_base(struct hdmi_core_data *core)
 	return core->base + HDMI_CORE_AV;
 }
 
-static int hdmi_core_ddc_init(struct hdmi_core_data *core)
+int hdmi4_core_ddc_init(struct hdmi_core_data *core)
 {
 	void __iomem *base = core->base;
 
@@ -85,13 +85,11 @@ static int hdmi_core_ddc_init(struct hdmi_core_data *core)
 	return 0;
 }
 
-static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
-		u8 *pedid, int ext)
+int hdmi4_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len)
 {
+	struct hdmi_core_data *core = data;
 	void __iomem *base = core->base;
 	u32 i;
-	char checksum;
-	u32 offset = 0;
 
 	/* HDMI_CORE_DDC_STATUS_IN_PROG */
 	if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS,
@@ -100,24 +98,21 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
 		return -ETIMEDOUT;
 	}
 
-	if (ext % 2 != 0)
-		offset = 0x80;
-
 	/* Load Segment Address Register */
-	REG_FLD_MOD(base, HDMI_CORE_DDC_SEGM, ext / 2, 7, 0);
+	REG_FLD_MOD(base, HDMI_CORE_DDC_SEGM, block / 2, 7, 0);
 
 	/* Load Slave Address Register */
 	REG_FLD_MOD(base, HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1);
 
 	/* Load Offset Address Register */
-	REG_FLD_MOD(base, HDMI_CORE_DDC_OFFSET, offset, 7, 0);
+	REG_FLD_MOD(base, HDMI_CORE_DDC_OFFSET, block % 2 ? 0x80 : 0, 7, 0);
 
 	/* Load Byte Count */
-	REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT1, 0x80, 7, 0);
+	REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT1, len, 7, 0);
 	REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT2, 0x0, 1, 0);
 
 	/* Set DDC_CMD */
-	if (ext)
+	if (block)
 		REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x4, 3, 0);
 	else
 		REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x2, 3, 0);
@@ -133,7 +128,7 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
 		return -EIO;
 	}
 
-	for (i = 0; i < 0x80; ++i) {
+	for (i = 0; i < len; ++i) {
 		int t;
 
 		/* IN_PROG */
@@ -152,48 +147,12 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
 			udelay(1);
 		}
 
-		pedid[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 0);
-	}
-
-	checksum = 0;
-	for (i = 0; i < 0x80; ++i)
-		checksum += pedid[i];
-
-	if (checksum != 0) {
-		DSSERR("E-EDID checksum failed!!\n");
-		return -EIO;
+		buf[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 0);
 	}
 
 	return 0;
 }
 
-int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len)
-{
-	int r, l;
-
-	if (len < 128)
-		return -EINVAL;
-
-	r = hdmi_core_ddc_init(core);
-	if (r)
-		return r;
-
-	r = hdmi_core_ddc_edid(core, edid, 0);
-	if (r)
-		return r;
-
-	l = 128;
-
-	if (len >= 128 * 2 && edid[0x7e] > 0) {
-		r = hdmi_core_ddc_edid(core, edid + 0x80, 1);
-		if (r)
-			return r;
-		l += 128;
-	}
-
-	return l;
-}
-
 static void hdmi_core_init(struct hdmi_core_video_config *video_cfg)
 {
 	DSSDBG("Enter hdmi_core_init\n");
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4_core.h b/drivers/gpu/drm/omapdrm/dss/hdmi4_core.h
index 337a317c1a27..3a09f8e2e143 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4_core.h
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4_core.h
@@ -260,7 +260,9 @@ struct hdmi_core_packet_enable_repeat {
 	u32	generic_pkt_repeat;
 };
 
-int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len);
+int hdmi4_core_ddc_init(struct hdmi_core_data *core);
+int hdmi4_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len);
+
 void hdmi4_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
 		struct hdmi_config *cfg);
 void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s);
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 35/60] drm/omap: hdmi5: Rework EDID read to isolate data read
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (29 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 34/60] drm/omap: hdmi4: Rework EDID read to isolate data read Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 36/60] drm/omap: hdmi4: Register a drm_bridge for EDID read Laurent Pinchart
                     ` (25 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In preparation of adding DRM bridge support to the hdmi5 encoder code,
rework the EDID read to isolate data read.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi5.c      | 89 ++++++++++++++----------
 drivers/gpu/drm/omapdrm/dss/hdmi5_core.c | 48 +++----------
 drivers/gpu/drm/omapdrm/dss/hdmi5_core.h |  5 +-
 3 files changed, 65 insertions(+), 77 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 60d146ac8fd0..2dfcacdc072e 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -282,30 +282,6 @@ static int hdmi_dump_regs(struct seq_file *s, void *p)
 	return 0;
 }
 
-static int read_edid(struct omap_hdmi *hdmi, u8 *buf, int len)
-{
-	int r;
-	int idlemode;
-
-	mutex_lock(&hdmi->lock);
-
-	r = hdmi_runtime_get(hdmi);
-	BUG_ON(r);
-
-	idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2);
-	/* No-idle mode */
-	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
-
-	r = hdmi5_read_edid(&hdmi->core,  buf, len);
-
-	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2);
-
-	hdmi_runtime_put(hdmi);
-	mutex_unlock(&hdmi->lock);
-
-	return r;
-}
-
 static void hdmi_start_audio_stream(struct omap_hdmi *hd)
 {
 	REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
@@ -421,32 +397,73 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+static struct edid *hdmi_read_edid_data(struct hdmi_core_data *core)
 {
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-	bool need_enable;
+	int max_ext_blocks = 3;
+	int r, n, i;
 	u8 *edid;
-	int r;
 
 	edid = kzalloc(512, GFP_KERNEL);
 	if (!edid)
 		return NULL;
 
+	r = hdmi5_core_ddc_read(core, edid, 0, EDID_LENGTH);
+	if (r)
+		goto error;
+
+	n = edid[0x7e];
+
+	if (n > max_ext_blocks)
+		n = max_ext_blocks;
+
+	for (i = 1; i <= n; i++) {
+		r = hdmi5_core_ddc_read(core, edid + i * EDID_LENGTH, i,
+					EDID_LENGTH);
+		if (r)
+			goto error;
+	}
+
+	return (struct edid *)edid;
+
+error:
+	kfree(edid);
+	return NULL;
+}
+
+static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+{
+	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
+	struct edid *edid;
+	bool need_enable;
+	int idlemode;
+	int r;
+
 	need_enable = hdmi->core_enabled == false;
 
 	if (need_enable) {
 		r = hdmi_core_enable(hdmi);
-		if (r) {
-			kfree(edid);
+		if (r)
 			return NULL;
-		}
 	}
 
-	r = read_edid(hdmi, edid, 512);
-	if (r < 0) {
-		kfree(edid);
-		edid = NULL;
-	}
+	mutex_lock(&hdmi->lock);
+	r = hdmi_runtime_get(hdmi);
+	BUG_ON(r);
+
+	idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2);
+	/* No-idle mode */
+	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
+
+	hdmi5_core_ddc_init(&hdmi->core);
+
+	edid = hdmi_read_edid_data(&hdmi->core);
+
+	hdmi5_core_ddc_uninit(&hdmi->core);
+
+	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2);
+
+	hdmi_runtime_put(hdmi);
+	mutex_unlock(&hdmi->lock);
 
 	if (need_enable)
 		hdmi_core_disable(hdmi);
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
index 02efabc7ed76..00782654ac86 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.c
@@ -46,7 +46,7 @@ static const struct csc_table csc_table_deepcolor[] = {
 	[3] = { 8192, 0, 0, 0, 0, 8192, 0, 0, 0, 0, 8192, 0, },
 };
 
-static void hdmi_core_ddc_init(struct hdmi_core_data *core)
+void hdmi5_core_ddc_init(struct hdmi_core_data *core)
 {
 	void __iomem *base = core->base;
 	const unsigned long long iclk = 266000000;	/* DSS L3 ICLK */
@@ -125,7 +125,7 @@ static void hdmi_core_ddc_init(struct hdmi_core_data *core)
 	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x0, 2, 2);
 }
 
-static void hdmi_core_ddc_uninit(struct hdmi_core_data *core)
+void hdmi5_core_ddc_uninit(struct hdmi_core_data *core)
 {
 	void __iomem *base = core->base;
 
@@ -135,14 +135,14 @@ static void hdmi_core_ddc_uninit(struct hdmi_core_data *core)
 	REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2);
 }
 
-static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext)
+int hdmi5_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len)
 {
+	struct hdmi_core_data *core = data;
 	void __iomem *base = core->base;
 	u8 cur_addr;
-	char checksum = 0;
 	const int retries = 1000;
-	u8 seg_ptr = ext / 2;
-	u8 edidbase = ((ext % 2) * 0x80);
+	u8 seg_ptr = block / 2;
+	u8 edidbase = ((block % 2) * EDID_LENGTH);
 
 	REG_FLD_MOD(base, HDMI_CORE_I2CM_SEGPTR, seg_ptr, 7, 0);
 
@@ -150,7 +150,7 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext)
 	 * TODO: We use polling here, although we probably should use proper
 	 * interrupts.
 	 */
-	for (cur_addr = 0; cur_addr < 128; ++cur_addr) {
+	for (cur_addr = 0; cur_addr < len; ++cur_addr) {
 		int i;
 
 		/* clear ERROR and DONE */
@@ -187,45 +187,13 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext)
 			return -EIO;
 		}
 
-		pedid[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 0);
-		checksum += pedid[cur_addr];
+		buf[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 0);
 	}
 
 	return 0;
 
 }
 
-int hdmi5_read_edid(struct hdmi_core_data *core, u8 *edid, int len)
-{
-	int r, n, i;
-	int max_ext_blocks = (len / 128) - 1;
-
-	if (len < 128)
-		return -EINVAL;
-
-	hdmi_core_ddc_init(core);
-
-	r = hdmi_core_ddc_edid(core, edid, 0);
-	if (r)
-		goto out;
-
-	n = edid[0x7e];
-
-	if (n > max_ext_blocks)
-		n = max_ext_blocks;
-
-	for (i = 1; i <= n; i++) {
-		r = hdmi_core_ddc_edid(core, edid + i * EDID_LENGTH, i);
-		if (r)
-			goto out;
-	}
-
-out:
-	hdmi_core_ddc_uninit(core);
-
-	return r ? r : len;
-}
-
 void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s)
 {
 
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
index f2f1022c5516..41bcc4a98ac5 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5_core.h
@@ -292,7 +292,10 @@ struct csc_table {
 	u16 c1, c2, c3, c4;
 };
 
-int hdmi5_read_edid(struct hdmi_core_data *core, u8 *edid, int len);
+void hdmi5_core_ddc_init(struct hdmi_core_data *core);
+int hdmi5_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len);
+void hdmi5_core_ddc_uninit(struct hdmi_core_data *core);
+
 void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s);
 int hdmi5_core_handle_irqs(struct hdmi_core_data *core);
 void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 36/60] drm/omap: hdmi4: Register a drm_bridge for EDID read
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (30 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 35/60] drm/omap: hdmi5: " Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 37/60] drm/omap: hdmi5: " Laurent Pinchart
                     ` (24 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In order to integrate with a chain of drm_bridge, the internal HDMI4
encoder has to expose the EDID read operation through the drm_bridge
API. Register a bridge at initialisation time to do so.

For the time being make the next bridge in the chain optional as the
HDMI output is still based on omap_dss_device. The create_connector
argument to the bridge attach function is also ignored for the same
reason. This will be changed later when removing the related
omapdrm-specific display drivers.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi.h  |  3 ++
 drivers/gpu/drm/omapdrm/dss/hdmi4.c | 73 +++++++++++++++++++++++++++--
 2 files changed, 71 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi.h b/drivers/gpu/drm/omapdrm/dss/hdmi.h
index 7f0dc490a31d..d1e3f625eebc 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi.h
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi.h
@@ -25,6 +25,7 @@
 #include <linux/hdmi.h>
 #include <sound/omap-hdmi-audio.h>
 #include <media/cec.h>
+#include <drm/drm_bridge.h>
 
 #include "omapdss.h"
 #include "dss.h"
@@ -375,6 +376,7 @@ struct omap_hdmi {
 	bool core_enabled;
 
 	struct omap_dss_device output;
+	struct drm_bridge bridge;
 
 	struct platform_device *audio_pdev;
 	void (*audio_abort_cb)(struct device *dev);
@@ -390,5 +392,6 @@ struct omap_hdmi {
 };
 
 #define dssdev_to_hdmi(dssdev) container_of(dssdev, struct omap_hdmi, output)
+#define drm_bridge_to_hdmi(b) container_of(b, struct omap_hdmi, bridge)
 
 #endif
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index 5d7b17b52dc0..3084a200911b 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -399,7 +399,8 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi)
+static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi,
+					struct drm_connector *connector)
 {
 	u8 *edid;
 	int r;
@@ -437,9 +438,12 @@ static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi)
 	return NULL;
 }
 
-static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+static struct edid *
+hdmi_do_read_edid(struct omap_hdmi *hdmi,
+		  struct edid *(*read)(struct omap_hdmi *hdmi,
+				       struct drm_connector *connector),
+		  struct drm_connector *connector)
 {
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
 	struct edid *edid = NULL;
 	unsigned int cec_addr;
 	bool need_enable;
@@ -461,7 +465,7 @@ static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 	if (r)
 		goto done;
 
-	edid = hdmi_read_edid_data(hdmi);
+	edid = read(hdmi, connector);
 
 done:
 	hdmi_runtime_put(hdmi);
@@ -483,6 +487,12 @@ static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 	return edid;
 }
 
+static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+{
+	return hdmi_do_read_edid(dssdev_to_hdmi(dssdev), hdmi_read_edid_data,
+				 NULL);
+}
+
 static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
 {
 	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
@@ -526,6 +536,55 @@ static const struct omap_dss_device_ops hdmi_ops = {
 	},
 };
 
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static int hdmi4_bridge_attach(struct drm_bridge *bridge, bool create_connector)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	if (!hdmi->output.next_bridge)
+		return 0;
+
+	return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge,
+				 bridge, false);
+}
+
+static struct edid *hdmi4_bridge_read_edid(struct omap_hdmi *hdmi,
+					   struct drm_connector *connector)
+{
+	return drm_do_get_edid(connector, hdmi4_core_ddc_read, &hdmi->core);
+}
+
+static struct edid *hdmi4_bridge_get_edid(struct drm_bridge *bridge,
+					  struct drm_connector *connector)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	return hdmi_do_read_edid(hdmi, hdmi4_bridge_read_edid, connector);
+}
+
+static const struct drm_bridge_funcs hdmi4_bridge_funcs = {
+	.attach = hdmi4_bridge_attach,
+	.get_edid = hdmi4_bridge_get_edid,
+};
+
+static void hdmi4_bridge_init(struct omap_hdmi *hdmi)
+{
+	hdmi->bridge.funcs = &hdmi4_bridge_funcs;
+	hdmi->bridge.of_node = hdmi->pdev->dev.of_node;
+	hdmi->bridge.ops = DRM_BRIDGE_OP_EDID;
+	hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+	drm_bridge_add(&hdmi->bridge);
+}
+
+static void hdmi4_bridge_cleanup(struct omap_hdmi *hdmi)
+{
+	drm_bridge_remove(&hdmi->bridge);
+}
+
 /* -----------------------------------------------------------------------------
  * Audio Callbacks
  */
@@ -717,6 +776,8 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
 	struct omap_dss_device *out = &hdmi->output;
 	int r;
 
+	hdmi4_bridge_init(hdmi);
+
 	out->dev = &hdmi->pdev->dev;
 	out->id = OMAP_DSS_OUTPUT_HDMI;
 	out->type = OMAP_DISPLAY_TYPE_HDMI;
@@ -727,7 +788,7 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
-	r = omapdss_device_init_output(out, NULL);
+	r = omapdss_device_init_output(out, &hdmi->bridge);
 	if (r < 0)
 		return r;
 
@@ -742,6 +803,8 @@ static void hdmi4_uninit_output(struct omap_hdmi *hdmi)
 
 	omapdss_device_unregister(out);
 	omapdss_device_cleanup_output(out);
+
+	hdmi4_bridge_cleanup(hdmi);
 }
 
 static int hdmi4_probe_of(struct omap_hdmi *hdmi)
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 37/60] drm/omap: hdmi5: Register a drm_bridge for EDID read
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (31 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 36/60] drm/omap: hdmi4: Register a drm_bridge for EDID read Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 38/60] drm/omap: hdmi4: Move mode set, enable and disable operations to bridge Laurent Pinchart
                     ` (23 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In order to integrate with a chain of drm_bridge, the internal HDMI5
encoder has to expose the EDID read operation through the drm_bridge
API. Register a bridge at initialisation time to do so.

For the time being make the next bridge in the chain optional as the
HDMI output is still based on omap_dss_device. The create_connector
argument to the bridge attach function is also ignored for the same
reason. This will be changed later when removing the related
omapdrm-specific display drivers.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi5.c | 74 +++++++++++++++++++++++++++--
 1 file changed, 69 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 2dfcacdc072e..dd67a604453f 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -397,8 +397,10 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static struct edid *hdmi_read_edid_data(struct hdmi_core_data *core)
+static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi,
+					struct drm_connector *connector)
 {
+	struct hdmi_core_data *core = &hdmi->core;
 	int max_ext_blocks = 3;
 	int r, n, i;
 	u8 *edid;
@@ -430,9 +432,12 @@ static struct edid *hdmi_read_edid_data(struct hdmi_core_data *core)
 	return NULL;
 }
 
-static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+static struct edid *
+hdmi_do_read_edid(struct omap_hdmi *hdmi,
+		  struct edid *(*read)(struct omap_hdmi *hdmi,
+				       struct drm_connector *connector),
+		  struct drm_connector *connector)
 {
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
 	struct edid *edid;
 	bool need_enable;
 	int idlemode;
@@ -456,7 +461,7 @@ static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 
 	hdmi5_core_ddc_init(&hdmi->core);
 
-	edid = hdmi_read_edid_data(&hdmi->core);
+	edid = read(hdmi, connector);
 
 	hdmi5_core_ddc_uninit(&hdmi->core);
 
@@ -471,6 +476,12 @@ static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 	return (struct edid *)edid;
 }
 
+static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
+{
+	return hdmi_do_read_edid(dssdev_to_hdmi(dssdev), hdmi_read_edid_data,
+				 NULL);
+}
+
 static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
 		const struct hdmi_avi_infoframe *avi)
 {
@@ -506,6 +517,55 @@ static const struct omap_dss_device_ops hdmi_ops = {
 	},
 };
 
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static int hdmi5_bridge_attach(struct drm_bridge *bridge, bool create_connector)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	if (!hdmi->output.next_bridge)
+		return 0;
+
+	return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge,
+				 bridge, false);
+}
+
+static struct edid *hdmi5_bridge_read_edid(struct omap_hdmi *hdmi,
+					   struct drm_connector *connector)
+{
+	return drm_do_get_edid(connector, hdmi5_core_ddc_read, &hdmi->core);
+}
+
+static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
+					  struct drm_connector *connector)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	return hdmi_do_read_edid(hdmi, hdmi5_bridge_read_edid, connector);
+}
+
+static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
+	.attach = hdmi5_bridge_attach,
+	.get_edid = hdmi5_bridge_get_edid,
+};
+
+static void hdmi5_bridge_init(struct omap_hdmi *hdmi)
+{
+	hdmi->bridge.funcs = &hdmi5_bridge_funcs;
+	hdmi->bridge.of_node = hdmi->pdev->dev.of_node;
+	hdmi->bridge.ops = DRM_BRIDGE_OP_EDID;
+	hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+	drm_bridge_add(&hdmi->bridge);
+}
+
+static void hdmi5_bridge_cleanup(struct omap_hdmi *hdmi)
+{
+	drm_bridge_remove(&hdmi->bridge);
+}
+
 /* -----------------------------------------------------------------------------
  * Audio Callbacks
  */
@@ -688,6 +748,8 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
 	struct omap_dss_device *out = &hdmi->output;
 	int r;
 
+	hdmi5_bridge_init(hdmi);
+
 	out->dev = &hdmi->pdev->dev;
 	out->id = OMAP_DSS_OUTPUT_HDMI;
 	out->type = OMAP_DISPLAY_TYPE_HDMI;
@@ -698,7 +760,7 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
-	r = omapdss_device_init_output(out, NULL);
+	r = omapdss_device_init_output(out, &hdmi->bridge);
 	if (r < 0)
 		return r;
 
@@ -713,6 +775,8 @@ static void hdmi5_uninit_output(struct omap_hdmi *hdmi)
 
 	omapdss_device_unregister(out);
 	omapdss_device_cleanup_output(out);
+
+	hdmi5_bridge_cleanup(hdmi);
 }
 
 static int hdmi5_probe_of(struct omap_hdmi *hdmi)
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 38/60] drm/omap: hdmi4: Move mode set, enable and disable operations to bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (32 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 37/60] drm/omap: hdmi5: " Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 39/60] drm/omap: hdmi5: " Laurent Pinchart
                     ` (22 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Move the omap_dss_device .set_timings(), .enable() and .disable()
operations to the drm_bridge functions. As the drm_bridge for the HDMI
encoder is unconditionally registered and attached, those operations
will be called at the appropriate time.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi4.c | 201 +++++++++++++++-------------
 1 file changed, 106 insertions(+), 95 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index 3084a200911b..a8f582c0f387 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -39,6 +39,8 @@
 #include <sound/omap-hdmi-audio.h>
 #include <media/cec.h>
 
+#include <drm/drm_atomic.h>
+
 #include "omapdss.h"
 #include "hdmi4_core.h"
 #include "hdmi4_cec.h"
@@ -248,20 +250,6 @@ static void hdmi_power_off_full(struct omap_hdmi *hdmi)
 	hdmi_power_off_core(hdmi);
 }
 
-static void hdmi_display_set_timings(struct omap_dss_device *dssdev,
-				     const struct drm_display_mode *mode)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	mutex_lock(&hdmi->lock);
-
-	drm_display_mode_to_videomode(mode, &hdmi->cfg.vm);
-
-	dispc_set_tv_pclk(hdmi->dss->dispc, mode->clock * 1000);
-
-	mutex_unlock(&hdmi->lock);
-}
-
 static int hdmi_dump_regs(struct seq_file *s, void *p)
 {
 	struct omap_hdmi *hdmi = s->private;
@@ -295,62 +283,6 @@ static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
 	hdmi_wp_audio_enable(&hd->wp, false);
 }
 
-static void hdmi_display_enable(struct omap_dss_device *dssdev)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-	unsigned long flags;
-	int r;
-
-	DSSDBG("ENTER hdmi_display_enable\n");
-
-	mutex_lock(&hdmi->lock);
-
-	r = hdmi_power_on_full(hdmi);
-	if (r) {
-		DSSERR("failed to power on device\n");
-		goto done;
-	}
-
-	if (hdmi->audio_configured) {
-		r = hdmi4_audio_config(&hdmi->core, &hdmi->wp,
-				       &hdmi->audio_config,
-				       hdmi->cfg.vm.pixelclock);
-		if (r) {
-			DSSERR("Error restoring audio configuration: %d", r);
-			hdmi->audio_abort_cb(&hdmi->pdev->dev);
-			hdmi->audio_configured = false;
-		}
-	}
-
-	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
-	if (hdmi->audio_configured && hdmi->audio_playing)
-		hdmi_start_audio_stream(hdmi);
-	hdmi->display_enabled = true;
-	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
-
-done:
-	mutex_unlock(&hdmi->lock);
-}
-
-static void hdmi_display_disable(struct omap_dss_device *dssdev)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-	unsigned long flags;
-
-	DSSDBG("Enter hdmi_display_disable\n");
-
-	mutex_lock(&hdmi->lock);
-
-	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
-	hdmi_stop_audio_stream(hdmi);
-	hdmi->display_enabled = false;
-	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
-
-	hdmi_power_off_full(hdmi);
-
-	mutex_unlock(&hdmi->lock);
-}
-
 int hdmi4_core_enable(struct hdmi_core_data *core)
 {
 	struct omap_hdmi *hdmi = container_of(core, struct omap_hdmi, core);
@@ -500,39 +432,14 @@ static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
 	hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
 }
 
-static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
-		const struct hdmi_avi_infoframe *avi)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	hdmi->cfg.infoframe = *avi;
-	return 0;
-}
-
-static int hdmi_set_hdmi_mode(struct omap_dss_device *dssdev,
-		bool hdmi_mode)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	hdmi->cfg.hdmi_dvi_mode = hdmi_mode ? HDMI_HDMI : HDMI_DVI;
-	return 0;
-}
-
 static const struct omap_dss_device_ops hdmi_ops = {
 	.connect		= hdmi_connect,
 	.disconnect		= hdmi_disconnect,
 
-	.enable			= hdmi_display_enable,
-	.disable		= hdmi_display_disable,
-
-	.set_timings		= hdmi_display_set_timings,
-
 	.read_edid		= hdmi_read_edid,
 
 	.hdmi = {
 		.lost_hotplug		= hdmi_lost_hotplug,
-		.set_infoframe		= hdmi_set_infoframe,
-		.set_hdmi_mode		= hdmi_set_hdmi_mode,
 	},
 };
 
@@ -551,6 +458,107 @@ static int hdmi4_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 				 bridge, false);
 }
 
+static void hdmi4_bridge_mode_set(struct drm_bridge *bridge,
+				  const struct drm_display_mode *mode,
+				  const struct drm_display_mode *adjusted_mode)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	mutex_lock(&hdmi->lock);
+
+	drm_display_mode_to_videomode(adjusted_mode, &hdmi->cfg.vm);
+
+	dispc_set_tv_pclk(hdmi->dss->dispc, adjusted_mode->clock * 1000);
+
+	mutex_unlock(&hdmi->lock);
+}
+
+static void hdmi4_bridge_enable(struct drm_bridge *bridge,
+				struct drm_atomic_state *state)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+	struct drm_connector_state *conn_state;
+	struct drm_connector *connector;
+	struct drm_crtc_state *crtc_state;
+	unsigned long flags;
+	int ret;
+
+	/*
+	 * None of these should fail, as the bridge can't be enabled without a
+	 * valid CRTC to connector path with fully populated new states.
+	 */
+	connector = drm_atomic_get_new_connector_for_encoder(state,
+							     bridge->encoder);
+	if (WARN_ON(!connector))
+		return;
+	conn_state = drm_atomic_get_new_connector_state(state, connector);
+	if (WARN_ON(!conn_state))
+		return;
+	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+	if (WARN_ON(!crtc_state))
+		return;
+
+	hdmi->cfg.hdmi_dvi_mode = connector->display_info.is_hdmi
+				? HDMI_HDMI : HDMI_DVI;
+
+	if (connector->display_info.is_hdmi) {
+		const struct drm_display_mode *mode;
+		struct hdmi_avi_infoframe avi;
+
+		mode = &crtc_state->adjusted_mode;
+		ret = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector,
+							       mode);
+		if (ret == 0)
+			hdmi->cfg.infoframe = avi;
+	}
+
+	mutex_lock(&hdmi->lock);
+
+	ret = hdmi_power_on_full(hdmi);
+	if (ret) {
+		DSSERR("failed to power on device\n");
+		goto done;
+	}
+
+	if (hdmi->audio_configured) {
+		ret = hdmi4_audio_config(&hdmi->core, &hdmi->wp,
+					 &hdmi->audio_config,
+					 hdmi->cfg.vm.pixelclock);
+		if (ret) {
+			DSSERR("Error restoring audio configuration: %d", ret);
+			hdmi->audio_abort_cb(&hdmi->pdev->dev);
+			hdmi->audio_configured = false;
+		}
+	}
+
+	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
+	if (hdmi->audio_configured && hdmi->audio_playing)
+		hdmi_start_audio_stream(hdmi);
+	hdmi->display_enabled = true;
+	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
+
+done:
+	mutex_unlock(&hdmi->lock);
+}
+
+static void hdmi4_bridge_disable(struct drm_bridge *bridge,
+				 struct drm_atomic_state *state)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+	unsigned long flags;
+
+	mutex_lock(&hdmi->lock);
+
+	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
+	hdmi_stop_audio_stream(hdmi);
+	hdmi->display_enabled = false;
+	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
+
+	hdmi_power_off_full(hdmi);
+
+	mutex_unlock(&hdmi->lock);
+}
+
 static struct edid *hdmi4_bridge_read_edid(struct omap_hdmi *hdmi,
 					   struct drm_connector *connector)
 {
@@ -567,6 +575,9 @@ static struct edid *hdmi4_bridge_get_edid(struct drm_bridge *bridge,
 
 static const struct drm_bridge_funcs hdmi4_bridge_funcs = {
 	.attach = hdmi4_bridge_attach,
+	.mode_set = hdmi4_bridge_mode_set,
+	.atomic_enable = hdmi4_bridge_enable,
+	.atomic_disable = hdmi4_bridge_disable,
 	.get_edid = hdmi4_bridge_get_edid,
 };
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 39/60] drm/omap: hdmi5: Move mode set, enable and disable operations to bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (33 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 38/60] drm/omap: hdmi4: Move mode set, enable and disable operations to bridge Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 40/60] drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation Laurent Pinchart
                     ` (21 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Move the omap_dss_device .set_timings(), .enable() and .disable()
operations to the drm_bridge functions. As the drm_bridge for the HDMI
encoder is unconditionally registered and attached, those operations
will be called at the appropriate time.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi5.c | 204 +++++++++++++++-------------
 1 file changed, 106 insertions(+), 98 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index dd67a604453f..d03f2a415fd4 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -42,6 +42,8 @@
 #include <linux/of_graph.h>
 #include <sound/omap-hdmi-audio.h>
 
+#include <drm/drm_atomic.h>
+
 #include "omapdss.h"
 #include "hdmi5_core.h"
 #include "dss.h"
@@ -247,20 +249,6 @@ static void hdmi_power_off_full(struct omap_hdmi *hdmi)
 	hdmi_power_off_core(hdmi);
 }
 
-static void hdmi_display_set_timings(struct omap_dss_device *dssdev,
-				     const struct drm_display_mode *mode)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	mutex_lock(&hdmi->lock);
-
-	drm_display_mode_to_videomode(mode, &hdmi->cfg.vm);
-
-	dispc_set_tv_pclk(hdmi->dss->dispc, mode->clock * 1000);
-
-	mutex_unlock(&hdmi->lock);
-}
-
 static int hdmi_dump_regs(struct seq_file *s, void *p)
 {
 	struct omap_hdmi *hdmi = s->private;
@@ -296,62 +284,6 @@ static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
 	REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2);
 }
 
-static void hdmi_display_enable(struct omap_dss_device *dssdev)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-	unsigned long flags;
-	int r;
-
-	DSSDBG("ENTER hdmi_display_enable\n");
-
-	mutex_lock(&hdmi->lock);
-
-	r = hdmi_power_on_full(hdmi);
-	if (r) {
-		DSSERR("failed to power on device\n");
-		goto done;
-	}
-
-	if (hdmi->audio_configured) {
-		r = hdmi5_audio_config(&hdmi->core, &hdmi->wp,
-				       &hdmi->audio_config,
-				       hdmi->cfg.vm.pixelclock);
-		if (r) {
-			DSSERR("Error restoring audio configuration: %d", r);
-			hdmi->audio_abort_cb(&hdmi->pdev->dev);
-			hdmi->audio_configured = false;
-		}
-	}
-
-	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
-	if (hdmi->audio_configured && hdmi->audio_playing)
-		hdmi_start_audio_stream(hdmi);
-	hdmi->display_enabled = true;
-	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
-
-done:
-	mutex_unlock(&hdmi->lock);
-}
-
-static void hdmi_display_disable(struct omap_dss_device *dssdev)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-	unsigned long flags;
-
-	DSSDBG("Enter hdmi_display_disable\n");
-
-	mutex_lock(&hdmi->lock);
-
-	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
-	hdmi_stop_audio_stream(hdmi);
-	hdmi->display_enabled = false;
-	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
-
-	hdmi_power_off_full(hdmi);
-
-	mutex_unlock(&hdmi->lock);
-}
-
 static int hdmi_core_enable(struct omap_hdmi *hdmi)
 {
 	int r = 0;
@@ -482,39 +414,11 @@ static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
 				 NULL);
 }
 
-static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
-		const struct hdmi_avi_infoframe *avi)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	hdmi->cfg.infoframe = *avi;
-	return 0;
-}
-
-static int hdmi_set_hdmi_mode(struct omap_dss_device *dssdev,
-		bool hdmi_mode)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	hdmi->cfg.hdmi_dvi_mode = hdmi_mode ? HDMI_HDMI : HDMI_DVI;
-	return 0;
-}
-
 static const struct omap_dss_device_ops hdmi_ops = {
 	.connect		= hdmi_connect,
 	.disconnect		= hdmi_disconnect,
 
-	.enable			= hdmi_display_enable,
-	.disable		= hdmi_display_disable,
-
-	.set_timings		= hdmi_display_set_timings,
-
 	.read_edid		= hdmi_read_edid,
-
-	.hdmi = {
-		.set_infoframe		= hdmi_set_infoframe,
-		.set_hdmi_mode		= hdmi_set_hdmi_mode,
-	},
 };
 
 /* -----------------------------------------------------------------------------
@@ -532,6 +436,107 @@ static int hdmi5_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 				 bridge, false);
 }
 
+static void hdmi5_bridge_mode_set(struct drm_bridge *bridge,
+				  const struct drm_display_mode *mode,
+				  const struct drm_display_mode *adjusted_mode)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	mutex_lock(&hdmi->lock);
+
+	drm_display_mode_to_videomode(adjusted_mode, &hdmi->cfg.vm);
+
+	dispc_set_tv_pclk(hdmi->dss->dispc, adjusted_mode->clock * 1000);
+
+	mutex_unlock(&hdmi->lock);
+}
+
+static void hdmi5_bridge_enable(struct drm_bridge *bridge,
+				struct drm_atomic_state *state)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+	struct drm_connector_state *conn_state;
+	struct drm_connector *connector;
+	struct drm_crtc_state *crtc_state;
+	unsigned long flags;
+	int ret;
+
+	/*
+	 * None of these should fail, as the bridge can't be enabled without a
+	 * valid CRTC to connector path with fully populated new states.
+	 */
+	connector = drm_atomic_get_new_connector_for_encoder(state,
+							     bridge->encoder);
+	if (WARN_ON(!connector))
+		return;
+	conn_state = drm_atomic_get_new_connector_state(state, connector);
+	if (WARN_ON(!conn_state))
+		return;
+	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+	if (WARN_ON(!crtc_state))
+		return;
+
+	hdmi->cfg.hdmi_dvi_mode = connector->display_info.is_hdmi
+				? HDMI_HDMI : HDMI_DVI;
+
+	if (connector->display_info.is_hdmi) {
+		const struct drm_display_mode *mode;
+		struct hdmi_avi_infoframe avi;
+
+		mode = &crtc_state->adjusted_mode;
+		ret = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector,
+							       mode);
+		if (ret == 0)
+			hdmi->cfg.infoframe = avi;
+	}
+
+	mutex_lock(&hdmi->lock);
+
+	ret = hdmi_power_on_full(hdmi);
+	if (ret) {
+		DSSERR("failed to power on device\n");
+		goto done;
+	}
+
+	if (hdmi->audio_configured) {
+		ret = hdmi5_audio_config(&hdmi->core, &hdmi->wp,
+					 &hdmi->audio_config,
+					 hdmi->cfg.vm.pixelclock);
+		if (ret) {
+			DSSERR("Error restoring audio configuration: %d", ret);
+			hdmi->audio_abort_cb(&hdmi->pdev->dev);
+			hdmi->audio_configured = false;
+		}
+	}
+
+	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
+	if (hdmi->audio_configured && hdmi->audio_playing)
+		hdmi_start_audio_stream(hdmi);
+	hdmi->display_enabled = true;
+	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
+
+done:
+	mutex_unlock(&hdmi->lock);
+}
+
+static void hdmi5_bridge_disable(struct drm_bridge *bridge,
+				 struct drm_atomic_state *state)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+	unsigned long flags;
+
+	mutex_lock(&hdmi->lock);
+
+	spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
+	hdmi_stop_audio_stream(hdmi);
+	hdmi->display_enabled = false;
+	spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
+
+	hdmi_power_off_full(hdmi);
+
+	mutex_unlock(&hdmi->lock);
+}
+
 static struct edid *hdmi5_bridge_read_edid(struct omap_hdmi *hdmi,
 					   struct drm_connector *connector)
 {
@@ -548,6 +553,9 @@ static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
 
 static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
 	.attach = hdmi5_bridge_attach,
+	.mode_set = hdmi5_bridge_mode_set,
+	.atomic_enable = hdmi5_bridge_enable,
+	.atomic_disable = hdmi5_bridge_disable,
 	.get_edid = hdmi5_bridge_get_edid,
 };
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 40/60] drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (34 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 39/60] drm/omap: hdmi5: " Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 41/60] drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations Laurent Pinchart
                     ` (20 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The HDMI4 encoder is transitioning to the drm_bridge API, implement the
last missing operation.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi4.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index a8f582c0f387..df56acb5cb27 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -559,6 +559,13 @@ static void hdmi4_bridge_disable(struct drm_bridge *bridge,
 	mutex_unlock(&hdmi->lock);
 }
 
+static void hdmi4_bridge_lost_hotplug(struct drm_bridge *bridge)
+{
+	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+
+	hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
+}
+
 static struct edid *hdmi4_bridge_read_edid(struct omap_hdmi *hdmi,
 					   struct drm_connector *connector)
 {
@@ -578,6 +585,7 @@ static const struct drm_bridge_funcs hdmi4_bridge_funcs = {
 	.mode_set = hdmi4_bridge_mode_set,
 	.atomic_enable = hdmi4_bridge_enable,
 	.atomic_disable = hdmi4_bridge_disable,
+	.lost_hotplug = hdmi4_bridge_lost_hotplug,
 	.get_edid = hdmi4_bridge_get_edid,
 };
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 41/60] drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (35 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 40/60] drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 42/60] drm/omap: venc: Register a drm_bridge Laurent Pinchart
                     ` (19 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The omapdss_hdmi_ops .set_hdmi_mode() and .set_infoframe() operations
operations are not used anymore, remove them.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/omapdss.h  |  3 ---
 drivers/gpu/drm/omapdrm/omap_encoder.c | 26 --------------------------
 2 files changed, 29 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index ccdf42617613..5e0a146b2f36 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -298,9 +298,6 @@ struct omap_dss_writeback_info {
 
 struct omapdss_hdmi_ops {
 	void (*lost_hotplug)(struct omap_dss_device *dssdev);
-	int (*set_hdmi_mode)(struct omap_dss_device *dssdev, bool hdmi_mode);
-	int (*set_infoframe)(struct omap_dss_device *dssdev,
-		const struct hdmi_avi_infoframe *avi);
 };
 
 struct omapdss_dsi_ops {
diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c
index fa4e00e65f9d..8af889f0af06 100644
--- a/drivers/gpu/drm/omapdrm/omap_encoder.c
+++ b/drivers/gpu/drm/omapdrm/omap_encoder.c
@@ -79,28 +79,6 @@ static void omap_encoder_update_videomode_flags(struct videomode *vm,
 	}
 }
 
-static void omap_encoder_hdmi_mode_set(struct drm_connector *connector,
-				       struct drm_encoder *encoder,
-				       struct drm_display_mode *adjusted_mode)
-{
-	struct omap_encoder *omap_encoder = to_omap_encoder(encoder);
-	struct omap_dss_device *dssdev = omap_encoder->output;
-	bool hdmi_mode = connector->display_info.is_hdmi;
-
-	if (dssdev->ops && dssdev->ops->hdmi.set_hdmi_mode)
-		dssdev->ops->hdmi.set_hdmi_mode(dssdev, hdmi_mode);
-
-	if (hdmi_mode && dssdev->ops && dssdev->ops->hdmi.set_infoframe) {
-		struct hdmi_avi_infoframe avi;
-		int r;
-
-		r = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector,
-							     adjusted_mode);
-		if (r == 0)
-			dssdev->ops->hdmi.set_infoframe(dssdev, &avi);
-	}
-}
-
 static void omap_encoder_mode_set(struct drm_encoder *encoder,
 				  struct drm_display_mode *mode,
 				  struct drm_display_mode *adjusted_mode)
@@ -151,10 +129,6 @@ static void omap_encoder_mode_set(struct drm_encoder *encoder,
 		if (dssdev->ops && dssdev->ops->set_timings)
 			dssdev->ops->set_timings(dssdev, adjusted_mode);
 	}
-
-	/* Set the HDMI mode and HDMI infoframe if applicable. */
-	if (output->type == OMAP_DISPLAY_TYPE_HDMI)
-		omap_encoder_hdmi_mode_set(connector, encoder, adjusted_mode);
 }
 
 static void omap_encoder_disable(struct drm_encoder *encoder)
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 42/60] drm/omap: venc: Register a drm_bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (36 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 41/60] drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 43/60] drm/omap: Create connector for bridges Laurent Pinchart
                     ` (18 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In order to integrate with a chain of drm_bridge, the internal VENC
encoder has to expose the mode valid, fixup and set, the enable and
disable and the get modes operations through the drm_bridge API.
Register a bridge at initialisation time to do so.

Most of those operations are removed from the omap_dss_device as they
are now called through the drm_bridge API by the DRM atomic helpers. The
only exception is the .get_modes() operation that is still invoked
through the omap_dss_device-based pipeline.

For the time being make the next bridge in the chain optional as the
VENC output is still based on omap_dss_device. The create_connector
argument to the bridge attach function is also ignored for the same
reason. This will be changed later when removing the related
omapdrm-specific display drivers.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/venc.c | 244 +++++++++++++++++++----------
 1 file changed, 160 insertions(+), 84 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/venc.c b/drivers/gpu/drm/omapdrm/dss/venc.c
index 0a81dd5c93d6..d9f8208e0ee9 100644
--- a/drivers/gpu/drm/omapdrm/dss/venc.c
+++ b/drivers/gpu/drm/omapdrm/dss/venc.c
@@ -37,6 +37,8 @@
 #include <linux/component.h>
 #include <linux/sys_soc.h>
 
+#include <drm/drm_bridge.h>
+
 #include "omapdss.h"
 #include "dss.h"
 
@@ -314,9 +316,11 @@ struct venc_device {
 	bool requires_tv_dac_clk;
 
 	struct omap_dss_device output;
+	struct drm_bridge bridge;
 };
 
 #define dssdev_to_venc(dssdev) container_of(dssdev, struct venc_device, output)
+#define drm_bridge_to_venc(b) container_of(b, struct venc_device, bridge)
 
 static inline void venc_write_reg(struct venc_device *venc, int idx, u32 val)
 {
@@ -488,32 +492,6 @@ static void venc_power_off(struct venc_device *venc)
 	venc_runtime_put(venc);
 }
 
-static void venc_display_enable(struct omap_dss_device *dssdev)
-{
-	struct venc_device *venc = dssdev_to_venc(dssdev);
-
-	DSSDBG("venc_display_enable\n");
-
-	mutex_lock(&venc->venc_lock);
-
-	venc_power_on(venc);
-
-	mutex_unlock(&venc->venc_lock);
-}
-
-static void venc_display_disable(struct omap_dss_device *dssdev)
-{
-	struct venc_device *venc = dssdev_to_venc(dssdev);
-
-	DSSDBG("venc_display_disable\n");
-
-	mutex_lock(&venc->venc_lock);
-
-	venc_power_off(venc);
-
-	mutex_unlock(&venc->venc_lock);
-}
-
 static int venc_get_modes(struct omap_dss_device *dssdev,
 			  struct drm_connector *connector)
 {
@@ -556,57 +534,6 @@ static enum venc_videomode venc_get_videomode(const struct drm_display_mode *mod
 	return VENC_MODE_UNKNOWN;
 }
 
-static void venc_set_timings(struct omap_dss_device *dssdev,
-			     const struct drm_display_mode *mode)
-{
-	struct venc_device *venc = dssdev_to_venc(dssdev);
-	enum venc_videomode venc_mode = venc_get_videomode(mode);
-
-	DSSDBG("venc_set_timings\n");
-
-	mutex_lock(&venc->venc_lock);
-
-	switch (venc_mode) {
-	default:
-		WARN_ON_ONCE(1);
-		/* Fall-through */
-	case VENC_MODE_PAL:
-		venc->config = &venc_config_pal_trm;
-		break;
-
-	case VENC_MODE_NTSC:
-		venc->config = &venc_config_ntsc_trm;
-		break;
-	}
-
-	dispc_set_tv_pclk(venc->dss->dispc, 13500000);
-
-	mutex_unlock(&venc->venc_lock);
-}
-
-static int venc_check_timings(struct omap_dss_device *dssdev,
-			      struct drm_display_mode *mode)
-{
-	DSSDBG("venc_check_timings\n");
-
-	switch (venc_get_videomode(mode)) {
-	case VENC_MODE_PAL:
-		drm_mode_copy(mode, &omap_dss_pal_mode);
-		break;
-
-	case VENC_MODE_NTSC:
-		drm_mode_copy(mode, &omap_dss_ntsc_mode);
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
-	drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
-	drm_mode_set_name(mode);
-	return 0;
-}
-
 static int venc_dump_regs(struct seq_file *s, void *p)
 {
 	struct venc_device *venc = s->private;
@@ -700,15 +627,160 @@ static const struct omap_dss_device_ops venc_ops = {
 	.connect = venc_connect,
 	.disconnect = venc_disconnect,
 
-	.enable = venc_display_enable,
-	.disable = venc_display_disable,
-
-	.check_timings = venc_check_timings,
-	.set_timings = venc_set_timings,
-
 	.get_modes = venc_get_modes,
 };
 
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static int venc_bridge_attach(struct drm_bridge *bridge, bool create_connector)
+{
+	struct venc_device *venc = drm_bridge_to_venc(bridge);
+
+	if (venc->output.next_bridge)
+		return 0;
+
+	return drm_bridge_attach(bridge->encoder, venc->output.next_bridge,
+				 bridge, false);
+}
+
+static enum drm_mode_status
+venc_bridge_mode_valid(struct drm_bridge *bridge,
+		       const struct drm_display_mode *mode)
+{
+	switch (venc_get_videomode(mode)) {
+	case VENC_MODE_PAL:
+	case VENC_MODE_NTSC:
+		return MODE_OK;
+
+	default:
+		return MODE_BAD;
+	}
+}
+
+static bool venc_bridge_mode_fixup(struct drm_bridge *bridge,
+				   const struct drm_display_mode *mode,
+				   struct drm_display_mode *adjusted_mode)
+{
+	const struct drm_display_mode *venc_mode;
+
+	switch (venc_get_videomode(adjusted_mode)) {
+	case VENC_MODE_PAL:
+		venc_mode = &omap_dss_pal_mode;
+		break;
+
+	case VENC_MODE_NTSC:
+		venc_mode = &omap_dss_ntsc_mode;
+		break;
+
+	default:
+		return false;
+	}
+
+	drm_mode_copy(adjusted_mode, venc_mode);
+	drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V);
+	drm_mode_set_name(adjusted_mode);
+
+	return true;
+}
+
+static void venc_bridge_mode_set(struct drm_bridge *bridge,
+				 const struct drm_display_mode *mode,
+				 const struct drm_display_mode *adjusted_mode)
+{
+	struct venc_device *venc = drm_bridge_to_venc(bridge);
+	enum venc_videomode venc_mode = venc_get_videomode(adjusted_mode);
+
+	mutex_lock(&venc->venc_lock);
+
+	switch (venc_mode) {
+	default:
+		WARN_ON_ONCE(1);
+		/* Fall-through */
+	case VENC_MODE_PAL:
+		venc->config = &venc_config_pal_trm;
+		break;
+
+	case VENC_MODE_NTSC:
+		venc->config = &venc_config_ntsc_trm;
+		break;
+	}
+
+	dispc_set_tv_pclk(venc->dss->dispc, 13500000);
+
+	mutex_unlock(&venc->venc_lock);
+}
+
+static void venc_bridge_enable(struct drm_bridge *bridge,
+			       struct drm_atomic_state *state)
+{
+	struct venc_device *venc = drm_bridge_to_venc(bridge);
+
+	mutex_lock(&venc->venc_lock);
+	venc_power_on(venc);
+	mutex_unlock(&venc->venc_lock);
+}
+
+static void venc_bridge_disable(struct drm_bridge *bridge,
+				struct drm_atomic_state *state)
+{
+	struct venc_device *venc = drm_bridge_to_venc(bridge);
+
+	mutex_lock(&venc->venc_lock);
+	venc_power_off(venc);
+	mutex_unlock(&venc->venc_lock);
+}
+
+static int venc_bridge_get_modes(struct drm_bridge *bridge,
+				 struct drm_connector *connector)
+{
+	static const struct drm_display_mode *modes[] = {
+		&omap_dss_pal_mode,
+		&omap_dss_ntsc_mode,
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(modes); ++i) {
+		struct drm_display_mode *mode;
+
+		mode = drm_mode_duplicate(connector->dev, modes[i]);
+		if (!mode)
+			return i;
+
+		mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+		drm_mode_set_name(mode);
+		drm_mode_probed_add(connector, mode);
+	}
+
+	return ARRAY_SIZE(modes);
+}
+
+static const struct drm_bridge_funcs venc_bridge_funcs = {
+	.attach = venc_bridge_attach,
+	.mode_valid = venc_bridge_mode_valid,
+	.mode_fixup = venc_bridge_mode_fixup,
+	.mode_set = venc_bridge_mode_set,
+	.atomic_enable = venc_bridge_enable,
+	.atomic_disable = venc_bridge_disable,
+	.get_modes = venc_bridge_get_modes,
+};
+
+static void venc_bridge_init(struct venc_device *venc)
+{
+	venc->bridge.funcs = &venc_bridge_funcs;
+	venc->bridge.of_node = venc->pdev->dev.of_node;
+	venc->bridge.ops = DRM_BRIDGE_OP_MODES;
+	venc->bridge.type = DRM_MODE_CONNECTOR_SVIDEO;
+
+	drm_bridge_add(&venc->bridge);
+}
+
+static void venc_bridge_cleanup(struct venc_device *venc)
+{
+	drm_bridge_remove(&venc->bridge);
+}
+
 /* -----------------------------------------------------------------------------
  * Component Bind & Unbind
  */
@@ -758,6 +830,8 @@ static int venc_init_output(struct venc_device *venc)
 	struct omap_dss_device *out = &venc->output;
 	int r;
 
+	venc_bridge_init(venc);
+
 	out->dev = &venc->pdev->dev;
 	out->id = OMAP_DSS_OUTPUT_VENC;
 	out->type = OMAP_DISPLAY_TYPE_VENC;
@@ -768,7 +842,7 @@ static int venc_init_output(struct venc_device *venc)
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
 
-	r = omapdss_device_init_output(out, NULL);
+	r = omapdss_device_init_output(out, &venc->bridge);
 	if (r < 0)
 		return r;
 
@@ -781,6 +855,8 @@ static void venc_uninit_output(struct venc_device *venc)
 {
 	omapdss_device_unregister(&venc->output);
 	omapdss_device_cleanup_output(&venc->output);
+
+	venc_bridge_cleanup(venc);
 }
 
 static int venc_probe_of(struct venc_device *venc)
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 43/60] drm/omap: Create connector for bridges
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (37 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 42/60] drm/omap: venc: Register a drm_bridge Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 44/60] drm/omap: Switch the HDMI and VENC outputs to drm_bridge Laurent Pinchart
                     ` (17 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Use the drm_bridge_connector helper to create a connector for pipelines
that use drm_bridge. This allows splitting connector operations across
multiple bridges when necessary, instead of having the last bridge in
the chain creating the connector and handling all connector operations
internally.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/omap_drv.c | 57 ++++++++++++++++++++++++++----
 1 file changed, 50 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 790a2b5a1591..88f9328eda72 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -21,7 +21,9 @@
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge_connector.h>
 #include <drm/drm_fb_helper.h>
+#include <drm/drm_panel.h>
 #include <drm/drm_probe_helper.h>
 
 #include "omap_dmm_tiler.h"
@@ -296,9 +298,13 @@ static int omap_modeset_init(struct drm_device *dev)
 		if (pipe->output->bridge) {
 			ret = drm_bridge_attach(pipe->encoder,
 						pipe->output->bridge, NULL,
-						true);
-			if (ret < 0)
+						false);
+			if (ret < 0) {
+				dev_err(priv->dev,
+					"unable to attach bridge %pOF\n",
+					pipe->output->bridge->of_node);
 				return ret;
+			}
 		}
 
 		id = omap_display_id(pipe->output);
@@ -334,8 +340,31 @@ static int omap_modeset_init(struct drm_device *dev)
 							      encoder);
 			if (!pipe->connector)
 				return -ENOMEM;
+		} else {
+			struct drm_bridge *bridge = pipe->output->bridge;
 
-			drm_connector_attach_encoder(pipe->connector, encoder);
+			pipe->connector = drm_bridge_connector_init(dev,
+								    bridge);
+			if (IS_ERR(pipe->connector)) {
+				dev_err(priv->dev,
+					"unable to create bridge connector for %pOF\n",
+					bridge->of_node);
+				return PTR_ERR(pipe->connector);
+			}
+		}
+
+		drm_connector_attach_encoder(pipe->connector, encoder);
+
+		/*
+		 * FIXME: drm_panel should not store the drm_connector pointer
+		 * internally but should receive it in its .get_modes()
+		 * operation.
+		 */
+		if (pipe->output->panel) {
+			ret = drm_panel_attach(pipe->output->panel,
+					       pipe->connector);
+			if (ret < 0)
+				return ret;
 		}
 
 		crtc = omap_crtc_init(dev, pipe, priv->planes[i]);
@@ -400,8 +429,15 @@ static void omap_modeset_enable_external_hpd(struct drm_device *ddev)
 	unsigned int i;
 
 	for (i = 0; i < priv->num_pipes; i++) {
-		if (priv->pipes[i].connector)
-			omap_connector_enable_hpd(priv->pipes[i].connector);
+		struct drm_connector *connector = priv->pipes[i].connector;
+
+		if (!connector)
+			continue;
+
+		if (priv->pipes[i].output->next)
+			omap_connector_enable_hpd(connector);
+		else
+			drm_bridge_connector_enable_hpd(connector);
 	}
 }
 
@@ -414,8 +450,15 @@ static void omap_modeset_disable_external_hpd(struct drm_device *ddev)
 	unsigned int i;
 
 	for (i = 0; i < priv->num_pipes; i++) {
-		if (priv->pipes[i].connector)
-			omap_connector_disable_hpd(priv->pipes[i].connector);
+		struct drm_connector *connector = priv->pipes[i].connector;
+
+		if (!connector)
+			continue;
+
+		if (priv->pipes[i].output->next)
+			omap_connector_disable_hpd(connector);
+		else
+			drm_bridge_connector_disable_hpd(connector);
 	}
 }
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 44/60] drm/omap: Switch the HDMI and VENC outputs to drm_bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (38 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 43/60] drm/omap: Create connector for bridges Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 45/60] drm/omap: Remove HPD, detect and EDID omapdss operations Laurent Pinchart
                     ` (16 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The TPD12S015, OPA362 and analog and HDMI connectors are now supported
by DRM bridge drivers, and the omapdrm HDMI and VENC outputs can be
handled through the drm_bridge API. Switch the outputs to drm_brdige by
making the next bridge mandatory and removing the related
omapdrm-specific display drivers.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/displays/Kconfig      |  22 --
 drivers/gpu/drm/omapdrm/displays/Makefile     |   4 -
 .../omapdrm/displays/connector-analog-tv.c    | 100 --------
 .../gpu/drm/omapdrm/displays/connector-hdmi.c | 186 ---------------
 .../gpu/drm/omapdrm/displays/encoder-opa362.c | 140 -----------
 .../drm/omapdrm/displays/encoder-tpd12s015.c  | 220 ------------------
 drivers/gpu/drm/omapdrm/dss/hdmi4.c           |   4 +-
 drivers/gpu/drm/omapdrm/dss/hdmi5.c           |   4 +-
 .../gpu/drm/omapdrm/dss/omapdss-boot-init.c   |   5 -
 drivers/gpu/drm/omapdrm/dss/output.c          |   5 +
 drivers/gpu/drm/omapdrm/dss/venc.c            |   4 +-
 11 files changed, 11 insertions(+), 683 deletions(-)
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c

diff --git a/drivers/gpu/drm/omapdrm/displays/Kconfig b/drivers/gpu/drm/omapdrm/displays/Kconfig
index c2566da32ac4..bb73d49fc3e0 100644
--- a/drivers/gpu/drm/omapdrm/displays/Kconfig
+++ b/drivers/gpu/drm/omapdrm/displays/Kconfig
@@ -1,28 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menu "OMAPDRM External Display Device Drivers"
 
-config DRM_OMAP_ENCODER_OPA362
-	tristate "OPA362 external analog amplifier"
-	help
-	  Driver for OPA362 external analog TV amplifier controlled
-	  through a GPIO.
-
-config DRM_OMAP_ENCODER_TPD12S015
-        tristate "TPD12S015 HDMI ESD protection and level shifter"
-	help
-	  Driver for TPD12S015, which offers HDMI ESD protection and level
-	  shifting.
-
-config DRM_OMAP_CONNECTOR_HDMI
-        tristate "HDMI Connector"
-	help
-	  Driver for a generic HDMI connector.
-
-config DRM_OMAP_CONNECTOR_ANALOG_TV
-        tristate "Analog TV Connector"
-	help
-	  Driver for a generic analog TV connector.
-
 config DRM_OMAP_PANEL_DSI_CM
 	tristate "Generic DSI Command Mode Panel"
 	depends on BACKLIGHT_CLASS_DEVICE
diff --git a/drivers/gpu/drm/omapdrm/displays/Makefile b/drivers/gpu/drm/omapdrm/displays/Makefile
index 1db34d4fed64..58ddfa9917f8 100644
--- a/drivers/gpu/drm/omapdrm/displays/Makefile
+++ b/drivers/gpu/drm/omapdrm/displays/Makefile
@@ -1,8 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_DRM_OMAP_ENCODER_OPA362) += encoder-opa362.o
-obj-$(CONFIG_DRM_OMAP_ENCODER_TPD12S015) += encoder-tpd12s015.o
-obj-$(CONFIG_DRM_OMAP_CONNECTOR_HDMI) += connector-hdmi.o
-obj-$(CONFIG_DRM_OMAP_CONNECTOR_ANALOG_TV) += connector-analog-tv.o
 obj-$(CONFIG_DRM_OMAP_PANEL_DSI_CM) += panel-dsi-cm.o
 obj-$(CONFIG_DRM_OMAP_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
 obj-$(CONFIG_DRM_OMAP_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o
diff --git a/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c b/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
deleted file mode 100644
index 2ab18744a5d7..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Analog TV Connector driver
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
-
-#include <linux/slab.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/of.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-
-	struct device *dev;
-};
-
-#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
-
-static int tvc_connect(struct omap_dss_device *src,
-		       struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void tvc_disconnect(struct omap_dss_device *src,
-			   struct omap_dss_device *dst)
-{
-}
-
-static const struct omap_dss_device_ops tvc_ops = {
-	.connect		= tvc_connect,
-	.disconnect		= tvc_disconnect,
-};
-
-static int tvc_probe(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-
-	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
-	if (!ddata)
-		return -ENOMEM;
-
-	platform_set_drvdata(pdev, ddata);
-	ddata->dev = &pdev->dev;
-
-	dssdev = &ddata->dssdev;
-	dssdev->ops = &tvc_ops;
-	dssdev->dev = &pdev->dev;
-	dssdev->type = OMAP_DISPLAY_TYPE_VENC;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int __exit tvc_remove(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
-
-	omapdss_device_unregister(&ddata->dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id tvc_of_match[] = {
-	{ .compatible = "omapdss,svideo-connector", },
-	{ .compatible = "omapdss,composite-video-connector", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, tvc_of_match);
-
-static struct platform_driver tvc_connector_driver = {
-	.probe	= tvc_probe,
-	.remove	= __exit_p(tvc_remove),
-	.driver	= {
-		.name	= "connector-analog-tv",
-		.of_match_table = tvc_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_platform_driver(tvc_connector_driver);
-
-MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
-MODULE_DESCRIPTION("Analog TV Connector driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c b/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
deleted file mode 100644
index 5d2dc103c6b1..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * HDMI Connector driver
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
-
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-	void (*hpd_cb)(void *cb_data, enum drm_connector_status status);
-	void *hpd_cb_data;
-	struct mutex hpd_lock;
-
-	struct device *dev;
-
-	struct gpio_desc *hpd_gpio;
-};
-
-#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
-
-static int hdmic_connect(struct omap_dss_device *src,
-			 struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void hdmic_disconnect(struct omap_dss_device *src,
-			     struct omap_dss_device *dst)
-{
-}
-
-static bool hdmic_detect(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return gpiod_get_value_cansleep(ddata->hpd_gpio);
-}
-
-static void hdmic_register_hpd_cb(struct omap_dss_device *dssdev,
-				  void (*cb)(void *cb_data,
-					    enum drm_connector_status status),
-				  void *cb_data)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	mutex_lock(&ddata->hpd_lock);
-	ddata->hpd_cb = cb;
-	ddata->hpd_cb_data = cb_data;
-	mutex_unlock(&ddata->hpd_lock);
-}
-
-static void hdmic_unregister_hpd_cb(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	mutex_lock(&ddata->hpd_lock);
-	ddata->hpd_cb = NULL;
-	ddata->hpd_cb_data = NULL;
-	mutex_unlock(&ddata->hpd_lock);
-}
-
-static const struct omap_dss_device_ops hdmic_ops = {
-	.connect		= hdmic_connect,
-	.disconnect		= hdmic_disconnect,
-
-	.detect			= hdmic_detect,
-	.register_hpd_cb	= hdmic_register_hpd_cb,
-	.unregister_hpd_cb	= hdmic_unregister_hpd_cb,
-};
-
-static irqreturn_t hdmic_hpd_isr(int irq, void *data)
-{
-	struct panel_drv_data *ddata = data;
-
-	mutex_lock(&ddata->hpd_lock);
-	if (ddata->hpd_cb) {
-		enum drm_connector_status status;
-
-		if (hdmic_detect(&ddata->dssdev))
-			status = connector_status_connected;
-		else
-			status = connector_status_disconnected;
-
-		ddata->hpd_cb(ddata->hpd_cb_data, status);
-	}
-	mutex_unlock(&ddata->hpd_lock);
-
-	return IRQ_HANDLED;
-}
-
-static int hdmic_probe(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	struct gpio_desc *gpio;
-	int r;
-
-	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
-	if (!ddata)
-		return -ENOMEM;
-
-	platform_set_drvdata(pdev, ddata);
-	ddata->dev = &pdev->dev;
-
-	mutex_init(&ddata->hpd_lock);
-
-	/* HPD GPIO */
-	gpio = devm_gpiod_get_optional(&pdev->dev, "hpd", GPIOD_IN);
-	if (IS_ERR(gpio)) {
-		dev_err(&pdev->dev, "failed to parse HPD gpio\n");
-		return PTR_ERR(gpio);
-	}
-
-	ddata->hpd_gpio = gpio;
-
-	if (ddata->hpd_gpio) {
-		r = devm_request_threaded_irq(&pdev->dev,
-				gpiod_to_irq(ddata->hpd_gpio),
-				NULL, hdmic_hpd_isr,
-				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
-				IRQF_ONESHOT,
-				"hdmic hpd", ddata);
-		if (r)
-			return r;
-	}
-
-	dssdev = &ddata->dssdev;
-	dssdev->ops = &hdmic_ops;
-	dssdev->dev = &pdev->dev;
-	dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = ddata->hpd_gpio
-			  ? OMAP_DSS_DEVICE_OP_DETECT | OMAP_DSS_DEVICE_OP_HPD
-			  : 0;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int __exit hdmic_remove(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
-
-	omapdss_device_unregister(&ddata->dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id hdmic_of_match[] = {
-	{ .compatible = "omapdss,hdmi-connector", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, hdmic_of_match);
-
-static struct platform_driver hdmi_connector_driver = {
-	.probe	= hdmic_probe,
-	.remove	= __exit_p(hdmic_remove),
-	.driver	= {
-		.name	= "connector-hdmi",
-		.of_match_table = hdmic_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_platform_driver(hdmi_connector_driver);
-
-MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
-MODULE_DESCRIPTION("HDMI Connector driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c b/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
deleted file mode 100644
index fa2ff7174259..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * OPA362 analog video amplifier with output/power control
- *
- * Copyright (C) 2014 Golden Delicious Computers
- * Author: H. Nikolaus Schaller <hns@goldelico.com>
- *
- * based on encoder-tfp410
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
-
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-
-	struct gpio_desc *enable_gpio;
-};
-
-#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
-
-static int opa362_connect(struct omap_dss_device *src,
-			  struct omap_dss_device *dst)
-{
-	return omapdss_device_connect(dst->dss, dst, dst->next);
-}
-
-static void opa362_disconnect(struct omap_dss_device *src,
-			      struct omap_dss_device *dst)
-{
-	omapdss_device_disconnect(dst, dst->next);
-}
-
-static void opa362_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (ddata->enable_gpio)
-		gpiod_set_value_cansleep(ddata->enable_gpio, 1);
-}
-
-static void opa362_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (ddata->enable_gpio)
-		gpiod_set_value_cansleep(ddata->enable_gpio, 0);
-}
-
-static const struct omap_dss_device_ops opa362_ops = {
-	.connect	= opa362_connect,
-	.disconnect	= opa362_disconnect,
-	.enable		= opa362_enable,
-	.disable	= opa362_disable,
-};
-
-static int opa362_probe(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	struct gpio_desc *gpio;
-
-	dev_dbg(&pdev->dev, "probe\n");
-
-	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
-	if (!ddata)
-		return -ENOMEM;
-
-	platform_set_drvdata(pdev, ddata);
-
-	gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW);
-	if (IS_ERR(gpio))
-		return PTR_ERR(gpio);
-
-	ddata->enable_gpio = gpio;
-
-	dssdev = &ddata->dssdev;
-	dssdev->ops = &opa362_ops;
-	dssdev->dev = &pdev->dev;
-	dssdev->type = OMAP_DISPLAY_TYPE_VENC;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 1;
-
-	dssdev->next = omapdss_of_find_connected_device(pdev->dev.of_node, 1);
-	if (IS_ERR(dssdev->next)) {
-		if (PTR_ERR(dssdev->next) != -EPROBE_DEFER)
-			dev_err(&pdev->dev, "failed to find video sink\n");
-		return PTR_ERR(dssdev->next);
-	}
-
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int __exit opa362_remove(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	if (dssdev->next)
-		omapdss_device_put(dssdev->next);
-	omapdss_device_unregister(&ddata->dssdev);
-
-	opa362_disable(dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id opa362_of_match[] = {
-	{ .compatible = "omapdss,ti,opa362", },
-	{},
-};
-MODULE_DEVICE_TABLE(of, opa362_of_match);
-
-static struct platform_driver opa362_driver = {
-	.probe	= opa362_probe,
-	.remove	= __exit_p(opa362_remove),
-	.driver	= {
-		.name	= "amplifier-opa362",
-		.of_match_table = opa362_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_platform_driver(opa362_driver);
-
-MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
-MODULE_DESCRIPTION("OPA362 analog video amplifier with output/power control");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c b/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
deleted file mode 100644
index b0e62ed3fce4..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * TPD12S015 HDMI ESD protection & level shifter chip driver
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
-
-#include <linux/completion.h>
-#include <linux/delay.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/platform_device.h>
-#include <linux/gpio/consumer.h>
-#include <linux/mutex.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-	void (*hpd_cb)(void *cb_data, enum drm_connector_status status);
-	void *hpd_cb_data;
-	struct mutex hpd_lock;
-
-	struct gpio_desc *ct_cp_hpd_gpio;
-	struct gpio_desc *ls_oe_gpio;
-	struct gpio_desc *hpd_gpio;
-};
-
-#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
-
-static int tpd_connect(struct omap_dss_device *src,
-		       struct omap_dss_device *dst)
-{
-	struct panel_drv_data *ddata = to_panel_data(dst);
-	int r;
-
-	r = omapdss_device_connect(dst->dss, dst, dst->next);
-	if (r)
-		return r;
-
-	gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1);
-	gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1);
-
-	/* DC-DC converter needs at max 300us to get to 90% of 5V */
-	udelay(300);
-
-	return 0;
-}
-
-static void tpd_disconnect(struct omap_dss_device *src,
-			   struct omap_dss_device *dst)
-{
-	struct panel_drv_data *ddata = to_panel_data(dst);
-
-	gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0);
-	gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0);
-
-	omapdss_device_disconnect(dst, dst->next);
-}
-
-static bool tpd_detect(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return gpiod_get_value_cansleep(ddata->hpd_gpio);
-}
-
-static void tpd_register_hpd_cb(struct omap_dss_device *dssdev,
-				void (*cb)(void *cb_data,
-					  enum drm_connector_status status),
-				void *cb_data)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	mutex_lock(&ddata->hpd_lock);
-	ddata->hpd_cb = cb;
-	ddata->hpd_cb_data = cb_data;
-	mutex_unlock(&ddata->hpd_lock);
-}
-
-static void tpd_unregister_hpd_cb(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	mutex_lock(&ddata->hpd_lock);
-	ddata->hpd_cb = NULL;
-	ddata->hpd_cb_data = NULL;
-	mutex_unlock(&ddata->hpd_lock);
-}
-
-static const struct omap_dss_device_ops tpd_ops = {
-	.connect		= tpd_connect,
-	.disconnect		= tpd_disconnect,
-	.detect			= tpd_detect,
-	.register_hpd_cb	= tpd_register_hpd_cb,
-	.unregister_hpd_cb	= tpd_unregister_hpd_cb,
-};
-
-static irqreturn_t tpd_hpd_isr(int irq, void *data)
-{
-	struct panel_drv_data *ddata = data;
-
-	mutex_lock(&ddata->hpd_lock);
-	if (ddata->hpd_cb) {
-		enum drm_connector_status status;
-
-		if (tpd_detect(&ddata->dssdev))
-			status = connector_status_connected;
-		else
-			status = connector_status_disconnected;
-
-		ddata->hpd_cb(ddata->hpd_cb_data, status);
-	}
-	mutex_unlock(&ddata->hpd_lock);
-
-	return IRQ_HANDLED;
-}
-
-static int tpd_probe(struct platform_device *pdev)
-{
-	struct omap_dss_device *dssdev;
-	struct panel_drv_data *ddata;
-	int r;
-	struct gpio_desc *gpio;
-
-	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
-	if (!ddata)
-		return -ENOMEM;
-
-	platform_set_drvdata(pdev, ddata);
-
-	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
-		 GPIOD_OUT_LOW);
-	if (IS_ERR(gpio))
-		return PTR_ERR(gpio);
-
-	ddata->ct_cp_hpd_gpio = gpio;
-
-	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
-		 GPIOD_OUT_LOW);
-	if (IS_ERR(gpio))
-		return PTR_ERR(gpio);
-
-	ddata->ls_oe_gpio = gpio;
-
-	gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2,
-		GPIOD_IN);
-	if (IS_ERR(gpio))
-		return PTR_ERR(gpio);
-
-	ddata->hpd_gpio = gpio;
-
-	mutex_init(&ddata->hpd_lock);
-
-	r = devm_request_threaded_irq(&pdev->dev, gpiod_to_irq(ddata->hpd_gpio),
-		NULL, tpd_hpd_isr,
-		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-		"tpd12s015 hpd", ddata);
-	if (r)
-		return r;
-
-	dssdev = &ddata->dssdev;
-	dssdev->ops = &tpd_ops;
-	dssdev->dev = &pdev->dev;
-	dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 1;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_DETECT
-			  | OMAP_DSS_DEVICE_OP_HPD;
-
-	dssdev->next = omapdss_of_find_connected_device(pdev->dev.of_node, 1);
-	if (IS_ERR(dssdev->next)) {
-		if (PTR_ERR(dssdev->next) != -EPROBE_DEFER)
-			dev_err(&pdev->dev, "failed to find video sink\n");
-		return PTR_ERR(dssdev->next);
-	}
-
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int __exit tpd_remove(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	if (dssdev->next)
-		omapdss_device_put(dssdev->next);
-	omapdss_device_unregister(&ddata->dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id tpd_of_match[] = {
-	{ .compatible = "omapdss,ti,tpd12s015", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, tpd_of_match);
-
-static struct platform_driver tpd_driver = {
-	.probe	= tpd_probe,
-	.remove	= __exit_p(tpd_remove),
-	.driver	= {
-		.name	= "tpd12s015",
-		.of_match_table = tpd_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_platform_driver(tpd_driver);
-
-MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
-MODULE_DESCRIPTION("TPD12S015 driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index df56acb5cb27..b65afa8b93c6 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -451,8 +451,8 @@ static int hdmi4_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
 
-	if (!hdmi->output.next_bridge)
-		return 0;
+	if (create_connector)
+		return -EINVAL;
 
 	return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge,
 				 bridge, false);
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index d03f2a415fd4..fd386e189ab4 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -429,8 +429,8 @@ static int hdmi5_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
 
-	if (!hdmi->output.next_bridge)
-		return 0;
+	if (create_connector)
+		return -EINVAL;
 
 	return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge,
 				 bridge, false);
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c b/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c
index e02aa8e70968..24e36d68f3c4 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c
@@ -185,16 +185,11 @@ static const struct of_device_id omapdss_of_match[] __initconst = {
 };
 
 static const struct of_device_id omapdss_of_fixups_whitelist[] __initconst = {
-	{ .compatible = "composite-video-connector" },
-	{ .compatible = "hdmi-connector" },
 	{ .compatible = "lgphilips,lb035q02" },
 	{ .compatible = "nec,nl8048hl11" },
 	{ .compatible = "panel-dsi-cm" },
 	{ .compatible = "sharp,ls037v7dw01" },
 	{ .compatible = "sony,acx565akm" },
-	{ .compatible = "svideo-connector" },
-	{ .compatible = "ti,opa362" },
-	{ .compatible = "ti,tpd12s015" },
 	{ .compatible = "toppoly,td028ttec1" },
 	{ .compatible = "tpo,td028ttec1" },
 	{ .compatible = "tpo,td043mtea1" },
diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c
index 71693d1abea8..258c326a5098 100644
--- a/drivers/gpu/drm/omapdrm/dss/output.c
+++ b/drivers/gpu/drm/omapdrm/dss/output.c
@@ -72,6 +72,11 @@ int omapdss_device_init_output(struct omap_dss_device *out,
 	}
 
 	if (local_bridge) {
+		if (!out->bridge) {
+			ret = -EPROBE_DEFER;
+			goto error;
+		}
+
 		out->next_bridge = out->bridge;
 		out->bridge = local_bridge;
 	}
diff --git a/drivers/gpu/drm/omapdrm/dss/venc.c b/drivers/gpu/drm/omapdrm/dss/venc.c
index d9f8208e0ee9..66cd75e71939 100644
--- a/drivers/gpu/drm/omapdrm/dss/venc.c
+++ b/drivers/gpu/drm/omapdrm/dss/venc.c
@@ -638,8 +638,8 @@ static int venc_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
 	struct venc_device *venc = drm_bridge_to_venc(bridge);
 
-	if (venc->output.next_bridge)
-		return 0;
+	if (create_connector)
+		return -EINVAL;
 
 	return drm_bridge_attach(bridge->encoder, venc->output.next_bridge,
 				 bridge, false);
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 45/60] drm/omap: Remove HPD, detect and EDID omapdss operations
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (39 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 44/60] drm/omap: Switch the HDMI and VENC outputs to drm_bridge Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 46/60] drm/omap: hdmi: Remove omap_dss_device operations Laurent Pinchart
                     ` (15 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Due to the removal of several omapdrm display drivers, the omapdss HPD,
detected and EDID operations are not used anymore. Remove them and all
related code.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi4.c      |  59 -------
 drivers/gpu/drm/omapdrm/dss/hdmi5.c      |  44 ------
 drivers/gpu/drm/omapdrm/dss/omapdss.h    |  25 +--
 drivers/gpu/drm/omapdrm/omap_connector.c | 190 +++--------------------
 drivers/gpu/drm/omapdrm/omap_connector.h |   2 -
 drivers/gpu/drm/omapdrm/omap_drv.c       |   8 +-
 6 files changed, 22 insertions(+), 306 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index b65afa8b93c6..2feae9ba1729 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -331,45 +331,6 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi,
-					struct drm_connector *connector)
-{
-	u8 *edid;
-	int r;
-
-	edid = kzalloc(512, GFP_KERNEL);
-	if (!edid)
-		return NULL;
-
-	r = hdmi4_core_ddc_read(&hdmi->core, edid, 0, EDID_LENGTH);
-	if (r)
-		goto error;
-
-	if (edid[0x7e] > 0) {
-		char checksum = 0;
-		unsigned int i;
-
-		r = hdmi4_core_ddc_read(&hdmi->core, edid + EDID_LENGTH, 1,
-					EDID_LENGTH);
-		if (r)
-			goto error;
-
-		for (i = 0; i < EDID_LENGTH; ++i)
-			checksum += edid[EDID_LENGTH + i];
-
-		if (checksum != 0) {
-			DSSERR("E-EDID checksum failed!!\n");
-			goto error;
-		}
-	}
-
-	return (struct edid *)edid;
-
-error:
-	kfree(edid);
-	return NULL;
-}
-
 static struct edid *
 hdmi_do_read_edid(struct omap_hdmi *hdmi,
 		  struct edid *(*read)(struct omap_hdmi *hdmi,
@@ -419,28 +380,9 @@ hdmi_do_read_edid(struct omap_hdmi *hdmi,
 	return edid;
 }
 
-static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
-{
-	return hdmi_do_read_edid(dssdev_to_hdmi(dssdev), hdmi_read_edid_data,
-				 NULL);
-}
-
-static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
-{
-	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
-
-	hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
-}
-
 static const struct omap_dss_device_ops hdmi_ops = {
 	.connect		= hdmi_connect,
 	.disconnect		= hdmi_disconnect,
-
-	.read_edid		= hdmi_read_edid,
-
-	.hdmi = {
-		.lost_hotplug		= hdmi_lost_hotplug,
-	},
 };
 
 /* -----------------------------------------------------------------------------
@@ -805,7 +747,6 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
 	out->ops = &hdmi_ops;
 	out->owner = THIS_MODULE;
 	out->of_port = 0;
-	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
 	r = omapdss_device_init_output(out, &hdmi->bridge);
 	if (r < 0)
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index fd386e189ab4..0502e692d646 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -329,41 +329,6 @@ static void hdmi_disconnect(struct omap_dss_device *src,
 	omapdss_device_disconnect(dst, dst->next);
 }
 
-static struct edid *hdmi_read_edid_data(struct omap_hdmi *hdmi,
-					struct drm_connector *connector)
-{
-	struct hdmi_core_data *core = &hdmi->core;
-	int max_ext_blocks = 3;
-	int r, n, i;
-	u8 *edid;
-
-	edid = kzalloc(512, GFP_KERNEL);
-	if (!edid)
-		return NULL;
-
-	r = hdmi5_core_ddc_read(core, edid, 0, EDID_LENGTH);
-	if (r)
-		goto error;
-
-	n = edid[0x7e];
-
-	if (n > max_ext_blocks)
-		n = max_ext_blocks;
-
-	for (i = 1; i <= n; i++) {
-		r = hdmi5_core_ddc_read(core, edid + i * EDID_LENGTH, i,
-					EDID_LENGTH);
-		if (r)
-			goto error;
-	}
-
-	return (struct edid *)edid;
-
-error:
-	kfree(edid);
-	return NULL;
-}
-
 static struct edid *
 hdmi_do_read_edid(struct omap_hdmi *hdmi,
 		  struct edid *(*read)(struct omap_hdmi *hdmi,
@@ -408,17 +373,9 @@ hdmi_do_read_edid(struct omap_hdmi *hdmi,
 	return (struct edid *)edid;
 }
 
-static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
-{
-	return hdmi_do_read_edid(dssdev_to_hdmi(dssdev), hdmi_read_edid_data,
-				 NULL);
-}
-
 static const struct omap_dss_device_ops hdmi_ops = {
 	.connect		= hdmi_connect,
 	.disconnect		= hdmi_disconnect,
-
-	.read_edid		= hdmi_read_edid,
 };
 
 /* -----------------------------------------------------------------------------
@@ -766,7 +723,6 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
 	out->ops = &hdmi_ops;
 	out->owner = THIS_MODULE;
 	out->of_port = 0;
-	out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
 
 	r = omapdss_device_init_output(out, &hdmi->bridge);
 	if (r < 0)
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 5e0a146b2f36..24b421f67406 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -296,10 +296,6 @@ struct omap_dss_writeback_info {
 	u8 pre_mult_alpha;
 };
 
-struct omapdss_hdmi_ops {
-	void (*lost_hotplug)(struct omap_dss_device *dssdev);
-};
-
 struct omapdss_dsi_ops {
 	void (*disable)(struct omap_dss_device *dssdev, bool disconnect_lanes,
 			bool enter_ulps);
@@ -367,36 +363,17 @@ struct omap_dss_device_ops {
 	void (*set_timings)(struct omap_dss_device *dssdev,
 			    const struct drm_display_mode *mode);
 
-	bool (*detect)(struct omap_dss_device *dssdev);
-
-	void (*register_hpd_cb)(struct omap_dss_device *dssdev,
-				void (*cb)(void *cb_data,
-					  enum drm_connector_status status),
-				void *cb_data);
-	void (*unregister_hpd_cb)(struct omap_dss_device *dssdev);
-
-	struct edid *(*read_edid)(struct omap_dss_device *dssdev);
-
 	int (*get_modes)(struct omap_dss_device *dssdev,
 			 struct drm_connector *connector);
 
-	union {
-		const struct omapdss_hdmi_ops hdmi;
-		const struct omapdss_dsi_ops dsi;
-	};
+	const struct omapdss_dsi_ops dsi;
 };
 
 /**
  * enum omap_dss_device_ops_flag - Indicates which device ops are supported
- * @OMAP_DSS_DEVICE_OP_DETECT: The device supports output connection detection
- * @OMAP_DSS_DEVICE_OP_HPD: The device supports all hot-plug-related operations
- * @OMAP_DSS_DEVICE_OP_EDID: The device supports reading EDID
  * @OMAP_DSS_DEVICE_OP_MODES: The device supports reading modes
  */
 enum omap_dss_device_ops_flag {
-	OMAP_DSS_DEVICE_OP_DETECT = BIT(0),
-	OMAP_DSS_DEVICE_OP_HPD = BIT(1),
-	OMAP_DSS_DEVICE_OP_EDID = BIT(2),
 	OMAP_DSS_DEVICE_OP_MODES = BIT(3),
 };
 
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index f8b2e63d9e74..4b8a99be2086 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -30,111 +30,22 @@
 struct omap_connector {
 	struct drm_connector base;
 	struct omap_dss_device *output;
-	struct omap_dss_device *hpd;
 };
 
-static void omap_connector_hpd_notify(struct drm_connector *connector,
-				      enum drm_connector_status status)
-{
-	struct omap_connector *omap_connector = to_omap_connector(connector);
-	struct omap_dss_device *dssdev;
-
-	if (status != connector_status_disconnected)
-		return;
-
-	/*
-	 * Notify all devics in the pipeline of disconnection. This is required
-	 * to let the HDMI encoders reset their internal state related to
-	 * connection status, such as the CEC address.
-	 */
-	for (dssdev = omap_connector->output; dssdev; dssdev = dssdev->next) {
-		if (dssdev->ops && dssdev->ops->hdmi.lost_hotplug)
-			dssdev->ops->hdmi.lost_hotplug(dssdev);
-	}
-}
-
-static void omap_connector_hpd_cb(void *cb_data,
-				  enum drm_connector_status status)
-{
-	struct omap_connector *omap_connector = cb_data;
-	struct drm_connector *connector = &omap_connector->base;
-	struct drm_device *dev = connector->dev;
-	enum drm_connector_status old_status;
-
-	mutex_lock(&dev->mode_config.mutex);
-	old_status = connector->status;
-	connector->status = status;
-	mutex_unlock(&dev->mode_config.mutex);
-
-	if (old_status == status)
-		return;
-
-	omap_connector_hpd_notify(connector, status);
-
-	drm_kms_helper_hotplug_event(dev);
-}
-
-void omap_connector_enable_hpd(struct drm_connector *connector)
-{
-	struct omap_connector *omap_connector = to_omap_connector(connector);
-	struct omap_dss_device *hpd = omap_connector->hpd;
-
-	if (hpd)
-		hpd->ops->register_hpd_cb(hpd, omap_connector_hpd_cb,
-					  omap_connector);
-}
-
-void omap_connector_disable_hpd(struct drm_connector *connector)
-{
-	struct omap_connector *omap_connector = to_omap_connector(connector);
-	struct omap_dss_device *hpd = omap_connector->hpd;
-
-	if (hpd)
-		hpd->ops->unregister_hpd_cb(hpd);
-}
-
-static struct omap_dss_device *
-omap_connector_find_device(struct drm_connector *connector,
-			   enum omap_dss_device_ops_flag op)
-{
-	struct omap_connector *omap_connector = to_omap_connector(connector);
-	struct omap_dss_device *dssdev = NULL;
-	struct omap_dss_device *d;
-
-	for (d = omap_connector->output; d; d = d->next) {
-		if (d->ops_flags & op)
-			dssdev = d;
-	}
-
-	return dssdev;
-}
-
 static enum drm_connector_status omap_connector_detect(
 		struct drm_connector *connector, bool force)
 {
-	struct omap_dss_device *dssdev;
 	enum drm_connector_status status;
 
-	dssdev = omap_connector_find_device(connector,
-					    OMAP_DSS_DEVICE_OP_DETECT);
-
-	if (dssdev) {
-		status = dssdev->ops->detect(dssdev)
-		       ? connector_status_connected
-		       : connector_status_disconnected;
-
-		omap_connector_hpd_notify(connector, status);
-	} else {
-		switch (connector->connector_type) {
-		case DRM_MODE_CONNECTOR_DPI:
-		case DRM_MODE_CONNECTOR_LVDS:
-		case DRM_MODE_CONNECTOR_DSI:
-			status = connector_status_connected;
-			break;
-		default:
-			status = connector_status_unknown;
-			break;
-		}
+	switch (connector->connector_type) {
+	case DRM_MODE_CONNECTOR_DPI:
+	case DRM_MODE_CONNECTOR_LVDS:
+	case DRM_MODE_CONNECTOR_DSI:
+		status = connector_status_connected;
+		break;
+	default:
+		status = connector_status_unknown;
+		break;
 	}
 
 	VERB("%s: %d (force=%d)", connector->name, status, force);
@@ -148,14 +59,6 @@ static void omap_connector_destroy(struct drm_connector *connector)
 
 	DBG("%s", connector->name);
 
-	if (omap_connector->hpd) {
-		struct omap_dss_device *hpd = omap_connector->hpd;
-
-		hpd->ops->unregister_hpd_cb(hpd);
-		omapdss_device_put(hpd);
-		omap_connector->hpd = NULL;
-	}
-
 	drm_connector_unregister(connector);
 	drm_connector_cleanup(connector);
 
@@ -164,63 +67,27 @@ static void omap_connector_destroy(struct drm_connector *connector)
 	kfree(omap_connector);
 }
 
-static int omap_connector_get_modes_edid(struct drm_connector *connector,
-					 struct omap_dss_device *dssdev)
-{
-	enum drm_connector_status status;
-	struct edid *edid;
-	int n;
-
-	status = omap_connector_detect(connector, false);
-	if (status != connector_status_connected)
-		goto no_edid;
-
-	edid = dssdev->ops->read_edid(dssdev);
-	if (!edid || !drm_edid_is_valid(edid)) {
-		kfree(edid);
-		goto no_edid;
-	}
-
-	drm_connector_update_edid_property(connector, edid);
-	n = drm_add_edid_modes(connector, edid);
-
-	kfree(edid);
-	return n;
-
-no_edid:
-	drm_connector_update_edid_property(connector, NULL);
-	return 0;
-}
-
 static int omap_connector_get_modes(struct drm_connector *connector)
 {
-	struct omap_dss_device *dssdev;
+	struct omap_connector *omap_connector = to_omap_connector(connector);
+	struct omap_dss_device *dssdev = NULL;
+	struct omap_dss_device *d;
 
 	DBG("%s", connector->name);
 
 	/*
-	 * If display exposes EDID, then we parse that in the normal way to
-	 * build table of supported modes.
+	 * If the display pipeline reports modes (e.g. with a fixed resolution
+	 * panel or an analog TV output), query it.
 	 */
-	dssdev = omap_connector_find_device(connector,
-					    OMAP_DSS_DEVICE_OP_EDID);
-	if (dssdev)
-		return omap_connector_get_modes_edid(connector, dssdev);
+	for (d = omap_connector->output; d; d = d->next) {
+		if (d->ops_flags & OMAP_DSS_DEVICE_OP_MODES)
+			dssdev = d;
+	}
 
-	/*
-	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
-	 * resolution panel or an analog TV output), query it.
-	 */
-	dssdev = omap_connector_find_device(connector,
-					    OMAP_DSS_DEVICE_OP_MODES);
 	if (dssdev)
 		return dssdev->ops->get_modes(dssdev, connector);
 
-	/*
-	 * We can't retrieve modes, which can happen for instance for a DVI or
-	 * VGA output with the DDC bus unconnected. The KMS core will add the
-	 * default modes.
-	 */
+	/* We can't retrieve modes. The KMS core will add the default modes. */
 	return 0;
 }
 
@@ -301,7 +168,6 @@ struct drm_connector *omap_connector_init(struct drm_device *dev,
 {
 	struct drm_connector *connector = NULL;
 	struct omap_connector *omap_connector;
-	struct omap_dss_device *dssdev;
 
 	DBG("%s", output->name);
 
@@ -319,24 +185,6 @@ struct drm_connector *omap_connector_init(struct drm_device *dev,
 			   omap_connector_get_type(output));
 	drm_connector_helper_add(connector, &omap_connector_helper_funcs);
 
-	/*
-	 * Initialize connector status handling. First try to find a device that
-	 * supports hot-plug reporting. If it fails, fall back to a device that
-	 * support polling. If that fails too, we don't support hot-plug
-	 * detection at all.
-	 */
-	dssdev = omap_connector_find_device(connector, OMAP_DSS_DEVICE_OP_HPD);
-	if (dssdev) {
-		omap_connector->hpd = omapdss_device_get(dssdev);
-		connector->polled = DRM_CONNECTOR_POLL_HPD;
-	} else {
-		dssdev = omap_connector_find_device(connector,
-						    OMAP_DSS_DEVICE_OP_DETECT);
-		if (dssdev)
-			connector->polled = DRM_CONNECTOR_POLL_CONNECT |
-					    DRM_CONNECTOR_POLL_DISCONNECT;
-	}
-
 	return connector;
 
 fail:
diff --git a/drivers/gpu/drm/omapdrm/omap_connector.h b/drivers/gpu/drm/omapdrm/omap_connector.h
index a23b3a80657b..487b8653e00c 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.h
+++ b/drivers/gpu/drm/omapdrm/omap_connector.h
@@ -32,8 +32,6 @@ struct omap_dss_device;
 struct drm_connector *omap_connector_init(struct drm_device *dev,
 					  struct omap_dss_device *output,
 					  struct drm_encoder *encoder);
-void omap_connector_enable_hpd(struct drm_connector *connector);
-void omap_connector_disable_hpd(struct drm_connector *connector);
 enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
 					const struct drm_display_mode *mode,
 					struct drm_display_mode *adjusted_mode);
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 88f9328eda72..89a3633b97ad 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -434,9 +434,7 @@ static void omap_modeset_enable_external_hpd(struct drm_device *ddev)
 		if (!connector)
 			continue;
 
-		if (priv->pipes[i].output->next)
-			omap_connector_enable_hpd(connector);
-		else
+		if (priv->pipes[i].output->bridge)
 			drm_bridge_connector_enable_hpd(connector);
 	}
 }
@@ -455,9 +453,7 @@ static void omap_modeset_disable_external_hpd(struct drm_device *ddev)
 		if (!connector)
 			continue;
 
-		if (priv->pipes[i].output->next)
-			omap_connector_disable_hpd(connector);
-		else
+		if (priv->pipes[i].output->bridge)
 			drm_bridge_connector_disable_hpd(connector);
 	}
 }
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 46/60] drm/omap: hdmi: Remove omap_dss_device operations
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (40 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 45/60] drm/omap: Remove HPD, detect and EDID omapdss operations Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 47/60] drm/omap: venc: " Laurent Pinchart
                     ` (14 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Now that the HDMI outputs are driven fully through the drm_bridge API
their omap_dss_device operations are not used anymore. Remove them.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi.h  |  1 -
 drivers/gpu/drm/omapdrm/dss/hdmi4.c | 18 ------------------
 drivers/gpu/drm/omapdrm/dss/hdmi5.c | 18 ------------------
 3 files changed, 37 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi.h b/drivers/gpu/drm/omapdrm/dss/hdmi.h
index d1e3f625eebc..a501cfc21af4 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi.h
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi.h
@@ -391,7 +391,6 @@ struct omap_hdmi {
 	bool display_enabled;
 };
 
-#define dssdev_to_hdmi(dssdev) container_of(dssdev, struct omap_hdmi, output)
 #define drm_bridge_to_hdmi(b) container_of(b, struct omap_hdmi, bridge)
 
 #endif
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index 2feae9ba1729..70d98f39b8c6 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -319,18 +319,6 @@ void hdmi4_core_disable(struct hdmi_core_data *core)
 	mutex_unlock(&hdmi->lock);
 }
 
-static int hdmi_connect(struct omap_dss_device *src,
-			struct omap_dss_device *dst)
-{
-	return omapdss_device_connect(dst->dss, dst, dst->next);
-}
-
-static void hdmi_disconnect(struct omap_dss_device *src,
-			    struct omap_dss_device *dst)
-{
-	omapdss_device_disconnect(dst, dst->next);
-}
-
 static struct edid *
 hdmi_do_read_edid(struct omap_hdmi *hdmi,
 		  struct edid *(*read)(struct omap_hdmi *hdmi,
@@ -380,11 +368,6 @@ hdmi_do_read_edid(struct omap_hdmi *hdmi,
 	return edid;
 }
 
-static const struct omap_dss_device_ops hdmi_ops = {
-	.connect		= hdmi_connect,
-	.disconnect		= hdmi_disconnect,
-};
-
 /* -----------------------------------------------------------------------------
  * DRM Bridge Operations
  */
@@ -744,7 +727,6 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
 	out->type = OMAP_DISPLAY_TYPE_HDMI;
 	out->name = "hdmi.0";
 	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
-	out->ops = &hdmi_ops;
 	out->owner = THIS_MODULE;
 	out->of_port = 0;
 
diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 0502e692d646..6399ee5bd6d6 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -317,18 +317,6 @@ static void hdmi_core_disable(struct omap_hdmi *hdmi)
 	mutex_unlock(&hdmi->lock);
 }
 
-static int hdmi_connect(struct omap_dss_device *src,
-			struct omap_dss_device *dst)
-{
-	return omapdss_device_connect(dst->dss, dst, dst->next);
-}
-
-static void hdmi_disconnect(struct omap_dss_device *src,
-			    struct omap_dss_device *dst)
-{
-	omapdss_device_disconnect(dst, dst->next);
-}
-
 static struct edid *
 hdmi_do_read_edid(struct omap_hdmi *hdmi,
 		  struct edid *(*read)(struct omap_hdmi *hdmi,
@@ -373,11 +361,6 @@ hdmi_do_read_edid(struct omap_hdmi *hdmi,
 	return (struct edid *)edid;
 }
 
-static const struct omap_dss_device_ops hdmi_ops = {
-	.connect		= hdmi_connect,
-	.disconnect		= hdmi_disconnect,
-};
-
 /* -----------------------------------------------------------------------------
  * DRM Bridge Operations
  */
@@ -720,7 +703,6 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
 	out->type = OMAP_DISPLAY_TYPE_HDMI;
 	out->name = "hdmi.0";
 	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
-	out->ops = &hdmi_ops;
 	out->owner = THIS_MODULE;
 	out->of_port = 0;
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 47/60] drm/omap: venc: Remove omap_dss_device operations
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (41 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 46/60] drm/omap: hdmi: Remove omap_dss_device operations Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 48/60] drm/omap: hdmi4: Simplify EDID read Laurent Pinchart
                     ` (13 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Now that the VENC output is driven fully through the drm_bridge API its
omap_dss_device operations are not used anymore. Remove them.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/venc.c | 45 ------------------------------
 1 file changed, 45 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/venc.c b/drivers/gpu/drm/omapdrm/dss/venc.c
index 66cd75e71939..53c85faaf9be 100644
--- a/drivers/gpu/drm/omapdrm/dss/venc.c
+++ b/drivers/gpu/drm/omapdrm/dss/venc.c
@@ -319,7 +319,6 @@ struct venc_device {
 	struct drm_bridge bridge;
 };
 
-#define dssdev_to_venc(dssdev) container_of(dssdev, struct venc_device, output)
 #define drm_bridge_to_venc(b) container_of(b, struct venc_device, bridge)
 
 static inline void venc_write_reg(struct venc_device *venc, int idx, u32 val)
@@ -492,30 +491,6 @@ static void venc_power_off(struct venc_device *venc)
 	venc_runtime_put(venc);
 }
 
-static int venc_get_modes(struct omap_dss_device *dssdev,
-			  struct drm_connector *connector)
-{
-	static const struct drm_display_mode *modes[] = {
-		&omap_dss_pal_mode,
-		&omap_dss_ntsc_mode,
-	};
-	unsigned int i;
-
-	for (i = 0; i < ARRAY_SIZE(modes); ++i) {
-		struct drm_display_mode *mode;
-
-		mode = drm_mode_duplicate(connector->dev, modes[i]);
-		if (!mode)
-			return i;
-
-		mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
-		drm_mode_set_name(mode);
-		drm_mode_probed_add(connector, mode);
-	}
-
-	return ARRAY_SIZE(modes);
-}
-
 static enum venc_videomode venc_get_videomode(const struct drm_display_mode *mode)
 {
 	if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
@@ -611,25 +586,6 @@ static int venc_get_clocks(struct venc_device *venc)
 	return 0;
 }
 
-static int venc_connect(struct omap_dss_device *src,
-			struct omap_dss_device *dst)
-{
-	return omapdss_device_connect(dst->dss, dst, dst->next);
-}
-
-static void venc_disconnect(struct omap_dss_device *src,
-			    struct omap_dss_device *dst)
-{
-	omapdss_device_disconnect(dst, dst->next);
-}
-
-static const struct omap_dss_device_ops venc_ops = {
-	.connect = venc_connect,
-	.disconnect = venc_disconnect,
-
-	.get_modes = venc_get_modes,
-};
-
 /* -----------------------------------------------------------------------------
  * DRM Bridge Operations
  */
@@ -837,7 +793,6 @@ static int venc_init_output(struct venc_device *venc)
 	out->type = OMAP_DISPLAY_TYPE_VENC;
 	out->name = "venc.0";
 	out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
-	out->ops = &venc_ops;
 	out->owner = THIS_MODULE;
 	out->of_port = 0;
 	out->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 48/60] drm/omap: hdmi4: Simplify EDID read
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (42 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 47/60] drm/omap: venc: " Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 49/60] drm/omap: hdmi5: " Laurent Pinchart
                     ` (12 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Now that the omap_dss_device EDID read operation has been removed,
simplify the bridge-based EDID access by merging multiple functions
together.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi4.c | 96 ++++++++++++-----------------
 1 file changed, 40 insertions(+), 56 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
index 70d98f39b8c6..b8a552b25f43 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
@@ -319,55 +319,6 @@ void hdmi4_core_disable(struct hdmi_core_data *core)
 	mutex_unlock(&hdmi->lock);
 }
 
-static struct edid *
-hdmi_do_read_edid(struct omap_hdmi *hdmi,
-		  struct edid *(*read)(struct omap_hdmi *hdmi,
-				       struct drm_connector *connector),
-		  struct drm_connector *connector)
-{
-	struct edid *edid = NULL;
-	unsigned int cec_addr;
-	bool need_enable;
-	int r;
-
-	need_enable = hdmi->core_enabled == false;
-
-	if (need_enable) {
-		r = hdmi4_core_enable(&hdmi->core);
-		if (r)
-			return NULL;
-	}
-
-	mutex_lock(&hdmi->lock);
-	r = hdmi_runtime_get(hdmi);
-	BUG_ON(r);
-
-	r = hdmi4_core_ddc_init(&hdmi->core);
-	if (r)
-		goto done;
-
-	edid = read(hdmi, connector);
-
-done:
-	hdmi_runtime_put(hdmi);
-	mutex_unlock(&hdmi->lock);
-
-	if (edid && edid->extensions) {
-		unsigned int len = (edid->extensions + 1) * EDID_LENGTH;
-
-		cec_addr = cec_get_edid_phys_addr((u8 *)edid, len, NULL);
-	} else {
-		cec_addr = CEC_PHYS_ADDR_INVALID;
-	}
-
-	hdmi4_cec_set_phys_addr(&hdmi->core, cec_addr);
-
-	if (need_enable)
-		hdmi4_core_disable(&hdmi->core);
-
-	return edid;
-}
-
 /* -----------------------------------------------------------------------------
  * DRM Bridge Operations
  */
@@ -491,18 +442,51 @@ static void hdmi4_bridge_lost_hotplug(struct drm_bridge *bridge)
 	hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
 }
 
-static struct edid *hdmi4_bridge_read_edid(struct omap_hdmi *hdmi,
-					   struct drm_connector *connector)
-{
-	return drm_do_get_edid(connector, hdmi4_core_ddc_read, &hdmi->core);
-}
-
 static struct edid *hdmi4_bridge_get_edid(struct drm_bridge *bridge,
 					  struct drm_connector *connector)
 {
 	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+	struct edid *edid = NULL;
+	unsigned int cec_addr;
+	bool need_enable;
+	int r;
 
-	return hdmi_do_read_edid(hdmi, hdmi4_bridge_read_edid, connector);
+	need_enable = hdmi->core_enabled == false;
+
+	if (need_enable) {
+		r = hdmi4_core_enable(&hdmi->core);
+		if (r)
+			return NULL;
+	}
+
+	mutex_lock(&hdmi->lock);
+	r = hdmi_runtime_get(hdmi);
+	BUG_ON(r);
+
+	r = hdmi4_core_ddc_init(&hdmi->core);
+	if (r)
+		goto done;
+
+	edid = drm_do_get_edid(connector, hdmi4_core_ddc_read, &hdmi->core);
+
+done:
+	hdmi_runtime_put(hdmi);
+	mutex_unlock(&hdmi->lock);
+
+	if (edid && edid->extensions) {
+		unsigned int len = (edid->extensions + 1) * EDID_LENGTH;
+
+		cec_addr = cec_get_edid_phys_addr((u8 *)edid, len, NULL);
+	} else {
+		cec_addr = CEC_PHYS_ADDR_INVALID;
+	}
+
+	hdmi4_cec_set_phys_addr(&hdmi->core, cec_addr);
+
+	if (need_enable)
+		hdmi4_core_disable(&hdmi->core);
+
+	return edid;
 }
 
 static const struct drm_bridge_funcs hdmi4_bridge_funcs = {
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 49/60] drm/omap: hdmi5: Simplify EDID read
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (43 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 48/60] drm/omap: hdmi4: Simplify EDID read Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 50/60] drm/omap: displays: Remove unused panel drivers Laurent Pinchart
                     ` (11 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Now that the omap_dss_device EDID read operation has been removed,
simplify the bridge-based EDID access by merging multiple functions
together.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/hdmi5.c | 86 ++++++++++++-----------------
 1 file changed, 35 insertions(+), 51 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
index 6399ee5bd6d6..effc22aeac4a 100644
--- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c
+++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c
@@ -317,50 +317,6 @@ static void hdmi_core_disable(struct omap_hdmi *hdmi)
 	mutex_unlock(&hdmi->lock);
 }
 
-static struct edid *
-hdmi_do_read_edid(struct omap_hdmi *hdmi,
-		  struct edid *(*read)(struct omap_hdmi *hdmi,
-				       struct drm_connector *connector),
-		  struct drm_connector *connector)
-{
-	struct edid *edid;
-	bool need_enable;
-	int idlemode;
-	int r;
-
-	need_enable = hdmi->core_enabled == false;
-
-	if (need_enable) {
-		r = hdmi_core_enable(hdmi);
-		if (r)
-			return NULL;
-	}
-
-	mutex_lock(&hdmi->lock);
-	r = hdmi_runtime_get(hdmi);
-	BUG_ON(r);
-
-	idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2);
-	/* No-idle mode */
-	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
-
-	hdmi5_core_ddc_init(&hdmi->core);
-
-	edid = read(hdmi, connector);
-
-	hdmi5_core_ddc_uninit(&hdmi->core);
-
-	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2);
-
-	hdmi_runtime_put(hdmi);
-	mutex_unlock(&hdmi->lock);
-
-	if (need_enable)
-		hdmi_core_disable(hdmi);
-
-	return (struct edid *)edid;
-}
-
 /* -----------------------------------------------------------------------------
  * DRM Bridge Operations
  */
@@ -477,18 +433,46 @@ static void hdmi5_bridge_disable(struct drm_bridge *bridge,
 	mutex_unlock(&hdmi->lock);
 }
 
-static struct edid *hdmi5_bridge_read_edid(struct omap_hdmi *hdmi,
-					   struct drm_connector *connector)
-{
-	return drm_do_get_edid(connector, hdmi5_core_ddc_read, &hdmi->core);
-}
-
 static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
 					  struct drm_connector *connector)
 {
 	struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
+	struct edid *edid;
+	bool need_enable;
+	int idlemode;
+	int r;
 
-	return hdmi_do_read_edid(hdmi, hdmi5_bridge_read_edid, connector);
+	need_enable = hdmi->core_enabled == false;
+
+	if (need_enable) {
+		r = hdmi_core_enable(hdmi);
+		if (r)
+			return NULL;
+	}
+
+	mutex_lock(&hdmi->lock);
+	r = hdmi_runtime_get(hdmi);
+	BUG_ON(r);
+
+	idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2);
+	/* No-idle mode */
+	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
+
+	hdmi5_core_ddc_init(&hdmi->core);
+
+	edid = drm_do_get_edid(connector, hdmi5_core_ddc_read, &hdmi->core);
+
+	hdmi5_core_ddc_uninit(&hdmi->core);
+
+	REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2);
+
+	hdmi_runtime_put(hdmi);
+	mutex_unlock(&hdmi->lock);
+
+	if (need_enable)
+		hdmi_core_disable(hdmi);
+
+	return (struct edid *)edid;
 }
 
 static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 50/60] drm/omap: displays: Remove unused panel drivers
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (44 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 49/60] drm/omap: hdmi5: " Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 51/60] drm/omap: dpi: Sort includes alphabetically Laurent Pinchart
                     ` (10 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

drm_panel-based drivers for the ACX565AKM, LB035Q02, LS037V7DW01,
NL8048HL11, TD028TTEC1 and TD043MTEA1 are available, remove the
omapdrm-specific drivers.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/displays/Kconfig      |  38 -
 drivers/gpu/drm/omapdrm/displays/Makefile     |   6 -
 .../displays/panel-lgphilips-lb035q02.c       | 254 ------
 .../omapdrm/displays/panel-nec-nl8048hl11.c   | 271 -------
 .../displays/panel-sharp-ls037v7dw01.c        | 265 ------
 .../omapdrm/displays/panel-sony-acx565akm.c   | 766 ------------------
 .../omapdrm/displays/panel-tpo-td028ttec1.c   | 401 ---------
 .../omapdrm/displays/panel-tpo-td043mtea1.c   | 513 ------------
 .../gpu/drm/omapdrm/dss/omapdss-boot-init.c   |   7 -
 9 files changed, 2521 deletions(-)
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
 delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c

diff --git a/drivers/gpu/drm/omapdrm/displays/Kconfig b/drivers/gpu/drm/omapdrm/displays/Kconfig
index bb73d49fc3e0..f2be594c7eff 100644
--- a/drivers/gpu/drm/omapdrm/displays/Kconfig
+++ b/drivers/gpu/drm/omapdrm/displays/Kconfig
@@ -7,42 +7,4 @@ config DRM_OMAP_PANEL_DSI_CM
 	help
 	  Driver for generic DSI command mode panels.
 
-config DRM_OMAP_PANEL_SONY_ACX565AKM
-	tristate "ACX565AKM Panel"
-	depends on SPI && BACKLIGHT_CLASS_DEVICE
-	help
-	  This is the LCD panel used on Nokia N900
-
-config DRM_OMAP_PANEL_LGPHILIPS_LB035Q02
-	tristate "LG.Philips LB035Q02 LCD Panel"
-	depends on SPI
-	help
-	  LCD Panel used on the Gumstix Overo Palo35
-
-config DRM_OMAP_PANEL_SHARP_LS037V7DW01
-        tristate "Sharp LS037V7DW01 LCD Panel"
-        depends on BACKLIGHT_CLASS_DEVICE
-        help
-          LCD Panel used in TI's SDP3430 and EVM boards
-
-config DRM_OMAP_PANEL_TPO_TD028TTEC1
-        tristate "TPO TD028TTEC1 LCD Panel"
-        depends on SPI
-        help
-          LCD panel used in Openmoko.
-
-config DRM_OMAP_PANEL_TPO_TD043MTEA1
-        tristate "TPO TD043MTEA1 LCD Panel"
-        depends on SPI
-        help
-          LCD Panel used in OMAP3 Pandora
-
-config DRM_OMAP_PANEL_NEC_NL8048HL11
-	tristate "NEC NL8048HL11 Panel"
-	depends on SPI
-	depends on BACKLIGHT_CLASS_DEVICE
-	help
-		This NEC NL8048HL11 panel is TFT LCD used in the
-		Zoom2/3/3630 sdp boards.
-
 endmenu
diff --git a/drivers/gpu/drm/omapdrm/displays/Makefile b/drivers/gpu/drm/omapdrm/displays/Makefile
index 58ddfa9917f8..488ddf153613 100644
--- a/drivers/gpu/drm/omapdrm/displays/Makefile
+++ b/drivers/gpu/drm/omapdrm/displays/Makefile
@@ -1,8 +1,2 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_OMAP_PANEL_DSI_CM) += panel-dsi-cm.o
-obj-$(CONFIG_DRM_OMAP_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
-obj-$(CONFIG_DRM_OMAP_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o
-obj-$(CONFIG_DRM_OMAP_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
-obj-$(CONFIG_DRM_OMAP_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o
-obj-$(CONFIG_DRM_OMAP_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o
-obj-$(CONFIG_DRM_OMAP_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c b/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
deleted file mode 100644
index 51eb98e96cde..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * LG.Philips LB035Q02 LCD Panel driver
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- * Based on a driver by: Steve Sakoman <steve@sakoman.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
-
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/spi/spi.h>
-#include <linux/mutex.h>
-#include <linux/gpio.h>
-#include <linux/gpio/consumer.h>
-
-#include "../dss/omapdss.h"
-
-static const struct videomode lb035q02_vm = {
-	.hactive = 320,
-	.vactive = 240,
-
-	.pixelclock	= 6500000,
-
-	.hsync_len	= 2,
-	.hfront_porch	= 20,
-	.hback_porch	= 68,
-
-	.vsync_len	= 2,
-	.vfront_porch	= 4,
-	.vback_porch	= 18,
-
-	.flags		= DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
-};
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-
-	struct spi_device *spi;
-
-	struct videomode vm;
-
-	struct gpio_desc *enable_gpio;
-};
-
-#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
-
-static int lb035q02_write_reg(struct spi_device *spi, u8 reg, u16 val)
-{
-	struct spi_message msg;
-	struct spi_transfer index_xfer = {
-		.len		= 3,
-		.cs_change	= 1,
-	};
-	struct spi_transfer value_xfer = {
-		.len		= 3,
-	};
-	u8	buffer[16];
-
-	spi_message_init(&msg);
-
-	/* register index */
-	buffer[0] = 0x70;
-	buffer[1] = 0x00;
-	buffer[2] = reg & 0x7f;
-	index_xfer.tx_buf = buffer;
-	spi_message_add_tail(&index_xfer, &msg);
-
-	/* register value */
-	buffer[4] = 0x72;
-	buffer[5] = val >> 8;
-	buffer[6] = val;
-	value_xfer.tx_buf = buffer + 4;
-	spi_message_add_tail(&value_xfer, &msg);
-
-	return spi_sync(spi, &msg);
-}
-
-static void init_lb035q02_panel(struct spi_device *spi)
-{
-	/* Init sequence from page 28 of the lb035q02 spec */
-	lb035q02_write_reg(spi, 0x01, 0x6300);
-	lb035q02_write_reg(spi, 0x02, 0x0200);
-	lb035q02_write_reg(spi, 0x03, 0x0177);
-	lb035q02_write_reg(spi, 0x04, 0x04c7);
-	lb035q02_write_reg(spi, 0x05, 0xffc0);
-	lb035q02_write_reg(spi, 0x06, 0xe806);
-	lb035q02_write_reg(spi, 0x0a, 0x4008);
-	lb035q02_write_reg(spi, 0x0b, 0x0000);
-	lb035q02_write_reg(spi, 0x0d, 0x0030);
-	lb035q02_write_reg(spi, 0x0e, 0x2800);
-	lb035q02_write_reg(spi, 0x0f, 0x0000);
-	lb035q02_write_reg(spi, 0x16, 0x9f80);
-	lb035q02_write_reg(spi, 0x17, 0x0a0f);
-	lb035q02_write_reg(spi, 0x1e, 0x00c1);
-	lb035q02_write_reg(spi, 0x30, 0x0300);
-	lb035q02_write_reg(spi, 0x31, 0x0007);
-	lb035q02_write_reg(spi, 0x32, 0x0000);
-	lb035q02_write_reg(spi, 0x33, 0x0000);
-	lb035q02_write_reg(spi, 0x34, 0x0707);
-	lb035q02_write_reg(spi, 0x35, 0x0004);
-	lb035q02_write_reg(spi, 0x36, 0x0302);
-	lb035q02_write_reg(spi, 0x37, 0x0202);
-	lb035q02_write_reg(spi, 0x3a, 0x0a0d);
-	lb035q02_write_reg(spi, 0x3b, 0x0806);
-}
-
-static int lb035q02_connect(struct omap_dss_device *src,
-			    struct omap_dss_device *dst)
-{
-	struct panel_drv_data *ddata = to_panel_data(dst);
-
-	init_lb035q02_panel(ddata->spi);
-
-	return 0;
-}
-
-static void lb035q02_disconnect(struct omap_dss_device *src,
-				struct omap_dss_device *dst)
-{
-}
-
-static void lb035q02_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (ddata->enable_gpio)
-		gpiod_set_value_cansleep(ddata->enable_gpio, 1);
-}
-
-static void lb035q02_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (ddata->enable_gpio)
-		gpiod_set_value_cansleep(ddata->enable_gpio, 0);
-}
-
-static int lb035q02_get_modes(struct omap_dss_device *dssdev,
-			      struct drm_connector *connector)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return omapdss_display_get_modes(connector, &ddata->vm);
-}
-
-static const struct omap_dss_device_ops lb035q02_ops = {
-	.connect	= lb035q02_connect,
-	.disconnect	= lb035q02_disconnect,
-
-	.enable		= lb035q02_enable,
-	.disable	= lb035q02_disable,
-
-	.get_modes	= lb035q02_get_modes,
-};
-
-static int lb035q02_probe_of(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
-	struct gpio_desc *gpio;
-
-	gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW);
-	if (IS_ERR(gpio)) {
-		dev_err(&spi->dev, "failed to parse enable gpio\n");
-		return PTR_ERR(gpio);
-	}
-
-	ddata->enable_gpio = gpio;
-
-	return 0;
-}
-
-static int lb035q02_panel_spi_probe(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	int r;
-
-	ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
-	if (ddata == NULL)
-		return -ENOMEM;
-
-	dev_set_drvdata(&spi->dev, ddata);
-
-	ddata->spi = spi;
-
-	r = lb035q02_probe_of(spi);
-	if (r)
-		return r;
-
-	ddata->vm = lb035q02_vm;
-
-	dssdev = &ddata->dssdev;
-	dssdev->dev = &spi->dev;
-	dssdev->ops = &lb035q02_ops;
-	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-
-	/*
-	 * Note: According to the panel documentation:
-	 * DE is active LOW
-	 * DATA needs to be driven on the FALLING edge
-	 */
-	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
-			  | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE
-			  | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int lb035q02_panel_spi_remove(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	omapdss_device_unregister(dssdev);
-
-	lb035q02_disable(dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id lb035q02_of_match[] = {
-	{ .compatible = "omapdss,lgphilips,lb035q02", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, lb035q02_of_match);
-
-static struct spi_driver lb035q02_spi_driver = {
-	.probe		= lb035q02_panel_spi_probe,
-	.remove		= lb035q02_panel_spi_remove,
-	.driver		= {
-		.name	= "panel_lgphilips_lb035q02",
-		.of_match_table = lb035q02_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_spi_driver(lb035q02_spi_driver);
-
-MODULE_ALIAS("spi:lgphilips,lb035q02");
-MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
-MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c b/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
deleted file mode 100644
index ebde3f6f670e..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
+++ /dev/null
@@ -1,271 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * NEC NL8048HL11 Panel driver
- *
- * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Erik Gilling <konkers@android.com>
- * Converted to new DSS device model: Tomi Valkeinen <tomi.valkeinen@ti.com>
- */
-
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/spi/spi.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device	dssdev;
-
-	struct videomode vm;
-
-	struct gpio_desc *res_gpio;
-
-	struct spi_device *spi;
-};
-
-#define LCD_XRES		800
-#define LCD_YRES		480
-/*
- * NEC PIX Clock Ratings
- * MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz
- */
-#define LCD_PIXEL_CLOCK		23800000
-
-static const struct {
-	unsigned char addr;
-	unsigned char dat;
-} nec_8048_init_seq[] = {
-	{ 3, 0x01 }, { 0, 0x00 }, { 1, 0x01 }, { 4, 0x00 }, { 5, 0x14 },
-	{ 6, 0x24 }, { 16, 0xD7 }, { 17, 0x00 }, { 18, 0x00 }, { 19, 0x55 },
-	{ 20, 0x01 }, { 21, 0x70 }, { 22, 0x1E }, { 23, 0x25 },	{ 24, 0x25 },
-	{ 25, 0x02 }, { 26, 0x02 }, { 27, 0xA0 }, { 32, 0x2F }, { 33, 0x0F },
-	{ 34, 0x0F }, { 35, 0x0F }, { 36, 0x0F }, { 37, 0x0F },	{ 38, 0x0F },
-	{ 39, 0x00 }, { 40, 0x02 }, { 41, 0x02 }, { 42, 0x02 },	{ 43, 0x0F },
-	{ 44, 0x0F }, { 45, 0x0F }, { 46, 0x0F }, { 47, 0x0F },	{ 48, 0x0F },
-	{ 49, 0x0F }, { 50, 0x00 }, { 51, 0x02 }, { 52, 0x02 }, { 53, 0x02 },
-	{ 80, 0x0C }, { 83, 0x42 }, { 84, 0x42 }, { 85, 0x41 },	{ 86, 0x14 },
-	{ 89, 0x88 }, { 90, 0x01 }, { 91, 0x00 }, { 92, 0x02 },	{ 93, 0x0C },
-	{ 94, 0x1C }, { 95, 0x27 }, { 98, 0x49 }, { 99, 0x27 }, { 102, 0x76 },
-	{ 103, 0x27 }, { 112, 0x01 }, { 113, 0x0E }, { 114, 0x02 },
-	{ 115, 0x0C }, { 118, 0x0C }, { 121, 0x30 }, { 130, 0x00 },
-	{ 131, 0x00 }, { 132, 0xFC }, { 134, 0x00 }, { 136, 0x00 },
-	{ 138, 0x00 }, { 139, 0x00 }, { 140, 0x00 }, { 141, 0xFC },
-	{ 143, 0x00 }, { 145, 0x00 }, { 147, 0x00 }, { 148, 0x00 },
-	{ 149, 0x00 }, { 150, 0xFC }, { 152, 0x00 }, { 154, 0x00 },
-	{ 156, 0x00 }, { 157, 0x00 }, { 2, 0x00 },
-};
-
-static const struct videomode nec_8048_panel_vm = {
-	.hactive	= LCD_XRES,
-	.vactive	= LCD_YRES,
-	.pixelclock	= LCD_PIXEL_CLOCK,
-	.hfront_porch	= 6,
-	.hsync_len	= 1,
-	.hback_porch	= 4,
-	.vfront_porch	= 3,
-	.vsync_len	= 1,
-	.vback_porch	= 4,
-
-	.flags		= DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
-};
-
-#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
-
-static int nec_8048_spi_send(struct spi_device *spi, unsigned char reg_addr,
-			unsigned char reg_data)
-{
-	int ret = 0;
-	unsigned int cmd = 0, data = 0;
-
-	cmd = 0x0000 | reg_addr; /* register address write */
-	data = 0x0100 | reg_data; /* register data write */
-	data = (cmd << 16) | data;
-
-	ret = spi_write(spi, (unsigned char *)&data, 4);
-	if (ret)
-		pr_err("error in spi_write %x\n", data);
-
-	return ret;
-}
-
-static int init_nec_8048_wvga_lcd(struct spi_device *spi)
-{
-	unsigned int i;
-	/* Initialization Sequence */
-	/* nec_8048_spi_send(spi, REG, VAL) */
-	for (i = 0; i < (ARRAY_SIZE(nec_8048_init_seq) - 1); i++)
-		nec_8048_spi_send(spi, nec_8048_init_seq[i].addr,
-				nec_8048_init_seq[i].dat);
-	udelay(20);
-	nec_8048_spi_send(spi, nec_8048_init_seq[i].addr,
-				nec_8048_init_seq[i].dat);
-	return 0;
-}
-
-static int nec_8048_connect(struct omap_dss_device *src,
-			    struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void nec_8048_disconnect(struct omap_dss_device *src,
-				struct omap_dss_device *dst)
-{
-}
-
-static void nec_8048_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	gpiod_set_value_cansleep(ddata->res_gpio, 1);
-}
-
-static void nec_8048_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	gpiod_set_value_cansleep(ddata->res_gpio, 0);
-}
-
-static int nec_8048_get_modes(struct omap_dss_device *dssdev,
-			      struct drm_connector *connector)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return omapdss_display_get_modes(connector, &ddata->vm);
-}
-
-static const struct omap_dss_device_ops nec_8048_ops = {
-	.connect	= nec_8048_connect,
-	.disconnect	= nec_8048_disconnect,
-
-	.enable		= nec_8048_enable,
-	.disable	= nec_8048_disable,
-
-	.get_modes	= nec_8048_get_modes,
-};
-
-static int nec_8048_probe(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	struct gpio_desc *gpio;
-	int r;
-
-	dev_dbg(&spi->dev, "%s\n", __func__);
-
-	spi->mode = SPI_MODE_0;
-	spi->bits_per_word = 32;
-
-	r = spi_setup(spi);
-	if (r < 0) {
-		dev_err(&spi->dev, "spi_setup failed: %d\n", r);
-		return r;
-	}
-
-	init_nec_8048_wvga_lcd(spi);
-
-	ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
-	if (ddata == NULL)
-		return -ENOMEM;
-
-	dev_set_drvdata(&spi->dev, ddata);
-
-	ddata->spi = spi;
-
-	gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
-	if (IS_ERR(gpio)) {
-		dev_err(&spi->dev, "failed to get reset gpio\n");
-		return PTR_ERR(gpio);
-	}
-
-	ddata->res_gpio = gpio;
-
-	ddata->vm = nec_8048_panel_vm;
-
-	dssdev = &ddata->dssdev;
-	dssdev->dev = &spi->dev;
-	dssdev->ops = &nec_8048_ops;
-	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
-			  | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE
-			  | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int nec_8048_remove(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	dev_dbg(&ddata->spi->dev, "%s\n", __func__);
-
-	omapdss_device_unregister(dssdev);
-
-	nec_8048_disable(dssdev);
-
-	return 0;
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int nec_8048_suspend(struct device *dev)
-{
-	struct spi_device *spi = to_spi_device(dev);
-
-	nec_8048_spi_send(spi, 2, 0x01);
-	mdelay(40);
-
-	return 0;
-}
-
-static int nec_8048_resume(struct device *dev)
-{
-	struct spi_device *spi = to_spi_device(dev);
-
-	/* reinitialize the panel */
-	spi_setup(spi);
-	nec_8048_spi_send(spi, 2, 0x00);
-	init_nec_8048_wvga_lcd(spi);
-
-	return 0;
-}
-static SIMPLE_DEV_PM_OPS(nec_8048_pm_ops, nec_8048_suspend,
-		nec_8048_resume);
-#define NEC_8048_PM_OPS (&nec_8048_pm_ops)
-#else
-#define NEC_8048_PM_OPS NULL
-#endif
-
-static const struct of_device_id nec_8048_of_match[] = {
-	{ .compatible = "omapdss,nec,nl8048hl11", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, nec_8048_of_match);
-
-static struct spi_driver nec_8048_driver = {
-	.driver = {
-		.name	= "panel-nec-nl8048hl11",
-		.pm	= NEC_8048_PM_OPS,
-		.of_match_table = nec_8048_of_match,
-		.suppress_bind_attrs = true,
-	},
-	.probe	= nec_8048_probe,
-	.remove	= nec_8048_remove,
-};
-
-module_spi_driver(nec_8048_driver);
-
-MODULE_ALIAS("spi:nec,nl8048hl11");
-MODULE_AUTHOR("Erik Gilling <konkers@android.com>");
-MODULE_DESCRIPTION("NEC-NL8048HL11 Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c b/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
deleted file mode 100644
index 637eb64371d9..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * LCD panel driver for Sharp LS037V7DW01
- *
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
-
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-#include <linux/regulator/consumer.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-	struct regulator *vcc;
-
-	struct videomode vm;
-
-	struct gpio_desc *resb_gpio;	/* low = reset active min 20 us */
-	struct gpio_desc *ini_gpio;	/* high = power on */
-	struct gpio_desc *mo_gpio;	/* low = 480x640, high = 240x320 */
-	struct gpio_desc *lr_gpio;	/* high = conventional horizontal scanning */
-	struct gpio_desc *ud_gpio;	/* high = conventional vertical scanning */
-};
-
-static const struct videomode sharp_ls_vm = {
-	.hactive = 480,
-	.vactive = 640,
-
-	.pixelclock	= 19200000,
-
-	.hsync_len	= 2,
-	.hfront_porch	= 1,
-	.hback_porch	= 28,
-
-	.vsync_len	= 1,
-	.vfront_porch	= 1,
-	.vback_porch	= 1,
-
-	.flags		= DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
-};
-
-#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
-
-static int sharp_ls_connect(struct omap_dss_device *src,
-			    struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void sharp_ls_disconnect(struct omap_dss_device *src,
-				struct omap_dss_device *dst)
-{
-}
-
-static void sharp_ls_pre_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-	int r;
-
-	if (ddata->vcc) {
-		r = regulator_enable(ddata->vcc);
-		if (r)
-			dev_err(dssdev->dev, "%s: failed to enable regulator\n",
-				__func__);
-	}
-}
-
-static void sharp_ls_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	/* wait couple of vsyncs until enabling the LCD */
-	msleep(50);
-
-	if (ddata->resb_gpio)
-		gpiod_set_value_cansleep(ddata->resb_gpio, 1);
-
-	if (ddata->ini_gpio)
-		gpiod_set_value_cansleep(ddata->ini_gpio, 1);
-}
-
-static void sharp_ls_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (ddata->ini_gpio)
-		gpiod_set_value_cansleep(ddata->ini_gpio, 0);
-
-	if (ddata->resb_gpio)
-		gpiod_set_value_cansleep(ddata->resb_gpio, 0);
-
-	/* wait at least 5 vsyncs after disabling the LCD */
-	msleep(100);
-}
-
-static void sharp_ls_post_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (ddata->vcc)
-		regulator_disable(ddata->vcc);
-}
-
-static int sharp_ls_get_modes(struct omap_dss_device *dssdev,
-			      struct drm_connector *connector)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return omapdss_display_get_modes(connector, &ddata->vm);
-}
-
-static const struct omap_dss_device_ops sharp_ls_ops = {
-	.connect	= sharp_ls_connect,
-	.disconnect	= sharp_ls_disconnect,
-
-	.pre_enable	= sharp_ls_pre_enable,
-	.enable		= sharp_ls_enable,
-	.disable	= sharp_ls_disable,
-	.post_disable	= sharp_ls_post_disable,
-
-	.get_modes	= sharp_ls_get_modes,
-};
-
-static  int sharp_ls_get_gpio_of(struct device *dev, int index, int val,
-	const char *desc, struct gpio_desc **gpiod)
-{
-	struct gpio_desc *gd;
-
-	*gpiod = NULL;
-
-	gd = devm_gpiod_get_index(dev, desc, index, GPIOD_OUT_LOW);
-	if (IS_ERR(gd))
-		return PTR_ERR(gd);
-
-	*gpiod = gd;
-	return 0;
-}
-
-static int sharp_ls_probe_of(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
-	int r;
-
-	ddata->vcc = devm_regulator_get(&pdev->dev, "envdd");
-	if (IS_ERR(ddata->vcc)) {
-		dev_err(&pdev->dev, "failed to get regulator\n");
-		return PTR_ERR(ddata->vcc);
-	}
-
-	/* lcd INI */
-	r = sharp_ls_get_gpio_of(&pdev->dev, 0, 0, "enable", &ddata->ini_gpio);
-	if (r)
-		return r;
-
-	/* lcd RESB */
-	r = sharp_ls_get_gpio_of(&pdev->dev, 0, 0, "reset", &ddata->resb_gpio);
-	if (r)
-		return r;
-
-	/* lcd MO */
-	r = sharp_ls_get_gpio_of(&pdev->dev, 0, 0, "mode", &ddata->mo_gpio);
-	if (r)
-		return r;
-
-	/* lcd LR */
-	r = sharp_ls_get_gpio_of(&pdev->dev, 1, 1, "mode", &ddata->lr_gpio);
-	if (r)
-		return r;
-
-	/* lcd UD */
-	r = sharp_ls_get_gpio_of(&pdev->dev, 2, 1, "mode", &ddata->ud_gpio);
-	if (r)
-		return r;
-
-	return 0;
-}
-
-static int sharp_ls_probe(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	int r;
-
-	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
-	if (ddata == NULL)
-		return -ENOMEM;
-
-	platform_set_drvdata(pdev, ddata);
-
-	r = sharp_ls_probe_of(pdev);
-	if (r)
-		return r;
-
-	ddata->vm = sharp_ls_vm;
-
-	dssdev = &ddata->dssdev;
-	dssdev->dev = &pdev->dev;
-	dssdev->ops = &sharp_ls_ops;
-	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-
-	/*
-	 * Note: According to the panel documentation:
-	 * DATA needs to be driven on the FALLING edge
-	 */
-	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
-			  | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE
-			  | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int __exit sharp_ls_remove(struct platform_device *pdev)
-{
-	struct panel_drv_data *ddata = platform_get_drvdata(pdev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	omapdss_device_unregister(dssdev);
-
-	if (omapdss_device_is_enabled(dssdev)) {
-		sharp_ls_disable(dssdev);
-		sharp_ls_post_disable(dssdev);
-	}
-
-	return 0;
-}
-
-static const struct of_device_id sharp_ls_of_match[] = {
-	{ .compatible = "omapdss,sharp,ls037v7dw01", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, sharp_ls_of_match);
-
-static struct platform_driver sharp_ls_driver = {
-	.probe = sharp_ls_probe,
-	.remove = __exit_p(sharp_ls_remove),
-	.driver = {
-		.name = "panel-sharp-ls037v7dw01",
-		.of_match_table = sharp_ls_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_platform_driver(sharp_ls_driver);
-
-MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
-MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c b/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
deleted file mode 100644
index 67379f71bd88..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
+++ /dev/null
@@ -1,766 +0,0 @@
-/*
- * Sony ACX565AKM LCD Panel driver
- *
- * Copyright (C) 2010 Nokia Corporation
- *
- * Original Driver Author: Imre Deak <imre.deak@nokia.com>
- * Based on panel-generic.c by Tomi Valkeinen <tomi.valkeinen@ti.com>
- * Adapted to new DSS2 framework: Roger Quadros <roger.quadros@nokia.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <linux/backlight.h>
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/jiffies.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/sched.h>
-#include <linux/spi/spi.h>
-
-#include "../dss/omapdss.h"
-
-#define MIPID_CMD_READ_DISP_ID		0x04
-#define MIPID_CMD_READ_RED		0x06
-#define MIPID_CMD_READ_GREEN		0x07
-#define MIPID_CMD_READ_BLUE		0x08
-#define MIPID_CMD_READ_DISP_STATUS	0x09
-#define MIPID_CMD_RDDSDR		0x0F
-#define MIPID_CMD_SLEEP_IN		0x10
-#define MIPID_CMD_SLEEP_OUT		0x11
-#define MIPID_CMD_DISP_OFF		0x28
-#define MIPID_CMD_DISP_ON		0x29
-#define MIPID_CMD_WRITE_DISP_BRIGHTNESS	0x51
-#define MIPID_CMD_READ_DISP_BRIGHTNESS	0x52
-#define MIPID_CMD_WRITE_CTRL_DISP	0x53
-
-#define CTRL_DISP_BRIGHTNESS_CTRL_ON	(1 << 5)
-#define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON	(1 << 4)
-#define CTRL_DISP_BACKLIGHT_ON		(1 << 2)
-#define CTRL_DISP_AUTO_BRIGHTNESS_ON	(1 << 1)
-
-#define MIPID_CMD_READ_CTRL_DISP	0x54
-#define MIPID_CMD_WRITE_CABC		0x55
-#define MIPID_CMD_READ_CABC		0x56
-
-#define MIPID_VER_LPH8923		3
-#define MIPID_VER_LS041Y3		4
-#define MIPID_VER_L4F00311		8
-#define MIPID_VER_ACX565AKM		9
-
-struct panel_drv_data {
-	struct omap_dss_device	dssdev;
-
-	struct gpio_desc *reset_gpio;
-
-	struct videomode vm;
-
-	char		*name;
-	int		enabled;
-	int		model;
-	int		revision;
-	u8		display_id[3];
-	unsigned	has_bc:1;
-	unsigned	has_cabc:1;
-	unsigned	cabc_mode;
-	unsigned long	hw_guard_end;		/* next value of jiffies
-						   when we can issue the
-						   next sleep in/out command */
-	unsigned long	hw_guard_wait;		/* max guard time in jiffies */
-
-	struct spi_device	*spi;
-	struct mutex		mutex;
-
-	struct backlight_device *bl_dev;
-};
-
-static const struct videomode acx565akm_panel_vm = {
-	.hactive	= 800,
-	.vactive	= 480,
-	.pixelclock	= 24000000,
-	.hfront_porch	= 28,
-	.hsync_len	= 4,
-	.hback_porch	= 24,
-	.vfront_porch	= 3,
-	.vsync_len	= 3,
-	.vback_porch	= 4,
-
-	.flags		= DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
-};
-
-#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
-
-static void acx565akm_transfer(struct panel_drv_data *ddata, int cmd,
-			      const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
-{
-	struct spi_message	m;
-	struct spi_transfer	*x, xfer[5];
-	int			r;
-
-	BUG_ON(ddata->spi == NULL);
-
-	spi_message_init(&m);
-
-	memset(xfer, 0, sizeof(xfer));
-	x = &xfer[0];
-
-	cmd &=  0xff;
-	x->tx_buf = &cmd;
-	x->bits_per_word = 9;
-	x->len = 2;
-
-	if (rlen > 1 && wlen == 0) {
-		/*
-		 * Between the command and the response data there is a
-		 * dummy clock cycle. Add an extra bit after the command
-		 * word to account for this.
-		 */
-		x->bits_per_word = 10;
-		cmd <<= 1;
-	}
-	spi_message_add_tail(x, &m);
-
-	if (wlen) {
-		x++;
-		x->tx_buf = wbuf;
-		x->len = wlen;
-		x->bits_per_word = 9;
-		spi_message_add_tail(x, &m);
-	}
-
-	if (rlen) {
-		x++;
-		x->rx_buf	= rbuf;
-		x->len		= rlen;
-		spi_message_add_tail(x, &m);
-	}
-
-	r = spi_sync(ddata->spi, &m);
-	if (r < 0)
-		dev_dbg(&ddata->spi->dev, "spi_sync %d\n", r);
-}
-
-static inline void acx565akm_cmd(struct panel_drv_data *ddata, int cmd)
-{
-	acx565akm_transfer(ddata, cmd, NULL, 0, NULL, 0);
-}
-
-static inline void acx565akm_write(struct panel_drv_data *ddata,
-			       int reg, const u8 *buf, int len)
-{
-	acx565akm_transfer(ddata, reg, buf, len, NULL, 0);
-}
-
-static inline void acx565akm_read(struct panel_drv_data *ddata,
-			      int reg, u8 *buf, int len)
-{
-	acx565akm_transfer(ddata, reg, NULL, 0, buf, len);
-}
-
-static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec)
-{
-	ddata->hw_guard_wait = msecs_to_jiffies(guard_msec);
-	ddata->hw_guard_end = jiffies + ddata->hw_guard_wait;
-}
-
-static void hw_guard_wait(struct panel_drv_data *ddata)
-{
-	unsigned long wait = ddata->hw_guard_end - jiffies;
-
-	if ((long)wait > 0 && wait <= ddata->hw_guard_wait) {
-		set_current_state(TASK_UNINTERRUPTIBLE);
-		schedule_timeout(wait);
-	}
-}
-
-static void set_sleep_mode(struct panel_drv_data *ddata, int on)
-{
-	int cmd;
-
-	if (on)
-		cmd = MIPID_CMD_SLEEP_IN;
-	else
-		cmd = MIPID_CMD_SLEEP_OUT;
-	/*
-	 * We have to keep 120msec between sleep in/out commands.
-	 * (8.2.15, 8.2.16).
-	 */
-	hw_guard_wait(ddata);
-	acx565akm_cmd(ddata, cmd);
-	hw_guard_start(ddata, 120);
-}
-
-static void set_display_state(struct panel_drv_data *ddata, int enabled)
-{
-	int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
-
-	acx565akm_cmd(ddata, cmd);
-}
-
-static int panel_enabled(struct panel_drv_data *ddata)
-{
-	__be32 v;
-	u32 disp_status;
-	int enabled;
-
-	acx565akm_read(ddata, MIPID_CMD_READ_DISP_STATUS, (u8 *)&v, 4);
-	disp_status = __be32_to_cpu(v);
-	enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10));
-	dev_dbg(&ddata->spi->dev,
-		"LCD panel %senabled by bootloader (status 0x%04x)\n",
-		enabled ? "" : "not ", disp_status);
-	return enabled;
-}
-
-static int panel_detect(struct panel_drv_data *ddata)
-{
-	acx565akm_read(ddata, MIPID_CMD_READ_DISP_ID, ddata->display_id, 3);
-	dev_dbg(&ddata->spi->dev, "MIPI display ID: %02x%02x%02x\n",
-		ddata->display_id[0],
-		ddata->display_id[1],
-		ddata->display_id[2]);
-
-	switch (ddata->display_id[0]) {
-	case 0x10:
-		ddata->model = MIPID_VER_ACX565AKM;
-		ddata->name = "acx565akm";
-		ddata->has_bc = 1;
-		ddata->has_cabc = 1;
-		break;
-	case 0x29:
-		ddata->model = MIPID_VER_L4F00311;
-		ddata->name = "l4f00311";
-		break;
-	case 0x45:
-		ddata->model = MIPID_VER_LPH8923;
-		ddata->name = "lph8923";
-		break;
-	case 0x83:
-		ddata->model = MIPID_VER_LS041Y3;
-		ddata->name = "ls041y3";
-		break;
-	default:
-		ddata->name = "unknown";
-		dev_err(&ddata->spi->dev, "invalid display ID\n");
-		return -ENODEV;
-	}
-
-	ddata->revision = ddata->display_id[1];
-
-	dev_info(&ddata->spi->dev, "omapfb: %s rev %02x LCD detected\n",
-			ddata->name, ddata->revision);
-
-	return 0;
-}
-
-/*----------------------Backlight Control-------------------------*/
-
-static void enable_backlight_ctrl(struct panel_drv_data *ddata, int enable)
-{
-	u16 ctrl;
-
-	acx565akm_read(ddata, MIPID_CMD_READ_CTRL_DISP, (u8 *)&ctrl, 1);
-	if (enable) {
-		ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON |
-			CTRL_DISP_BACKLIGHT_ON;
-	} else {
-		ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON |
-			  CTRL_DISP_BACKLIGHT_ON);
-	}
-
-	ctrl |= 1 << 8;
-	acx565akm_write(ddata, MIPID_CMD_WRITE_CTRL_DISP, (u8 *)&ctrl, 2);
-}
-
-static void set_cabc_mode(struct panel_drv_data *ddata, unsigned int mode)
-{
-	u16 cabc_ctrl;
-
-	ddata->cabc_mode = mode;
-	if (!ddata->enabled)
-		return;
-	cabc_ctrl = 0;
-	acx565akm_read(ddata, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1);
-	cabc_ctrl &= ~3;
-	cabc_ctrl |= (1 << 8) | (mode & 3);
-	acx565akm_write(ddata, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2);
-}
-
-static unsigned int get_cabc_mode(struct panel_drv_data *ddata)
-{
-	return ddata->cabc_mode;
-}
-
-static unsigned int get_hw_cabc_mode(struct panel_drv_data *ddata)
-{
-	u8 cabc_ctrl;
-
-	acx565akm_read(ddata, MIPID_CMD_READ_CABC, &cabc_ctrl, 1);
-	return cabc_ctrl & 3;
-}
-
-static void acx565akm_set_brightness(struct panel_drv_data *ddata, int level)
-{
-	int bv;
-
-	bv = level | (1 << 8);
-	acx565akm_write(ddata, MIPID_CMD_WRITE_DISP_BRIGHTNESS, (u8 *)&bv, 2);
-
-	if (level)
-		enable_backlight_ctrl(ddata, 1);
-	else
-		enable_backlight_ctrl(ddata, 0);
-}
-
-static int acx565akm_get_actual_brightness(struct panel_drv_data *ddata)
-{
-	u8 bv;
-
-	acx565akm_read(ddata, MIPID_CMD_READ_DISP_BRIGHTNESS, &bv, 1);
-
-	return bv;
-}
-
-
-static int acx565akm_bl_update_status(struct backlight_device *dev)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
-	int level;
-
-	dev_dbg(&ddata->spi->dev, "%s\n", __func__);
-
-	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
-			dev->props.power == FB_BLANK_UNBLANK)
-		level = dev->props.brightness;
-	else
-		level = 0;
-
-	if (ddata->has_bc)
-		acx565akm_set_brightness(ddata, level);
-	else
-		return -ENODEV;
-
-	return 0;
-}
-
-static int acx565akm_bl_get_intensity(struct backlight_device *dev)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
-
-	dev_dbg(&dev->dev, "%s\n", __func__);
-
-	if (!ddata->has_bc)
-		return -ENODEV;
-
-	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
-			dev->props.power == FB_BLANK_UNBLANK) {
-		if (ddata->has_bc)
-			return acx565akm_get_actual_brightness(ddata);
-		else
-			return dev->props.brightness;
-	}
-
-	return 0;
-}
-
-static int acx565akm_bl_update_status_locked(struct backlight_device *dev)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
-	int r;
-
-	mutex_lock(&ddata->mutex);
-	r = acx565akm_bl_update_status(dev);
-	mutex_unlock(&ddata->mutex);
-
-	return r;
-}
-
-static int acx565akm_bl_get_intensity_locked(struct backlight_device *dev)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
-	int r;
-
-	mutex_lock(&ddata->mutex);
-	r = acx565akm_bl_get_intensity(dev);
-	mutex_unlock(&ddata->mutex);
-
-	return r;
-}
-
-static const struct backlight_ops acx565akm_bl_ops = {
-	.get_brightness = acx565akm_bl_get_intensity_locked,
-	.update_status  = acx565akm_bl_update_status_locked,
-};
-
-/*--------------------Auto Brightness control via Sysfs---------------------*/
-
-static const char * const cabc_modes[] = {
-	"off",		/* always used when CABC is not supported */
-	"ui",
-	"still-image",
-	"moving-image",
-};
-
-static ssize_t show_cabc_mode(struct device *dev,
-		struct device_attribute *attr,
-		char *buf)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	const char *mode_str;
-	int mode;
-	int len;
-
-	if (!ddata->has_cabc)
-		mode = 0;
-	else
-		mode = get_cabc_mode(ddata);
-	mode_str = "unknown";
-	if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes))
-		mode_str = cabc_modes[mode];
-	len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str);
-
-	return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1;
-}
-
-static ssize_t store_cabc_mode(struct device *dev,
-		struct device_attribute *attr,
-		const char *buf, size_t count)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) {
-		const char *mode_str = cabc_modes[i];
-		int cmp_len = strlen(mode_str);
-
-		if (count > 0 && buf[count - 1] == '\n')
-			count--;
-		if (count != cmp_len)
-			continue;
-
-		if (strncmp(buf, mode_str, cmp_len) == 0)
-			break;
-	}
-
-	if (i == ARRAY_SIZE(cabc_modes))
-		return -EINVAL;
-
-	if (!ddata->has_cabc && i != 0)
-		return -EINVAL;
-
-	mutex_lock(&ddata->mutex);
-	set_cabc_mode(ddata, i);
-	mutex_unlock(&ddata->mutex);
-
-	return count;
-}
-
-static ssize_t show_cabc_available_modes(struct device *dev,
-		struct device_attribute *attr,
-		char *buf)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	int len;
-	int i;
-
-	if (!ddata->has_cabc)
-		return snprintf(buf, PAGE_SIZE, "%s\n", cabc_modes[0]);
-
-	for (i = 0, len = 0;
-	     len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++)
-		len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s",
-			i ? " " : "", cabc_modes[i],
-			i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : "");
-
-	return len < PAGE_SIZE ? len : PAGE_SIZE - 1;
-}
-
-static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR,
-		show_cabc_mode, store_cabc_mode);
-static DEVICE_ATTR(cabc_available_modes, S_IRUGO,
-		show_cabc_available_modes, NULL);
-
-static struct attribute *bldev_attrs[] = {
-	&dev_attr_cabc_mode.attr,
-	&dev_attr_cabc_available_modes.attr,
-	NULL,
-};
-
-static const struct attribute_group bldev_attr_group = {
-	.attrs = bldev_attrs,
-};
-
-static int acx565akm_connect(struct omap_dss_device *src,
-			     struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void acx565akm_disconnect(struct omap_dss_device *src,
-				 struct omap_dss_device *dst)
-{
-}
-
-static int acx565akm_panel_power_on(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	dev_dbg(&ddata->spi->dev, "%s\n", __func__);
-
-	/*FIXME tweak me */
-	msleep(50);
-
-	if (ddata->reset_gpio)
-		gpiod_set_value(ddata->reset_gpio, 1);
-
-	if (ddata->enabled) {
-		dev_dbg(&ddata->spi->dev, "panel already enabled\n");
-		return 0;
-	}
-
-	/*
-	 * We have to meet all the following delay requirements:
-	 * 1. tRW: reset pulse width 10usec (7.12.1)
-	 * 2. tRT: reset cancel time 5msec (7.12.1)
-	 * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst
-	 *    case (7.6.2)
-	 * 4. 120msec before the sleep out command (7.12.1)
-	 */
-	msleep(120);
-
-	set_sleep_mode(ddata, 0);
-	ddata->enabled = 1;
-
-	/* 5msec between sleep out and the next command. (8.2.16) */
-	usleep_range(5000, 10000);
-	set_display_state(ddata, 1);
-	set_cabc_mode(ddata, ddata->cabc_mode);
-
-	return acx565akm_bl_update_status(ddata->bl_dev);
-}
-
-static void acx565akm_panel_power_off(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	dev_dbg(dssdev->dev, "%s\n", __func__);
-
-	if (!ddata->enabled)
-		return;
-
-	set_display_state(ddata, 0);
-	set_sleep_mode(ddata, 1);
-	ddata->enabled = 0;
-	/*
-	 * We have to provide PCLK,HS,VS signals for 2 frames (worst case
-	 * ~50msec) after sending the sleep in command and asserting the
-	 * reset signal. We probably could assert the reset w/o the delay
-	 * but we still delay to avoid possible artifacts. (7.6.1)
-	 */
-	msleep(50);
-
-	if (ddata->reset_gpio)
-		gpiod_set_value(ddata->reset_gpio, 0);
-
-	/* FIXME need to tweak this delay */
-	msleep(100);
-}
-
-static void acx565akm_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	mutex_lock(&ddata->mutex);
-	acx565akm_panel_power_on(dssdev);
-	mutex_unlock(&ddata->mutex);
-}
-
-static void acx565akm_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	mutex_lock(&ddata->mutex);
-	acx565akm_panel_power_off(dssdev);
-	mutex_unlock(&ddata->mutex);
-}
-
-static int acx565akm_get_modes(struct omap_dss_device *dssdev,
-			       struct drm_connector *connector)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return omapdss_display_get_modes(connector, &ddata->vm);
-}
-
-static const struct omap_dss_device_ops acx565akm_ops = {
-	.connect	= acx565akm_connect,
-	.disconnect	= acx565akm_disconnect,
-
-	.enable		= acx565akm_enable,
-	.disable	= acx565akm_disable,
-
-	.get_modes	= acx565akm_get_modes,
-};
-
-static int acx565akm_probe(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	struct backlight_device *bldev;
-	int max_brightness, brightness;
-	struct backlight_properties props;
-	struct gpio_desc *gpio;
-	int r;
-
-	dev_dbg(&spi->dev, "%s\n", __func__);
-
-	spi->mode = SPI_MODE_3;
-
-	ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
-	if (ddata == NULL)
-		return -ENOMEM;
-
-	dev_set_drvdata(&spi->dev, ddata);
-
-	ddata->spi = spi;
-
-	mutex_init(&ddata->mutex);
-
-	gpio = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
-	if (IS_ERR(gpio)) {
-		dev_err(&spi->dev, "failed to parse reset gpio\n");
-		return PTR_ERR(gpio);
-	}
-
-	ddata->reset_gpio = gpio;
-
-	if (ddata->reset_gpio)
-		gpiod_set_value(ddata->reset_gpio, 1);
-
-	/*
-	 * After reset we have to wait 5 msec before the first
-	 * command can be sent.
-	 */
-	usleep_range(5000, 10000);
-
-	ddata->enabled = panel_enabled(ddata);
-
-	r = panel_detect(ddata);
-
-	if (!ddata->enabled && ddata->reset_gpio)
-		gpiod_set_value(ddata->reset_gpio, 0);
-
-	if (r) {
-		dev_err(&spi->dev, "%s panel detect error\n", __func__);
-		return r;
-	}
-
-	memset(&props, 0, sizeof(props));
-	props.fb_blank = FB_BLANK_UNBLANK;
-	props.power = FB_BLANK_UNBLANK;
-	props.type = BACKLIGHT_RAW;
-
-	bldev = backlight_device_register("acx565akm", &ddata->spi->dev,
-			ddata, &acx565akm_bl_ops, &props);
-	if (IS_ERR(bldev))
-		return PTR_ERR(bldev);
-	ddata->bl_dev = bldev;
-	if (ddata->has_cabc) {
-		r = sysfs_create_group(&bldev->dev.kobj, &bldev_attr_group);
-		if (r) {
-			dev_err(&bldev->dev,
-				"%s failed to create sysfs files\n", __func__);
-			goto err_backlight_unregister;
-		}
-		ddata->cabc_mode = get_hw_cabc_mode(ddata);
-	}
-
-	max_brightness = 255;
-
-	if (ddata->has_bc)
-		brightness = acx565akm_get_actual_brightness(ddata);
-	else
-		brightness = 0;
-
-	bldev->props.max_brightness = max_brightness;
-	bldev->props.brightness = brightness;
-
-	acx565akm_bl_update_status(bldev);
-
-
-	ddata->vm = acx565akm_panel_vm;
-
-	dssdev = &ddata->dssdev;
-	dssdev->dev = &spi->dev;
-	dssdev->ops = &acx565akm_ops;
-	dssdev->type = OMAP_DISPLAY_TYPE_SDI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
-			  | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE
-			  | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-
-err_backlight_unregister:
-	backlight_device_unregister(bldev);
-	return r;
-}
-
-static int acx565akm_remove(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	dev_dbg(&ddata->spi->dev, "%s\n", __func__);
-
-	sysfs_remove_group(&ddata->bl_dev->dev.kobj, &bldev_attr_group);
-	backlight_device_unregister(ddata->bl_dev);
-
-	omapdss_device_unregister(dssdev);
-
-	if (omapdss_device_is_enabled(dssdev))
-		acx565akm_disable(dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id acx565akm_of_match[] = {
-	{ .compatible = "omapdss,sony,acx565akm", },
-	{},
-};
-MODULE_DEVICE_TABLE(of, acx565akm_of_match);
-
-static struct spi_driver acx565akm_driver = {
-	.driver = {
-		.name	= "acx565akm",
-		.of_match_table = acx565akm_of_match,
-		.suppress_bind_attrs = true,
-	},
-	.probe	= acx565akm_probe,
-	.remove	= acx565akm_remove,
-};
-
-module_spi_driver(acx565akm_driver);
-
-MODULE_ALIAS("spi:sony,acx565akm");
-MODULE_AUTHOR("Nokia Corporation");
-MODULE_DESCRIPTION("acx565akm LCD Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c b/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
deleted file mode 100644
index 8993daa73e78..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Toppoly TD028TTEC1 panel support
- *
- * Copyright (C) 2008 Nokia Corporation
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * Neo 1973 code (jbt6k74.c):
- * Copyright (C) 2006-2007 by OpenMoko, Inc.
- * Author: Harald Welte <laforge@openmoko.org>
- *
- * Ported and adapted from Neo 1973 U-Boot by:
- * H. Nikolaus Schaller <hns@goldelico.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/spi/spi.h>
-
-#include "../dss/omapdss.h"
-
-struct panel_drv_data {
-	struct omap_dss_device dssdev;
-
-	struct videomode vm;
-
-	struct backlight_device *backlight;
-
-	struct spi_device *spi_dev;
-};
-
-static const struct videomode td028ttec1_panel_vm = {
-	.hactive	= 480,
-	.vactive	= 640,
-	.pixelclock	= 22153000,
-	.hfront_porch	= 24,
-	.hsync_len	= 8,
-	.hback_porch	= 8,
-	.vfront_porch	= 4,
-	.vsync_len	= 2,
-	.vback_porch	= 2,
-
-	.flags		= DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
-};
-
-#define JBT_COMMAND	0x000
-#define JBT_DATA	0x100
-
-static int jbt_ret_write_0(struct panel_drv_data *ddata, u8 reg)
-{
-	int rc;
-	u16 tx_buf = JBT_COMMAND | reg;
-
-	rc = spi_write(ddata->spi_dev, (u8 *)&tx_buf,
-			1*sizeof(u16));
-	if (rc != 0)
-		dev_err(&ddata->spi_dev->dev,
-			"jbt_ret_write_0 spi_write ret %d\n", rc);
-
-	return rc;
-}
-
-static int jbt_reg_write_1(struct panel_drv_data *ddata, u8 reg, u8 data)
-{
-	int rc;
-	u16 tx_buf[2];
-
-	tx_buf[0] = JBT_COMMAND | reg;
-	tx_buf[1] = JBT_DATA | data;
-	rc = spi_write(ddata->spi_dev, (u8 *)tx_buf,
-			2*sizeof(u16));
-	if (rc != 0)
-		dev_err(&ddata->spi_dev->dev,
-			"jbt_reg_write_1 spi_write ret %d\n", rc);
-
-	return rc;
-}
-
-static int jbt_reg_write_2(struct panel_drv_data *ddata, u8 reg, u16 data)
-{
-	int rc;
-	u16 tx_buf[3];
-
-	tx_buf[0] = JBT_COMMAND | reg;
-	tx_buf[1] = JBT_DATA | (data >> 8);
-	tx_buf[2] = JBT_DATA | (data & 0xff);
-
-	rc = spi_write(ddata->spi_dev, (u8 *)tx_buf,
-			3*sizeof(u16));
-
-	if (rc != 0)
-		dev_err(&ddata->spi_dev->dev,
-			"jbt_reg_write_2 spi_write ret %d\n", rc);
-
-	return rc;
-}
-
-enum jbt_register {
-	JBT_REG_SLEEP_IN		= 0x10,
-	JBT_REG_SLEEP_OUT		= 0x11,
-
-	JBT_REG_DISPLAY_OFF		= 0x28,
-	JBT_REG_DISPLAY_ON		= 0x29,
-
-	JBT_REG_RGB_FORMAT		= 0x3a,
-	JBT_REG_QUAD_RATE		= 0x3b,
-
-	JBT_REG_POWER_ON_OFF		= 0xb0,
-	JBT_REG_BOOSTER_OP		= 0xb1,
-	JBT_REG_BOOSTER_MODE		= 0xb2,
-	JBT_REG_BOOSTER_FREQ		= 0xb3,
-	JBT_REG_OPAMP_SYSCLK		= 0xb4,
-	JBT_REG_VSC_VOLTAGE		= 0xb5,
-	JBT_REG_VCOM_VOLTAGE		= 0xb6,
-	JBT_REG_EXT_DISPL		= 0xb7,
-	JBT_REG_OUTPUT_CONTROL		= 0xb8,
-	JBT_REG_DCCLK_DCEV		= 0xb9,
-	JBT_REG_DISPLAY_MODE1		= 0xba,
-	JBT_REG_DISPLAY_MODE2		= 0xbb,
-	JBT_REG_DISPLAY_MODE		= 0xbc,
-	JBT_REG_ASW_SLEW		= 0xbd,
-	JBT_REG_DUMMY_DISPLAY		= 0xbe,
-	JBT_REG_DRIVE_SYSTEM		= 0xbf,
-
-	JBT_REG_SLEEP_OUT_FR_A		= 0xc0,
-	JBT_REG_SLEEP_OUT_FR_B		= 0xc1,
-	JBT_REG_SLEEP_OUT_FR_C		= 0xc2,
-	JBT_REG_SLEEP_IN_LCCNT_D	= 0xc3,
-	JBT_REG_SLEEP_IN_LCCNT_E	= 0xc4,
-	JBT_REG_SLEEP_IN_LCCNT_F	= 0xc5,
-	JBT_REG_SLEEP_IN_LCCNT_G	= 0xc6,
-
-	JBT_REG_GAMMA1_FINE_1		= 0xc7,
-	JBT_REG_GAMMA1_FINE_2		= 0xc8,
-	JBT_REG_GAMMA1_INCLINATION	= 0xc9,
-	JBT_REG_GAMMA1_BLUE_OFFSET	= 0xca,
-
-	JBT_REG_BLANK_CONTROL		= 0xcf,
-	JBT_REG_BLANK_TH_TV		= 0xd0,
-	JBT_REG_CKV_ON_OFF		= 0xd1,
-	JBT_REG_CKV_1_2			= 0xd2,
-	JBT_REG_OEV_TIMING		= 0xd3,
-	JBT_REG_ASW_TIMING_1		= 0xd4,
-	JBT_REG_ASW_TIMING_2		= 0xd5,
-
-	JBT_REG_HCLOCK_VGA		= 0xec,
-	JBT_REG_HCLOCK_QVGA		= 0xed,
-};
-
-#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
-
-static int td028ttec1_panel_connect(struct omap_dss_device *src,
-				    struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void td028ttec1_panel_disconnect(struct omap_dss_device *src,
-					struct omap_dss_device *dst)
-{
-}
-
-static void td028ttec1_panel_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-	int r = 0;
-
-	dev_dbg(dssdev->dev, "%s: state %d\n", __func__, dssdev->state);
-
-	/* three times command zero */
-	r |= jbt_ret_write_0(ddata, 0x00);
-	usleep_range(1000, 2000);
-	r |= jbt_ret_write_0(ddata, 0x00);
-	usleep_range(1000, 2000);
-	r |= jbt_ret_write_0(ddata, 0x00);
-	usleep_range(1000, 2000);
-
-	if (r) {
-		dev_warn(dssdev->dev, "%s: transfer error\n", __func__);
-		return;
-	}
-
-	/* deep standby out */
-	r |= jbt_reg_write_1(ddata, JBT_REG_POWER_ON_OFF, 0x17);
-
-	/* RGB I/F on, RAM write off, QVGA through, SIGCON enable */
-	r |= jbt_reg_write_1(ddata, JBT_REG_DISPLAY_MODE, 0x80);
-
-	/* Quad mode off */
-	r |= jbt_reg_write_1(ddata, JBT_REG_QUAD_RATE, 0x00);
-
-	/* AVDD on, XVDD on */
-	r |= jbt_reg_write_1(ddata, JBT_REG_POWER_ON_OFF, 0x16);
-
-	/* Output control */
-	r |= jbt_reg_write_2(ddata, JBT_REG_OUTPUT_CONTROL, 0xfff9);
-
-	/* Sleep mode off */
-	r |= jbt_ret_write_0(ddata, JBT_REG_SLEEP_OUT);
-
-	/* at this point we have like 50% grey */
-
-	/* initialize register set */
-	r |= jbt_reg_write_1(ddata, JBT_REG_DISPLAY_MODE1, 0x01);
-	r |= jbt_reg_write_1(ddata, JBT_REG_DISPLAY_MODE2, 0x00);
-	r |= jbt_reg_write_1(ddata, JBT_REG_RGB_FORMAT, 0x60);
-	r |= jbt_reg_write_1(ddata, JBT_REG_DRIVE_SYSTEM, 0x10);
-	r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_OP, 0x56);
-	r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_MODE, 0x33);
-	r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_FREQ, 0x11);
-	r |= jbt_reg_write_1(ddata, JBT_REG_BOOSTER_FREQ, 0x11);
-	r |= jbt_reg_write_1(ddata, JBT_REG_OPAMP_SYSCLK, 0x02);
-	r |= jbt_reg_write_1(ddata, JBT_REG_VSC_VOLTAGE, 0x2b);
-	r |= jbt_reg_write_1(ddata, JBT_REG_VCOM_VOLTAGE, 0x40);
-	r |= jbt_reg_write_1(ddata, JBT_REG_EXT_DISPL, 0x03);
-	r |= jbt_reg_write_1(ddata, JBT_REG_DCCLK_DCEV, 0x04);
-	/*
-	 * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement
-	 * to avoid red / blue flicker
-	 */
-	r |= jbt_reg_write_1(ddata, JBT_REG_ASW_SLEW, 0x04);
-	r |= jbt_reg_write_1(ddata, JBT_REG_DUMMY_DISPLAY, 0x00);
-
-	r |= jbt_reg_write_1(ddata, JBT_REG_SLEEP_OUT_FR_A, 0x11);
-	r |= jbt_reg_write_1(ddata, JBT_REG_SLEEP_OUT_FR_B, 0x11);
-	r |= jbt_reg_write_1(ddata, JBT_REG_SLEEP_OUT_FR_C, 0x11);
-	r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040);
-	r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0);
-	r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020);
-	r |= jbt_reg_write_2(ddata, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0);
-
-	r |= jbt_reg_write_2(ddata, JBT_REG_GAMMA1_FINE_1, 0x5533);
-	r |= jbt_reg_write_1(ddata, JBT_REG_GAMMA1_FINE_2, 0x00);
-	r |= jbt_reg_write_1(ddata, JBT_REG_GAMMA1_INCLINATION, 0x00);
-	r |= jbt_reg_write_1(ddata, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00);
-
-	r |= jbt_reg_write_2(ddata, JBT_REG_HCLOCK_VGA, 0x1f0);
-	r |= jbt_reg_write_1(ddata, JBT_REG_BLANK_CONTROL, 0x02);
-	r |= jbt_reg_write_2(ddata, JBT_REG_BLANK_TH_TV, 0x0804);
-
-	r |= jbt_reg_write_1(ddata, JBT_REG_CKV_ON_OFF, 0x01);
-	r |= jbt_reg_write_2(ddata, JBT_REG_CKV_1_2, 0x0000);
-
-	r |= jbt_reg_write_2(ddata, JBT_REG_OEV_TIMING, 0x0d0e);
-	r |= jbt_reg_write_2(ddata, JBT_REG_ASW_TIMING_1, 0x11a4);
-	r |= jbt_reg_write_1(ddata, JBT_REG_ASW_TIMING_2, 0x0e);
-
-	r |= jbt_ret_write_0(ddata, JBT_REG_DISPLAY_ON);
-
-	if (r)
-		dev_err(dssdev->dev, "%s: write error\n", __func__);
-
-	backlight_enable(ddata->backlight);
-}
-
-static void td028ttec1_panel_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	backlight_disable(ddata->backlight);
-
-	dev_dbg(dssdev->dev, "td028ttec1_panel_disable()\n");
-
-	jbt_ret_write_0(ddata, JBT_REG_DISPLAY_OFF);
-	jbt_reg_write_2(ddata, JBT_REG_OUTPUT_CONTROL, 0x8002);
-	jbt_ret_write_0(ddata, JBT_REG_SLEEP_IN);
-	jbt_reg_write_1(ddata, JBT_REG_POWER_ON_OFF, 0x00);
-}
-
-static int td028ttec1_panel_get_modes(struct omap_dss_device *dssdev,
-				      struct drm_connector *connector)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return omapdss_display_get_modes(connector, &ddata->vm);
-}
-
-static const struct omap_dss_device_ops td028ttec1_ops = {
-	.connect	= td028ttec1_panel_connect,
-	.disconnect	= td028ttec1_panel_disconnect,
-
-	.enable		= td028ttec1_panel_enable,
-	.disable	= td028ttec1_panel_disable,
-
-	.get_modes	= td028ttec1_panel_get_modes,
-};
-
-static int td028ttec1_panel_probe(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	int r;
-
-	dev_dbg(&spi->dev, "%s\n", __func__);
-
-	spi->bits_per_word = 9;
-	spi->mode = SPI_MODE_3;
-
-	r = spi_setup(spi);
-	if (r < 0) {
-		dev_err(&spi->dev, "spi_setup failed: %d\n", r);
-		return r;
-	}
-
-	ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
-	if (ddata == NULL)
-		return -ENOMEM;
-
-	ddata->backlight = devm_of_find_backlight(&spi->dev);
-	if (IS_ERR(ddata->backlight))
-		return PTR_ERR(ddata->backlight);
-
-	dev_set_drvdata(&spi->dev, ddata);
-
-	ddata->spi_dev = spi;
-
-	ddata->vm = td028ttec1_panel_vm;
-
-	dssdev = &ddata->dssdev;
-	dssdev->dev = &spi->dev;
-	dssdev->ops = &td028ttec1_ops;
-	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-
-	/*
-	 * Note: According to the panel documentation:
-	 * SYNC needs to be driven on the FALLING edge
-	 */
-	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
-			  | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE
-			  | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int td028ttec1_panel_remove(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	dev_dbg(&ddata->spi_dev->dev, "%s\n", __func__);
-
-	omapdss_device_unregister(dssdev);
-
-	td028ttec1_panel_disable(dssdev);
-
-	return 0;
-}
-
-static const struct of_device_id td028ttec1_of_match[] = {
-	{ .compatible = "omapdss,tpo,td028ttec1", },
-	/* keep to not break older DTB */
-	{ .compatible = "omapdss,toppoly,td028ttec1", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, td028ttec1_of_match);
-
-static const struct spi_device_id td028ttec1_ids[] = {
-	{ "toppoly,td028ttec1", 0 },
-	{ "tpo,td028ttec1", 0},
-	{ /* sentinel */ }
-};
-
-MODULE_DEVICE_TABLE(spi, td028ttec1_ids);
-
-
-static struct spi_driver td028ttec1_spi_driver = {
-	.probe		= td028ttec1_panel_probe,
-	.remove		= td028ttec1_panel_remove,
-	.id_table	= td028ttec1_ids,
-
-	.driver         = {
-		.name   = "panel-tpo-td028ttec1",
-		.of_match_table = td028ttec1_of_match,
-		.suppress_bind_attrs = true,
-	},
-};
-
-module_spi_driver(td028ttec1_spi_driver);
-
-MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
-MODULE_DESCRIPTION("Toppoly TD028TTEC1 panel driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c b/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
deleted file mode 100644
index c667f4287893..000000000000
--- a/drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
+++ /dev/null
@@ -1,513 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * TPO TD043MTEA1 Panel driver
- *
- * Author: Gražvydas Ignotas <notasas@gmail.com>
- * Converted to new DSS device model: Tomi Valkeinen <tomi.valkeinen@ti.com>
- */
-
-#include <linux/delay.h>
-#include <linux/err.h>
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-#include <linux/regulator/consumer.h>
-#include <linux/slab.h>
-#include <linux/spi/spi.h>
-
-#include "../dss/omapdss.h"
-
-#define TPO_R02_MODE(x)		((x) & 7)
-#define TPO_R02_MODE_800x480	7
-#define TPO_R02_NCLK_RISING	BIT(3)
-#define TPO_R02_HSYNC_HIGH	BIT(4)
-#define TPO_R02_VSYNC_HIGH	BIT(5)
-
-#define TPO_R03_NSTANDBY	BIT(0)
-#define TPO_R03_EN_CP_CLK	BIT(1)
-#define TPO_R03_EN_VGL_PUMP	BIT(2)
-#define TPO_R03_EN_PWM		BIT(3)
-#define TPO_R03_DRIVING_CAP_100	BIT(4)
-#define TPO_R03_EN_PRE_CHARGE	BIT(6)
-#define TPO_R03_SOFTWARE_CTL	BIT(7)
-
-#define TPO_R04_NFLIP_H		BIT(0)
-#define TPO_R04_NFLIP_V		BIT(1)
-#define TPO_R04_CP_CLK_FREQ_1H	BIT(2)
-#define TPO_R04_VGL_FREQ_1H	BIT(4)
-
-#define TPO_R03_VAL_NORMAL (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | \
-			TPO_R03_EN_VGL_PUMP |  TPO_R03_EN_PWM | \
-			TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \
-			TPO_R03_SOFTWARE_CTL)
-
-#define TPO_R03_VAL_STANDBY (TPO_R03_DRIVING_CAP_100 | \
-			TPO_R03_EN_PRE_CHARGE | TPO_R03_SOFTWARE_CTL)
-
-static const u16 tpo_td043_def_gamma[12] = {
-	105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023
-};
-
-struct panel_drv_data {
-	struct omap_dss_device	dssdev;
-
-	struct videomode vm;
-
-	struct spi_device *spi;
-	struct regulator *vcc_reg;
-	struct gpio_desc *reset_gpio;
-	u16 gamma[12];
-	u32 mode;
-	u32 vmirror:1;
-	u32 powered_on:1;
-	u32 spi_suspended:1;
-	u32 power_on_resume:1;
-};
-
-static const struct videomode tpo_td043_vm = {
-	.hactive	= 800,
-	.vactive	= 480,
-
-	.pixelclock	= 36000000,
-
-	.hsync_len	= 1,
-	.hfront_porch	= 68,
-	.hback_porch	= 214,
-
-	.vsync_len	= 1,
-	.vfront_porch	= 39,
-	.vback_porch	= 34,
-
-	.flags		= DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW,
-};
-
-#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
-
-static int tpo_td043_write(struct spi_device *spi, u8 addr, u8 data)
-{
-	struct spi_message	m;
-	struct spi_transfer	xfer;
-	u16			w;
-	int			r;
-
-	spi_message_init(&m);
-
-	memset(&xfer, 0, sizeof(xfer));
-
-	w = ((u16)addr << 10) | (1 << 8) | data;
-	xfer.tx_buf = &w;
-	xfer.bits_per_word = 16;
-	xfer.len = 2;
-	spi_message_add_tail(&xfer, &m);
-
-	r = spi_sync(spi, &m);
-	if (r < 0)
-		dev_warn(&spi->dev, "failed to write to LCD reg (%d)\n", r);
-	return r;
-}
-
-static void tpo_td043_write_gamma(struct spi_device *spi, u16 gamma[12])
-{
-	u8 i, val;
-
-	/* gamma bits [9:8] */
-	for (val = i = 0; i < 4; i++)
-		val |= (gamma[i] & 0x300) >> ((i + 1) * 2);
-	tpo_td043_write(spi, 0x11, val);
-
-	for (val = i = 0; i < 4; i++)
-		val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2);
-	tpo_td043_write(spi, 0x12, val);
-
-	for (val = i = 0; i < 4; i++)
-		val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2);
-	tpo_td043_write(spi, 0x13, val);
-
-	/* gamma bits [7:0] */
-	for (val = i = 0; i < 12; i++)
-		tpo_td043_write(spi, 0x14 + i, gamma[i] & 0xff);
-}
-
-static int tpo_td043_write_mirror(struct spi_device *spi, bool h, bool v)
-{
-	u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V |
-		TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H;
-	if (h)
-		reg4 &= ~TPO_R04_NFLIP_H;
-	if (v)
-		reg4 &= ~TPO_R04_NFLIP_V;
-
-	return tpo_td043_write(spi, 4, reg4);
-}
-
-static ssize_t tpo_td043_vmirror_show(struct device *dev,
-	struct device_attribute *attr, char *buf)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-
-	return snprintf(buf, PAGE_SIZE, "%d\n", ddata->vmirror);
-}
-
-static ssize_t tpo_td043_vmirror_store(struct device *dev,
-	struct device_attribute *attr, const char *buf, size_t count)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	int val;
-	int ret;
-
-	ret = kstrtoint(buf, 0, &val);
-	if (ret < 0)
-		return ret;
-
-	val = !!val;
-
-	ret = tpo_td043_write_mirror(ddata->spi, false, val);
-	if (ret < 0)
-		return ret;
-
-	ddata->vmirror = val;
-
-	return count;
-}
-
-static ssize_t tpo_td043_mode_show(struct device *dev,
-	struct device_attribute *attr, char *buf)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-
-	return snprintf(buf, PAGE_SIZE, "%d\n", ddata->mode);
-}
-
-static ssize_t tpo_td043_mode_store(struct device *dev,
-	struct device_attribute *attr, const char *buf, size_t count)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	long val;
-	int ret;
-
-	ret = kstrtol(buf, 0, &val);
-	if (ret != 0 || val & ~7)
-		return -EINVAL;
-
-	ddata->mode = val;
-
-	val |= TPO_R02_NCLK_RISING;
-	tpo_td043_write(ddata->spi, 2, val);
-
-	return count;
-}
-
-static ssize_t tpo_td043_gamma_show(struct device *dev,
-	struct device_attribute *attr, char *buf)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	ssize_t len = 0;
-	int ret;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(ddata->gamma); i++) {
-		ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
-				ddata->gamma[i]);
-		if (ret < 0)
-			return ret;
-		len += ret;
-	}
-	buf[len - 1] = '\n';
-
-	return len;
-}
-
-static ssize_t tpo_td043_gamma_store(struct device *dev,
-	struct device_attribute *attr, const char *buf, size_t count)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	unsigned int g[12];
-	int ret;
-	int i;
-
-	ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u",
-			&g[0], &g[1], &g[2], &g[3], &g[4], &g[5],
-			&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
-
-	if (ret != 12)
-		return -EINVAL;
-
-	for (i = 0; i < 12; i++)
-		ddata->gamma[i] = g[i];
-
-	tpo_td043_write_gamma(ddata->spi, ddata->gamma);
-
-	return count;
-}
-
-static DEVICE_ATTR(vmirror, S_IRUGO | S_IWUSR,
-		tpo_td043_vmirror_show, tpo_td043_vmirror_store);
-static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR,
-		tpo_td043_mode_show, tpo_td043_mode_store);
-static DEVICE_ATTR(gamma, S_IRUGO | S_IWUSR,
-		tpo_td043_gamma_show, tpo_td043_gamma_store);
-
-static struct attribute *tpo_td043_attrs[] = {
-	&dev_attr_vmirror.attr,
-	&dev_attr_mode.attr,
-	&dev_attr_gamma.attr,
-	NULL,
-};
-
-static const struct attribute_group tpo_td043_attr_group = {
-	.attrs = tpo_td043_attrs,
-};
-
-static int tpo_td043_power_on(struct panel_drv_data *ddata)
-{
-	int r;
-
-	if (ddata->powered_on)
-		return 0;
-
-	r = regulator_enable(ddata->vcc_reg);
-	if (r != 0)
-		return r;
-
-	/* wait for panel to stabilize */
-	msleep(160);
-
-	gpiod_set_value(ddata->reset_gpio, 0);
-
-	tpo_td043_write(ddata->spi, 2,
-			TPO_R02_MODE(ddata->mode) | TPO_R02_NCLK_RISING);
-	tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_NORMAL);
-	tpo_td043_write(ddata->spi, 0x20, 0xf0);
-	tpo_td043_write(ddata->spi, 0x21, 0xf0);
-	tpo_td043_write_mirror(ddata->spi, false, ddata->vmirror);
-	tpo_td043_write_gamma(ddata->spi, ddata->gamma);
-
-	ddata->powered_on = 1;
-	return 0;
-}
-
-static void tpo_td043_power_off(struct panel_drv_data *ddata)
-{
-	if (!ddata->powered_on)
-		return;
-
-	tpo_td043_write(ddata->spi, 3,
-			TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
-
-	gpiod_set_value(ddata->reset_gpio, 1);
-
-	/* wait for at least 2 vsyncs before cutting off power */
-	msleep(50);
-
-	tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_STANDBY);
-
-	regulator_disable(ddata->vcc_reg);
-
-	ddata->powered_on = 0;
-}
-
-static int tpo_td043_connect(struct omap_dss_device *src,
-			     struct omap_dss_device *dst)
-{
-	return 0;
-}
-
-static void tpo_td043_disconnect(struct omap_dss_device *src,
-				 struct omap_dss_device *dst)
-{
-}
-
-static void tpo_td043_enable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-	int r;
-
-	/*
-	 * If we are resuming from system suspend, SPI clocks might not be
-	 * enabled yet, so we'll program the LCD from SPI PM resume callback.
-	 */
-	if (!ddata->spi_suspended) {
-		r = tpo_td043_power_on(ddata);
-		if (r) {
-			dev_err(&ddata->spi->dev, "%s: power on failed (%d)\n",
-				__func__, r);
-			return;
-		}
-	}
-}
-
-static void tpo_td043_disable(struct omap_dss_device *dssdev)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	if (!ddata->spi_suspended)
-		tpo_td043_power_off(ddata);
-}
-
-static int tpo_td043_get_modes(struct omap_dss_device *dssdev,
-			       struct drm_connector *connector)
-{
-	struct panel_drv_data *ddata = to_panel_data(dssdev);
-
-	return omapdss_display_get_modes(connector, &ddata->vm);
-}
-
-static const struct omap_dss_device_ops tpo_td043_ops = {
-	.connect	= tpo_td043_connect,
-	.disconnect	= tpo_td043_disconnect,
-
-	.enable		= tpo_td043_enable,
-	.disable	= tpo_td043_disable,
-
-	.get_modes	= tpo_td043_get_modes,
-};
-
-static int tpo_td043_probe(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata;
-	struct omap_dss_device *dssdev;
-	struct gpio_desc *gpio;
-	int r;
-
-	dev_dbg(&spi->dev, "%s\n", __func__);
-
-	spi->bits_per_word = 16;
-	spi->mode = SPI_MODE_0;
-
-	r = spi_setup(spi);
-	if (r < 0) {
-		dev_err(&spi->dev, "spi_setup failed: %d\n", r);
-		return r;
-	}
-
-	ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
-	if (ddata == NULL)
-		return -ENOMEM;
-
-	dev_set_drvdata(&spi->dev, ddata);
-
-	ddata->spi = spi;
-
-	ddata->mode = TPO_R02_MODE_800x480;
-	memcpy(ddata->gamma, tpo_td043_def_gamma, sizeof(ddata->gamma));
-
-	ddata->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
-	if (IS_ERR(ddata->vcc_reg)) {
-		dev_err(&spi->dev, "failed to get LCD VCC regulator\n");
-		return PTR_ERR(ddata->vcc_reg);
-	}
-
-	gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
-	if (IS_ERR(gpio)) {
-		dev_err(&spi->dev, "failed to get reset gpio\n");
-		return PTR_ERR(gpio);
-	}
-
-	ddata->reset_gpio = gpio;
-
-	r = sysfs_create_group(&spi->dev.kobj, &tpo_td043_attr_group);
-	if (r) {
-		dev_err(&spi->dev, "failed to create sysfs files\n");
-		return r;
-	}
-
-	ddata->vm = tpo_td043_vm;
-
-	dssdev = &ddata->dssdev;
-	dssdev->dev = &spi->dev;
-	dssdev->ops = &tpo_td043_ops;
-	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
-	dssdev->display = true;
-	dssdev->owner = THIS_MODULE;
-	dssdev->of_port = 0;
-	dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
-
-	/*
-	 * Note: According to the panel documentation:
-	 * SYNC needs to be driven on the FALLING edge
-	 */
-	dssdev->bus_flags = DRM_BUS_FLAG_DE_HIGH
-			  | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE
-			  | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
-
-	omapdss_display_init(dssdev);
-	omapdss_device_register(dssdev);
-
-	return 0;
-}
-
-static int tpo_td043_remove(struct spi_device *spi)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
-	struct omap_dss_device *dssdev = &ddata->dssdev;
-
-	dev_dbg(&ddata->spi->dev, "%s\n", __func__);
-
-	omapdss_device_unregister(dssdev);
-
-	if (omapdss_device_is_enabled(dssdev))
-		tpo_td043_disable(dssdev);
-
-	sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group);
-
-	return 0;
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int tpo_td043_spi_suspend(struct device *dev)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-
-	dev_dbg(dev, "tpo_td043_spi_suspend, tpo %p\n", ddata);
-
-	ddata->power_on_resume = ddata->powered_on;
-	tpo_td043_power_off(ddata);
-	ddata->spi_suspended = 1;
-
-	return 0;
-}
-
-static int tpo_td043_spi_resume(struct device *dev)
-{
-	struct panel_drv_data *ddata = dev_get_drvdata(dev);
-	int ret;
-
-	dev_dbg(dev, "tpo_td043_spi_resume\n");
-
-	if (ddata->power_on_resume) {
-		ret = tpo_td043_power_on(ddata);
-		if (ret)
-			return ret;
-	}
-	ddata->spi_suspended = 0;
-
-	return 0;
-}
-#endif
-
-static SIMPLE_DEV_PM_OPS(tpo_td043_spi_pm,
-	tpo_td043_spi_suspend, tpo_td043_spi_resume);
-
-static const struct of_device_id tpo_td043_of_match[] = {
-	{ .compatible = "omapdss,tpo,td043mtea1", },
-	{},
-};
-
-MODULE_DEVICE_TABLE(of, tpo_td043_of_match);
-
-static struct spi_driver tpo_td043_spi_driver = {
-	.driver = {
-		.name	= "panel-tpo-td043mtea1",
-		.pm	= &tpo_td043_spi_pm,
-		.of_match_table = tpo_td043_of_match,
-		.suppress_bind_attrs = true,
-	},
-	.probe	= tpo_td043_probe,
-	.remove	= tpo_td043_remove,
-};
-
-module_spi_driver(tpo_td043_spi_driver);
-
-MODULE_ALIAS("spi:tpo,td043mtea1");
-MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
-MODULE_DESCRIPTION("TPO TD043MTEA1 LCD Driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c b/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c
index 24e36d68f3c4..caa291ea1c5c 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss-boot-init.c
@@ -185,14 +185,7 @@ static const struct of_device_id omapdss_of_match[] __initconst = {
 };
 
 static const struct of_device_id omapdss_of_fixups_whitelist[] __initconst = {
-	{ .compatible = "lgphilips,lb035q02" },
-	{ .compatible = "nec,nl8048hl11" },
 	{ .compatible = "panel-dsi-cm" },
-	{ .compatible = "sharp,ls037v7dw01" },
-	{ .compatible = "sony,acx565akm" },
-	{ .compatible = "toppoly,td028ttec1" },
-	{ .compatible = "tpo,td028ttec1" },
-	{ .compatible = "tpo,td043mtea1" },
 	{},
 };
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 51/60] drm/omap: dpi: Sort includes alphabetically
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (45 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 50/60] drm/omap: displays: Remove unused panel drivers Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 52/60] drm/omap: dpi: Reorder functions in sections Laurent Pinchart
                     ` (9 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

This makes it easier to quickly locate duplicate includes.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/dpi.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c
index e1f94a84c9bd..da7d50cde31d 100644
--- a/drivers/gpu/drm/omapdrm/dss/dpi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dpi.c
@@ -20,20 +20,20 @@
 
 #define DSS_SUBSYS_NAME "DPI"
 
-#include <linux/kernel.h>
+#include <linux/clk.h>
 #include <linux/delay.h>
-#include <linux/export.h>
 #include <linux/err.h>
 #include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/regulator/consumer.h>
 #include <linux/string.h>
-#include <linux/of.h>
-#include <linux/clk.h>
 #include <linux/sys_soc.h>
 
-#include "omapdss.h"
 #include "dss.h"
+#include "omapdss.h"
 
 struct dpi_data {
 	struct platform_device *pdev;
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 52/60] drm/omap: dpi: Reorder functions in sections
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (46 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 51/60] drm/omap: dpi: Sort includes alphabetically Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 53/60] drm/omap: dpi: Simplify clock setting API Laurent Pinchart
                     ` (8 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Group functions based on their purpose and split them in sections to
make the source code easier to navigate.

No functional change is included.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/dpi.c | 146 ++++++++++++++++--------------
 1 file changed, 79 insertions(+), 67 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c
index da7d50cde31d..12a70b7163da 100644
--- a/drivers/gpu/drm/omapdrm/dss/dpi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dpi.c
@@ -59,6 +59,10 @@ static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev)
 	return container_of(dssdev, struct dpi_data, output);
 }
 
+/* -----------------------------------------------------------------------------
+ * Clock Handling and PLL
+ */
+
 static enum dss_clk_source dpi_get_clk_src_dra7xx(struct dpi_data *dpi,
 						  enum omap_channel channel)
 {
@@ -377,6 +381,62 @@ static void dpi_config_lcd_manager(struct dpi_data *dpi)
 	dss_mgr_set_lcd_config(&dpi->output, &dpi->mgr_config);
 }
 
+static int dpi_verify_pll(struct dss_pll *pll)
+{
+	int r;
+
+	/* do initial setup with the PLL to see if it is operational */
+
+	r = dss_pll_enable(pll);
+	if (r)
+		return r;
+
+	dss_pll_disable(pll);
+
+	return 0;
+}
+
+static void dpi_init_pll(struct dpi_data *dpi)
+{
+	struct dss_pll *pll;
+
+	if (dpi->pll)
+		return;
+
+	dpi->clk_src = dpi_get_clk_src(dpi);
+
+	pll = dss_pll_find_by_src(dpi->dss, dpi->clk_src);
+	if (!pll)
+		return;
+
+	if (dpi_verify_pll(pll)) {
+		DSSWARN("PLL not operational\n");
+		return;
+	}
+
+	dpi->pll = pll;
+}
+
+/* -----------------------------------------------------------------------------
+ * omap_dss_device Operations
+ */
+
+static int dpi_connect(struct omap_dss_device *src,
+		       struct omap_dss_device *dst)
+{
+	struct dpi_data *dpi = dpi_get_data_from_dssdev(dst);
+
+	dpi_init_pll(dpi);
+
+	return omapdss_device_connect(dst->dss, dst, dst->next);
+}
+
+static void dpi_disconnect(struct omap_dss_device *src,
+			   struct omap_dss_device *dst)
+{
+	omapdss_device_disconnect(dst, dst->next);
+}
+
 static void dpi_display_enable(struct omap_dss_device *dssdev)
 {
 	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
@@ -457,20 +517,6 @@ static void dpi_display_disable(struct omap_dss_device *dssdev)
 	mutex_unlock(&dpi->lock);
 }
 
-static void dpi_set_timings(struct omap_dss_device *dssdev,
-			    const struct drm_display_mode *mode)
-{
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
-
-	DSSDBG("dpi_set_timings\n");
-
-	mutex_lock(&dpi->lock);
-
-	dpi->pixelclock = mode->clock * 1000;
-
-	mutex_unlock(&dpi->lock);
-}
-
 static int dpi_check_timings(struct omap_dss_device *dssdev,
 			     struct drm_display_mode *mode)
 {
@@ -511,41 +557,30 @@ static int dpi_check_timings(struct omap_dss_device *dssdev,
 	return 0;
 }
 
-static int dpi_verify_pll(struct dss_pll *pll)
+static void dpi_set_timings(struct omap_dss_device *dssdev,
+			    const struct drm_display_mode *mode)
 {
-	int r;
+	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
 
-	/* do initial setup with the PLL to see if it is operational */
+	DSSDBG("dpi_set_timings\n");
 
-	r = dss_pll_enable(pll);
-	if (r)
-		return r;
+	mutex_lock(&dpi->lock);
 
-	dss_pll_disable(pll);
+	dpi->pixelclock = mode->clock * 1000;
 
-	return 0;
+	mutex_unlock(&dpi->lock);
 }
 
-static void dpi_init_pll(struct dpi_data *dpi)
-{
-	struct dss_pll *pll;
-
-	if (dpi->pll)
-		return;
-
-	dpi->clk_src = dpi_get_clk_src(dpi);
+static const struct omap_dss_device_ops dpi_ops = {
+	.connect = dpi_connect,
+	.disconnect = dpi_disconnect,
 
-	pll = dss_pll_find_by_src(dpi->dss, dpi->clk_src);
-	if (!pll)
-		return;
+	.enable = dpi_display_enable,
+	.disable = dpi_display_disable,
 
-	if (dpi_verify_pll(pll)) {
-		DSSWARN("PLL not operational\n");
-		return;
-	}
-
-	dpi->pll = pll;
-}
+	.check_timings = dpi_check_timings,
+	.set_timings = dpi_set_timings,
+};
 
 /*
  * Return a hardcoded channel for the DPI output. This should work for
@@ -583,33 +618,6 @@ static enum omap_channel dpi_get_channel(struct dpi_data *dpi)
 	}
 }
 
-static int dpi_connect(struct omap_dss_device *src,
-		       struct omap_dss_device *dst)
-{
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dst);
-
-	dpi_init_pll(dpi);
-
-	return omapdss_device_connect(dst->dss, dst, dst->next);
-}
-
-static void dpi_disconnect(struct omap_dss_device *src,
-			   struct omap_dss_device *dst)
-{
-	omapdss_device_disconnect(dst, dst->next);
-}
-
-static const struct omap_dss_device_ops dpi_ops = {
-	.connect = dpi_connect,
-	.disconnect = dpi_disconnect,
-
-	.enable = dpi_display_enable,
-	.disable = dpi_display_disable,
-
-	.check_timings = dpi_check_timings,
-	.set_timings = dpi_set_timings,
-};
-
 static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
 {
 	struct omap_dss_device *out = &dpi->output;
@@ -658,6 +666,10 @@ static void dpi_uninit_output_port(struct device_node *port)
 	omapdss_device_cleanup_output(out);
 }
 
+/* -----------------------------------------------------------------------------
+ * Initialisation and Cleanup
+ */
+
 static const struct soc_device_attribute dpi_soc_devices[] = {
 	{ .machine = "OMAP3[456]*" },
 	{ .machine = "[AD]M37*" },
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 53/60] drm/omap: dpi: Simplify clock setting API
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (47 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 52/60] drm/omap: dpi: Reorder functions in sections Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 54/60] drm/omap: dpi: Register a drm_bridge Laurent Pinchart
                     ` (7 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The dpi_set_pll_clk() and dpi_set_dispc_clk() return various information
through pointer arguments that are never used by the callers. Remove
them to simplify the clock setting API.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/dpi.c | 32 ++++++++-----------------------
 1 file changed, 8 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c
index 12a70b7163da..a83bcfe588df 100644
--- a/drivers/gpu/drm/omapdrm/dss/dpi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dpi.c
@@ -298,9 +298,7 @@ static bool dpi_dss_clk_calc(struct dpi_data *dpi, unsigned long pck,
 
 
 
-static int dpi_set_pll_clk(struct dpi_data *dpi, enum omap_channel channel,
-		unsigned long pck_req, unsigned long *fck, int *lck_div,
-		int *pck_div)
+static int dpi_set_pll_clk(struct dpi_data *dpi, unsigned long pck_req)
 {
 	struct dpi_clk_calc_ctx ctx;
 	int r;
@@ -314,19 +312,15 @@ static int dpi_set_pll_clk(struct dpi_data *dpi, enum omap_channel channel,
 	if (r)
 		return r;
 
-	dss_select_lcd_clk_source(dpi->dss, channel, dpi->clk_src);
+	dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel,
+				  dpi->clk_src);
 
 	dpi->mgr_config.clock_info = ctx.dispc_cinfo;
 
-	*fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
-	*lck_div = ctx.dispc_cinfo.lck_div;
-	*pck_div = ctx.dispc_cinfo.pck_div;
-
 	return 0;
 }
 
-static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req,
-		unsigned long *fck, int *lck_div, int *pck_div)
+static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req)
 {
 	struct dpi_clk_calc_ctx ctx;
 	int r;
@@ -342,29 +336,19 @@ static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req,
 
 	dpi->mgr_config.clock_info = ctx.dispc_cinfo;
 
-	*fck = ctx.fck;
-	*lck_div = ctx.dispc_cinfo.lck_div;
-	*pck_div = ctx.dispc_cinfo.pck_div;
-
 	return 0;
 }
 
 static int dpi_set_mode(struct dpi_data *dpi)
 {
-	int lck_div = 0, pck_div = 0;
-	unsigned long fck = 0;
-	int r = 0;
+	int r;
 
 	if (dpi->pll)
-		r = dpi_set_pll_clk(dpi, dpi->output.dispc_channel,
-				    dpi->pixelclock, &fck, &lck_div, &pck_div);
+		r = dpi_set_pll_clk(dpi, dpi->pixelclock);
 	else
-		r = dpi_set_dispc_clk(dpi, dpi->pixelclock, &fck,
-				&lck_div, &pck_div);
-	if (r)
-		return r;
+		r = dpi_set_dispc_clk(dpi, dpi->pixelclock);
 
-	return 0;
+	return r;
 }
 
 static void dpi_config_lcd_manager(struct dpi_data *dpi)
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 54/60] drm/omap: dpi: Register a drm_bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (48 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 53/60] drm/omap: dpi: Simplify clock setting API Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 55/60] drm/omap: sdi: Sort includes alphabetically Laurent Pinchart
                     ` (6 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In order to integrate with a chain of drm_bridge, the internal DPI
output has to expose its operations through the drm_bridge API.
Register a bridge at initialisation time to do so and remove the
omap_dss_device operations that are now unused.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/dpi.c | 204 ++++++++++++++++++------------
 1 file changed, 121 insertions(+), 83 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/dpi.c b/drivers/gpu/drm/omapdrm/dss/dpi.c
index a83bcfe588df..ebc258bf56aa 100644
--- a/drivers/gpu/drm/omapdrm/dss/dpi.c
+++ b/drivers/gpu/drm/omapdrm/dss/dpi.c
@@ -32,6 +32,8 @@
 #include <linux/string.h>
 #include <linux/sys_soc.h>
 
+#include <drm/drm_bridge.h>
+
 #include "dss.h"
 #include "omapdss.h"
 
@@ -52,12 +54,10 @@ struct dpi_data {
 	int data_lines;
 
 	struct omap_dss_device output;
+	struct drm_bridge bridge;
 };
 
-static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev)
-{
-	return container_of(dssdev, struct dpi_data, output);
-}
+#define drm_bridge_to_dpi(bridge) container_of(bridge, struct dpi_data, bridge)
 
 /* -----------------------------------------------------------------------------
  * Clock Handling and PLL
@@ -365,6 +365,32 @@ static void dpi_config_lcd_manager(struct dpi_data *dpi)
 	dss_mgr_set_lcd_config(&dpi->output, &dpi->mgr_config);
 }
 
+static int dpi_clock_update(struct dpi_data *dpi, unsigned long *clock)
+{
+	int lck_div, pck_div;
+	unsigned long fck;
+	struct dpi_clk_calc_ctx ctx;
+
+	if (dpi->pll) {
+		if (!dpi_pll_clk_calc(dpi, *clock, &ctx))
+			return -EINVAL;
+
+		fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
+	} else {
+		if (!dpi_dss_clk_calc(dpi, *clock, &ctx))
+			return -EINVAL;
+
+		fck = ctx.fck;
+	}
+
+	lck_div = ctx.dispc_cinfo.lck_div;
+	pck_div = ctx.dispc_cinfo.pck_div;
+
+	*clock = fck / lck_div / pck_div;
+
+	return 0;
+}
+
 static int dpi_verify_pll(struct dss_pll *pll)
 {
 	int r;
@@ -402,29 +428,75 @@ static void dpi_init_pll(struct dpi_data *dpi)
 }
 
 /* -----------------------------------------------------------------------------
- * omap_dss_device Operations
+ * DRM Bridge Operations
  */
 
-static int dpi_connect(struct omap_dss_device *src,
-		       struct omap_dss_device *dst)
+static int dpi_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dst);
+	struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
+
+	if (create_connector)
+		return -EINVAL;
 
 	dpi_init_pll(dpi);
 
-	return omapdss_device_connect(dst->dss, dst, dst->next);
+	return drm_bridge_attach(bridge->encoder, dpi->output.next_bridge,
+				 bridge, false);
 }
 
-static void dpi_disconnect(struct omap_dss_device *src,
-			   struct omap_dss_device *dst)
+static enum drm_mode_status
+dpi_bridge_mode_valid(struct drm_bridge *bridge,
+		       const struct drm_display_mode *mode)
 {
-	omapdss_device_disconnect(dst, dst->next);
+	struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
+	unsigned long clock = mode->clock * 1000;
+	int ret;
+
+	if (mode->hdisplay % 8 != 0)
+		return MODE_BAD_WIDTH;
+
+	if (mode->clock == 0)
+		return MODE_NOCLOCK;
+
+	ret = dpi_clock_update(dpi, &clock);
+	if (ret < 0)
+		return MODE_CLOCK_RANGE;
+
+	return MODE_OK;
+}
+
+static bool dpi_bridge_mode_fixup(struct drm_bridge *bridge,
+				   const struct drm_display_mode *mode,
+				   struct drm_display_mode *adjusted_mode)
+{
+	struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
+	unsigned long clock = mode->clock * 1000;
+	int ret;
+
+	ret = dpi_clock_update(dpi, &clock);
+	if (ret < 0)
+		return false;
+
+	adjusted_mode->clock = clock / 1000;
+
+	return true;
+}
+
+static void dpi_bridge_mode_set(struct drm_bridge *bridge,
+				 const struct drm_display_mode *mode,
+				 const struct drm_display_mode *adjusted_mode)
+{
+	struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
+
+	mutex_lock(&dpi->lock);
+	dpi->pixelclock = adjusted_mode->clock * 1000;
+	mutex_unlock(&dpi->lock);
 }
 
-static void dpi_display_enable(struct omap_dss_device *dssdev)
+static void dpi_bridge_enable(struct drm_bridge *bridge,
+			       struct drm_atomic_state *state)
 {
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
-	struct omap_dss_device *out = &dpi->output;
+	struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
 	int r;
 
 	mutex_lock(&dpi->lock);
@@ -439,7 +511,7 @@ static void dpi_display_enable(struct omap_dss_device *dssdev)
 	if (r)
 		goto err_get_dispc;
 
-	r = dss_dpi_select_source(dpi->dss, dpi->id, out->dispc_channel);
+	r = dss_dpi_select_source(dpi->dss, dpi->id, dpi->output.dispc_channel);
 	if (r)
 		goto err_src_sel;
 
@@ -479,9 +551,10 @@ static void dpi_display_enable(struct omap_dss_device *dssdev)
 	mutex_unlock(&dpi->lock);
 }
 
-static void dpi_display_disable(struct omap_dss_device *dssdev)
+static void dpi_bridge_disable(struct drm_bridge *bridge,
+				struct drm_atomic_state *state)
 {
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
+	struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
 
 	mutex_lock(&dpi->lock);
 
@@ -501,71 +574,33 @@ static void dpi_display_disable(struct omap_dss_device *dssdev)
 	mutex_unlock(&dpi->lock);
 }
 
-static int dpi_check_timings(struct omap_dss_device *dssdev,
-			     struct drm_display_mode *mode)
-{
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
-	int lck_div, pck_div;
-	unsigned long fck;
-	unsigned long pck;
-	struct dpi_clk_calc_ctx ctx;
-	bool ok;
-
-	if (mode->hdisplay % 8 != 0)
-		return -EINVAL;
-
-	if (mode->clock == 0)
-		return -EINVAL;
-
-	if (dpi->pll) {
-		ok = dpi_pll_clk_calc(dpi, mode->clock * 1000, &ctx);
-		if (!ok)
-			return -EINVAL;
-
-		fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
-	} else {
-		ok = dpi_dss_clk_calc(dpi, mode->clock * 1000, &ctx);
-		if (!ok)
-			return -EINVAL;
-
-		fck = ctx.fck;
-	}
-
-	lck_div = ctx.dispc_cinfo.lck_div;
-	pck_div = ctx.dispc_cinfo.pck_div;
-
-	pck = fck / lck_div / pck_div;
-
-	mode->clock = pck / 1000;
-
-	return 0;
-}
-
-static void dpi_set_timings(struct omap_dss_device *dssdev,
-			    const struct drm_display_mode *mode)
-{
-	struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
-
-	DSSDBG("dpi_set_timings\n");
-
-	mutex_lock(&dpi->lock);
-
-	dpi->pixelclock = mode->clock * 1000;
-
-	mutex_unlock(&dpi->lock);
-}
-
-static const struct omap_dss_device_ops dpi_ops = {
-	.connect = dpi_connect,
-	.disconnect = dpi_disconnect,
-
-	.enable = dpi_display_enable,
-	.disable = dpi_display_disable,
-
-	.check_timings = dpi_check_timings,
-	.set_timings = dpi_set_timings,
+static const struct drm_bridge_funcs dpi_bridge_funcs = {
+	.attach = dpi_bridge_attach,
+	.mode_valid = dpi_bridge_mode_valid,
+	.mode_fixup = dpi_bridge_mode_fixup,
+	.mode_set = dpi_bridge_mode_set,
+	.atomic_enable = dpi_bridge_enable,
+	.atomic_disable = dpi_bridge_disable,
 };
 
+static void dpi_bridge_init(struct dpi_data *dpi)
+{
+	dpi->bridge.funcs = &dpi_bridge_funcs;
+	dpi->bridge.of_node = dpi->pdev->dev.of_node;
+	dpi->bridge.type = DRM_MODE_CONNECTOR_DPI;
+
+	drm_bridge_add(&dpi->bridge);
+}
+
+static void dpi_bridge_cleanup(struct dpi_data *dpi)
+{
+	drm_bridge_remove(&dpi->bridge);
+}
+
+/* -----------------------------------------------------------------------------
+ * Initialisation and Cleanup
+ */
+
 /*
  * Return a hardcoded channel for the DPI output. This should work for
  * current use cases, but this can be later expanded to either resolve
@@ -608,6 +643,8 @@ static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
 	u32 port_num = 0;
 	int r;
 
+	dpi_bridge_init(dpi);
+
 	of_property_read_u32(port, "reg", &port_num);
 	dpi->id = port_num <= 2 ? port_num : 0;
 
@@ -629,10 +666,9 @@ static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
 	out->type = OMAP_DISPLAY_TYPE_DPI;
 	out->dispc_channel = dpi_get_channel(dpi);
 	out->of_port = port_num;
-	out->ops = &dpi_ops;
 	out->owner = THIS_MODULE;
 
-	r = omapdss_device_init_output(out, NULL);
+	r = omapdss_device_init_output(out, &dpi->bridge);
 	if (r < 0)
 		return r;
 
@@ -648,6 +684,8 @@ static void dpi_uninit_output_port(struct device_node *port)
 
 	omapdss_device_unregister(out);
 	omapdss_device_cleanup_output(out);
+
+	dpi_bridge_cleanup(dpi);
 }
 
 /* -----------------------------------------------------------------------------
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 55/60] drm/omap: sdi: Sort includes alphabetically
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (49 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 54/60] drm/omap: dpi: Register a drm_bridge Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 56/60] drm/omap: sdi: Register a drm_bridge Laurent Pinchart
                     ` (5 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

This makes it easier to quickly locate duplicate includes.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/sdi.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/sdi.c b/drivers/gpu/drm/omapdrm/dss/sdi.c
index 2c5eaac9193f..92c2cd2c0889 100644
--- a/drivers/gpu/drm/omapdrm/dss/sdi.c
+++ b/drivers/gpu/drm/omapdrm/dss/sdi.c
@@ -17,17 +17,17 @@
 
 #define DSS_SUBSYS_NAME "SDI"
 
-#include <linux/kernel.h>
 #include <linux/delay.h>
 #include <linux/err.h>
-#include <linux/regulator/consumer.h>
 #include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
 #include <linux/string.h>
-#include <linux/of.h>
 
-#include "omapdss.h"
 #include "dss.h"
+#include "omapdss.h"
 
 struct sdi_device {
 	struct platform_device *pdev;
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 56/60] drm/omap: sdi: Register a drm_bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (50 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 55/60] drm/omap: sdi: Sort includes alphabetically Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 57/60] drm/omap: Simplify connector implementation Laurent Pinchart
                     ` (4 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

In order to integrate with a chain of drm_bridge, the internal SDI
output has to expose its operations through the drm_bridge API.
Register a bridge at initialisation time to do so and remove the
omap_dss_device operations that are now unused.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/sdi.c | 177 +++++++++++++++++++-----------
 1 file changed, 112 insertions(+), 65 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/sdi.c b/drivers/gpu/drm/omapdrm/dss/sdi.c
index 92c2cd2c0889..154d247b672d 100644
--- a/drivers/gpu/drm/omapdrm/dss/sdi.c
+++ b/drivers/gpu/drm/omapdrm/dss/sdi.c
@@ -26,6 +26,8 @@
 #include <linux/regulator/consumer.h>
 #include <linux/string.h>
 
+#include <drm/drm_bridge.h>
+
 #include "dss.h"
 #include "omapdss.h"
 
@@ -41,9 +43,11 @@ struct sdi_device {
 	int datapairs;
 
 	struct omap_dss_device output;
+	struct drm_bridge bridge;
 };
 
-#define dssdev_to_sdi(dssdev) container_of(dssdev, struct sdi_device, output)
+#define drm_bridge_to_sdi(bridge) \
+	container_of(bridge, struct sdi_device, bridge)
 
 struct sdi_clk_calc_ctx {
 	struct sdi_device *sdi;
@@ -129,9 +133,81 @@ static void sdi_config_lcd_manager(struct sdi_device *sdi)
 	dss_mgr_set_lcd_config(&sdi->output, &sdi->mgr_config);
 }
 
-static void sdi_display_enable(struct omap_dss_device *dssdev)
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static int sdi_bridge_attach(struct drm_bridge *bridge, bool create_connector)
 {
-	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
+	struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
+
+	if (create_connector)
+		return -EINVAL;
+
+	return drm_bridge_attach(bridge->encoder, sdi->output.next_bridge,
+				 bridge, false);
+}
+
+static enum drm_mode_status
+sdi_bridge_mode_valid(struct drm_bridge *bridge,
+		      const struct drm_display_mode *mode)
+{
+	struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
+	unsigned long pixelclock = mode->clock * 1000;
+	struct dispc_clock_info dispc_cinfo;
+	unsigned long fck;
+	int ret;
+
+	if (pixelclock == 0)
+		return MODE_NOCLOCK;
+
+	ret = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
+	if (ret < 0)
+		return MODE_CLOCK_RANGE;
+
+	return MODE_OK;
+}
+
+static bool sdi_bridge_mode_fixup(struct drm_bridge *bridge,
+				  const struct drm_display_mode *mode,
+				  struct drm_display_mode *adjusted_mode)
+{
+	struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
+	unsigned long pixelclock = mode->clock * 1000;
+	struct dispc_clock_info dispc_cinfo;
+	unsigned long fck;
+	unsigned long pck;
+	int ret;
+
+	ret = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
+	if (ret < 0)
+		return false;
+
+	pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div;
+
+	if (pck != pixelclock)
+		dev_dbg(&sdi->pdev->dev,
+			"pixel clock adjusted from %lu Hz to %lu Hz\n",
+			pixelclock, pck);
+
+	adjusted_mode->clock = pck / 1000;
+
+	return true;
+}
+
+static void sdi_bridge_mode_set(struct drm_bridge *bridge,
+				const struct drm_display_mode *mode,
+				const struct drm_display_mode *adjusted_mode)
+{
+	struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
+
+	sdi->pixelclock = adjusted_mode->clock * 1000;
+}
+
+static void sdi_bridge_enable(struct drm_bridge *bridge,
+			      struct drm_atomic_state *state)
+{
+	struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
 	struct dispc_clock_info dispc_cinfo;
 	unsigned long fck;
 	int r;
@@ -192,9 +268,10 @@ static void sdi_display_enable(struct omap_dss_device *dssdev)
 	regulator_disable(sdi->vdds_sdi_reg);
 }
 
-static void sdi_display_disable(struct omap_dss_device *dssdev)
+static void sdi_bridge_disable(struct drm_bridge *bridge,
+			       struct drm_atomic_state *state)
 {
-	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
+	struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
 
 	dss_mgr_disable(&sdi->output);
 
@@ -205,71 +282,40 @@ static void sdi_display_disable(struct omap_dss_device *dssdev)
 	regulator_disable(sdi->vdds_sdi_reg);
 }
 
-static void sdi_set_timings(struct omap_dss_device *dssdev,
-			    const struct drm_display_mode *mode)
-{
-	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
-
-	sdi->pixelclock = mode->clock * 1000;
-}
-
-static int sdi_check_timings(struct omap_dss_device *dssdev,
-			     struct drm_display_mode *mode)
-{
-	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
-	struct dispc_clock_info dispc_cinfo;
-	unsigned long pixelclock = mode->clock * 1000;
-	unsigned long fck;
-	unsigned long pck;
-	int r;
-
-	if (pixelclock == 0)
-		return -EINVAL;
-
-	r = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
-	if (r)
-		return r;
-
-	pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div;
-
-	if (pck != pixelclock) {
-		DSSWARN("Pixel clock adjusted from %lu Hz to %lu Hz\n",
-			pixelclock, pck);
-
-		mode->clock = pck / 1000;
-	}
-
-	return 0;
-}
-
-static int sdi_connect(struct omap_dss_device *src,
-		       struct omap_dss_device *dst)
-{
-	return omapdss_device_connect(dst->dss, dst, dst->next);
-}
-
-static void sdi_disconnect(struct omap_dss_device *src,
-			   struct omap_dss_device *dst)
-{
-	omapdss_device_disconnect(dst, dst->next);
-}
-
-static const struct omap_dss_device_ops sdi_ops = {
-	.connect = sdi_connect,
-	.disconnect = sdi_disconnect,
-
-	.enable = sdi_display_enable,
-	.disable = sdi_display_disable,
-
-	.check_timings = sdi_check_timings,
-	.set_timings = sdi_set_timings,
+static const struct drm_bridge_funcs sdi_bridge_funcs = {
+	.attach = sdi_bridge_attach,
+	.mode_valid = sdi_bridge_mode_valid,
+	.mode_fixup = sdi_bridge_mode_fixup,
+	.mode_set = sdi_bridge_mode_set,
+	.atomic_enable = sdi_bridge_enable,
+	.atomic_disable = sdi_bridge_disable,
 };
 
+static void sdi_bridge_init(struct sdi_device *sdi)
+{
+	sdi->bridge.funcs = &sdi_bridge_funcs;
+	sdi->bridge.of_node = sdi->pdev->dev.of_node;
+	sdi->bridge.type = DRM_MODE_CONNECTOR_LVDS;
+
+	drm_bridge_add(&sdi->bridge);
+}
+
+static void sdi_bridge_cleanup(struct sdi_device *sdi)
+{
+	drm_bridge_remove(&sdi->bridge);
+}
+
+/* -----------------------------------------------------------------------------
+ * Initialisation and Cleanup
+ */
+
 static int sdi_init_output(struct sdi_device *sdi)
 {
 	struct omap_dss_device *out = &sdi->output;
 	int r;
 
+	sdi_bridge_init(sdi);
+
 	out->dev = &sdi->pdev->dev;
 	out->id = OMAP_DSS_OUTPUT_SDI;
 	out->type = OMAP_DISPLAY_TYPE_SDI;
@@ -277,12 +323,11 @@ static int sdi_init_output(struct sdi_device *sdi)
 	out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
 	/* We have SDI only on OMAP3, where it's on port 1 */
 	out->of_port = 1;
-	out->ops = &sdi_ops;
 	out->owner = THIS_MODULE;
 	out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE	/* 15.5.9.1.2 */
 		       | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
 
-	r = omapdss_device_init_output(out, NULL);
+	r = omapdss_device_init_output(out, &sdi->bridge);
 	if (r < 0)
 		return r;
 
@@ -295,6 +340,8 @@ static void sdi_uninit_output(struct sdi_device *sdi)
 {
 	omapdss_device_unregister(&sdi->output);
 	omapdss_device_cleanup_output(&sdi->output);
+
+	sdi_bridge_cleanup(sdi);
 }
 
 int sdi_init_port(struct dss_device *dss, struct platform_device *pdev,
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 57/60] drm/omap: Simplify connector implementation
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (51 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 56/60] drm/omap: sdi: Register a drm_bridge Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 58/60] drm/omap: dss: Remove unused omap_dss_device operations Laurent Pinchart
                     ` (3 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Now that the omap_connector is used for DSI only we can simplify its
implementation.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/omap_connector.c | 31 ++----------------------
 1 file changed, 2 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/omap_connector.c b/drivers/gpu/drm/omapdrm/omap_connector.c
index 4b8a99be2086..b8f3e246b01f 100644
--- a/drivers/gpu/drm/omapdrm/omap_connector.c
+++ b/drivers/gpu/drm/omapdrm/omap_connector.c
@@ -35,22 +35,7 @@ struct omap_connector {
 static enum drm_connector_status omap_connector_detect(
 		struct drm_connector *connector, bool force)
 {
-	enum drm_connector_status status;
-
-	switch (connector->connector_type) {
-	case DRM_MODE_CONNECTOR_DPI:
-	case DRM_MODE_CONNECTOR_LVDS:
-	case DRM_MODE_CONNECTOR_DSI:
-		status = connector_status_connected;
-		break;
-	default:
-		status = connector_status_unknown;
-		break;
-	}
-
-	VERB("%s: %d (force=%d)", connector->name, status, force);
-
-	return status;
+	return connector_status_connected;
 }
 
 static void omap_connector_destroy(struct drm_connector *connector)
@@ -149,18 +134,6 @@ static const struct drm_connector_helper_funcs omap_connector_helper_funcs = {
 	.mode_valid = omap_connector_mode_valid,
 };
 
-static int omap_connector_get_type(struct omap_dss_device *output)
-{
-	struct omap_dss_device *display;
-	enum omap_display_type type;
-
-	display = omapdss_display_get(output);
-	type = display->type;
-	omapdss_device_put(display);
-
-	return omapdss_device_connector_type(type);
-}
-
 /* initialize connector */
 struct drm_connector *omap_connector_init(struct drm_device *dev,
 					  struct omap_dss_device *output,
@@ -182,7 +155,7 @@ struct drm_connector *omap_connector_init(struct drm_device *dev,
 	connector->doublescan_allowed = 0;
 
 	drm_connector_init(dev, connector, &omap_connector_funcs,
-			   omap_connector_get_type(output));
+			   DRM_MODE_CONNECTOR_DSI);
 	drm_connector_helper_add(connector, &omap_connector_helper_funcs);
 
 	return connector;
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 58/60] drm/omap: dss: Remove unused omap_dss_device operations
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (52 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 57/60] drm/omap: Simplify connector implementation Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 59/60] drm/omap: dss: Inline the omapdss_display_get() function Laurent Pinchart
                     ` (2 subsequent siblings)
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The omap_dss_device .pre_enable(), .post_disable() and .set_timings()
are not used anymore. Remove them.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/base.c     | 26 ---------------
 drivers/gpu/drm/omapdrm/dss/omapdss.h  |  6 ----
 drivers/gpu/drm/omapdrm/omap_encoder.c | 44 +++-----------------------
 3 files changed, 5 insertions(+), 71 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c
index 2db3bd2f19db..58b333c6b036 100644
--- a/drivers/gpu/drm/omapdrm/dss/base.c
+++ b/drivers/gpu/drm/omapdrm/dss/base.c
@@ -234,18 +234,6 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
 }
 EXPORT_SYMBOL_GPL(omapdss_device_disconnect);
 
-void omapdss_device_pre_enable(struct omap_dss_device *dssdev)
-{
-	if (!dssdev)
-		return;
-
-	omapdss_device_pre_enable(dssdev->next);
-
-	if (dssdev->ops && dssdev->ops->pre_enable)
-		dssdev->ops->pre_enable(dssdev);
-}
-EXPORT_SYMBOL_GPL(omapdss_device_pre_enable);
-
 void omapdss_device_enable(struct omap_dss_device *dssdev)
 {
 	if (!dssdev)
@@ -272,20 +260,6 @@ void omapdss_device_disable(struct omap_dss_device *dssdev)
 }
 EXPORT_SYMBOL_GPL(omapdss_device_disable);
 
-void omapdss_device_post_disable(struct omap_dss_device *dssdev)
-{
-	if (!dssdev)
-		return;
-
-	if (dssdev->ops && dssdev->ops->post_disable)
-		dssdev->ops->post_disable(dssdev);
-
-	omapdss_device_post_disable(dssdev->next);
-
-	dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
-}
-EXPORT_SYMBOL_GPL(omapdss_device_post_disable);
-
 unsigned int omapdss_device_connector_type(enum omap_display_type type)
 {
 	switch (type) {
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 24b421f67406..8d4fed58d97c 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -353,15 +353,11 @@ struct omap_dss_device_ops {
 	void (*disconnect)(struct omap_dss_device *dssdev,
 			struct omap_dss_device *dst);
 
-	void (*pre_enable)(struct omap_dss_device *dssdev);
 	void (*enable)(struct omap_dss_device *dssdev);
 	void (*disable)(struct omap_dss_device *dssdev);
-	void (*post_disable)(struct omap_dss_device *dssdev);
 
 	int (*check_timings)(struct omap_dss_device *dssdev,
 			     struct drm_display_mode *mode);
-	void (*set_timings)(struct omap_dss_device *dssdev,
-			    const struct drm_display_mode *mode);
 
 	int (*get_modes)(struct omap_dss_device *dssdev,
 			 struct drm_connector *connector);
@@ -461,10 +457,8 @@ int omapdss_device_connect(struct dss_device *dss,
 			   struct omap_dss_device *dst);
 void omapdss_device_disconnect(struct omap_dss_device *src,
 			       struct omap_dss_device *dst);
-void omapdss_device_pre_enable(struct omap_dss_device *dssdev);
 void omapdss_device_enable(struct omap_dss_device *dssdev);
 void omapdss_device_disable(struct omap_dss_device *dssdev);
-void omapdss_device_post_disable(struct omap_dss_device *dssdev);
 unsigned int omapdss_device_connector_type(enum omap_display_type type);
 
 int omap_dss_get_num_overlay_managers(void);
diff --git a/drivers/gpu/drm/omapdrm/omap_encoder.c b/drivers/gpu/drm/omapdrm/omap_encoder.c
index 8af889f0af06..82096dce106a 100644
--- a/drivers/gpu/drm/omapdrm/omap_encoder.c
+++ b/drivers/gpu/drm/omapdrm/omap_encoder.c
@@ -122,13 +122,8 @@ static void omap_encoder_mode_set(struct drm_encoder *encoder,
 	bus_flags = connector->display_info.bus_flags;
 	omap_encoder_update_videomode_flags(&vm, bus_flags);
 
-	/* Set timings for all devices in the display pipeline. */
+	/* Set timings for the dss manager. */
 	dss_mgr_set_timings(output, &vm);
-
-	for (dssdev = output; dssdev; dssdev = dssdev->next) {
-		if (dssdev->ops && dssdev->ops->set_timings)
-			dssdev->ops->set_timings(dssdev, adjusted_mode);
-	}
 }
 
 static void omap_encoder_disable(struct drm_encoder *encoder)
@@ -141,26 +136,10 @@ static void omap_encoder_disable(struct drm_encoder *encoder)
 
 	/*
 	 * Disable the chain of external devices, starting at the one at the
-	 * internal encoder's output.
+	 * internal encoder's output. This is used for DSI outputs only, as
+	 * dssdev->next is NULL for all other outputs.
 	 */
 	omapdss_device_disable(dssdev->next);
-
-	/*
-	 * Disable the internal encoder. This will disable the DSS output. The
-	 * DSI is treated as an exception as DSI pipelines still use the legacy
-	 * flow where the pipeline output controls the encoder.
-	 */
-	if (dssdev->type != OMAP_DISPLAY_TYPE_DSI) {
-		if (dssdev->ops && dssdev->ops->disable)
-			dssdev->ops->disable(dssdev);
-		dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
-	}
-
-	/*
-	 * Perform the post-disable operations on the chain of external devices
-	 * to complete the display pipeline disable.
-	 */
-	omapdss_device_post_disable(dssdev->next);
 }
 
 static void omap_encoder_enable(struct drm_encoder *encoder)
@@ -171,23 +150,10 @@ static void omap_encoder_enable(struct drm_encoder *encoder)
 
 	dev_dbg(dev->dev, "enable(%s)\n", dssdev->name);
 
-	/* Prepare the chain of external devices for pipeline enable. */
-	omapdss_device_pre_enable(dssdev->next);
-
-	/*
-	 * Enable the internal encoder. This will enable the DSS output. The
-	 * DSI is treated as an exception as DSI pipelines still use the legacy
-	 * flow where the pipeline output controls the encoder.
-	 */
-	if (dssdev->type != OMAP_DISPLAY_TYPE_DSI) {
-		if (dssdev->ops && dssdev->ops->enable)
-			dssdev->ops->enable(dssdev);
-		dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
-	}
-
 	/*
 	 * Enable the chain of external devices, starting at the one at the
-	 * internal encoder's output.
+	 * internal encoder's output. This is used for DSI outputs only, as
+	 * dssdev->next is NULL for all other outputs.
 	 */
 	omapdss_device_enable(dssdev->next);
 }
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 59/60] drm/omap: dss: Inline the omapdss_display_get() function
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (53 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 58/60] drm/omap: dss: Remove unused omap_dss_device operations Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-07 18:19   ` [PATCH 60/60] drm/omap: dss: Remove unused omapdss_of_find_connected_device() function Laurent Pinchart
  2019-07-09 13:35   ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver to simple-bridge Andrzej Hajda
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Inline the omapdss_display_get() in its only caller to simplify the
code.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/display.c | 9 ---------
 drivers/gpu/drm/omapdrm/dss/omapdss.h | 1 -
 drivers/gpu/drm/omapdrm/omap_drv.c    | 7 ++++---
 3 files changed, 4 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/omapdrm/dss/display.c b/drivers/gpu/drm/omapdrm/dss/display.c
index e93f61a567a8..babe597106cb 100644
--- a/drivers/gpu/drm/omapdrm/dss/display.c
+++ b/drivers/gpu/drm/omapdrm/dss/display.c
@@ -51,15 +51,6 @@ void omapdss_display_init(struct omap_dss_device *dssdev)
 }
 EXPORT_SYMBOL_GPL(omapdss_display_init);
 
-struct omap_dss_device *omapdss_display_get(struct omap_dss_device *output)
-{
-	while (output->next)
-		output = output->next;
-
-	return omapdss_device_get(output);
-}
-EXPORT_SYMBOL_GPL(omapdss_display_get);
-
 int omapdss_display_get_modes(struct drm_connector *connector,
 			      const struct videomode *vm)
 {
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 8d4fed58d97c..7466953de46d 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -443,7 +443,6 @@ static inline bool omapdss_is_initialized(void)
 }
 
 void omapdss_display_init(struct omap_dss_device *dssdev);
-struct omap_dss_device *omapdss_display_get(struct omap_dss_device *output);
 int omapdss_display_get_modes(struct drm_connector *connector,
 			      const struct videomode *vm);
 
diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
index 89a3633b97ad..bc092e9aa5a2 100644
--- a/drivers/gpu/drm/omapdrm/omap_drv.c
+++ b/drivers/gpu/drm/omapdrm/omap_drv.c
@@ -211,11 +211,12 @@ static int omap_display_id(struct omap_dss_device *output)
 	struct device_node *node = NULL;
 
 	if (output->next) {
-		struct omap_dss_device *display;
+		struct omap_dss_device *display = output;
+
+		while (display->next)
+			display = display->next;
 
-		display = omapdss_display_get(output);
 		node = display->dev->of_node;
-		omapdss_device_put(display);
 	} else if (output->bridge) {
 		struct drm_bridge *bridge = output->bridge;
 
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* [PATCH 60/60] drm/omap: dss: Remove unused omapdss_of_find_connected_device() function
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (54 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 59/60] drm/omap: dss: Inline the omapdss_display_get() function Laurent Pinchart
@ 2019-07-07 18:19   ` Laurent Pinchart
  2019-07-09 13:35   ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver to simple-bridge Andrzej Hajda
  56 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-07 18:19 UTC (permalink / raw)
  To: dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

The omapdss_of_find_connected_device() function isn't used anymore,
remove it.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/omapdrm/dss/Makefile  |  2 +-
 drivers/gpu/drm/omapdrm/dss/dss-of.c  | 28 ---------------------------
 drivers/gpu/drm/omapdrm/dss/omapdss.h |  3 ---
 3 files changed, 1 insertion(+), 32 deletions(-)
 delete mode 100644 drivers/gpu/drm/omapdrm/dss/dss-of.c

diff --git a/drivers/gpu/drm/omapdrm/dss/Makefile b/drivers/gpu/drm/omapdrm/dss/Makefile
index 904101c5e79d..39e83d6fcb08 100644
--- a/drivers/gpu/drm/omapdrm/dss/Makefile
+++ b/drivers/gpu/drm/omapdrm/dss/Makefile
@@ -2,7 +2,7 @@
 obj-$(CONFIG_OMAP2_DSS_INIT) += omapdss-boot-init.o
 
 obj-$(CONFIG_OMAP_DSS_BASE) += omapdss-base.o
-omapdss-base-y := base.o display.o dss-of.o output.o
+omapdss-base-y := base.o display.o output.o
 
 obj-$(CONFIG_OMAP2_DSS) += omapdss.o
 # Core DSS files
diff --git a/drivers/gpu/drm/omapdrm/dss/dss-of.c b/drivers/gpu/drm/omapdrm/dss/dss-of.c
deleted file mode 100644
index b7981f3b80ad..000000000000
--- a/drivers/gpu/drm/omapdrm/dss/dss-of.c
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- */
-
-#include <linux/err.h>
-#include <linux/of.h>
-#include <linux/of_graph.h>
-
-#include "omapdss.h"
-
-struct omap_dss_device *
-omapdss_of_find_connected_device(struct device_node *node, unsigned int port)
-{
-	struct device_node *remote_node;
-	struct omap_dss_device *dssdev;
-
-	remote_node = of_graph_get_remote_node(node, port, 0);
-	if (!remote_node)
-		return NULL;
-
-	dssdev = omapdss_find_device_by_node(remote_node);
-	of_node_put(remote_node);
-
-	return dssdev ? dssdev : ERR_PTR(-EPROBE_DEFER);
-}
-EXPORT_SYMBOL_GPL(omapdss_of_find_connected_device);
diff --git a/drivers/gpu/drm/omapdrm/dss/omapdss.h b/drivers/gpu/drm/omapdrm/dss/omapdss.h
index 7466953de46d..62642fbc2ce9 100644
--- a/drivers/gpu/drm/omapdrm/dss/omapdss.h
+++ b/drivers/gpu/drm/omapdrm/dss/omapdss.h
@@ -483,9 +483,6 @@ static inline bool omapdss_device_is_enabled(struct omap_dss_device *dssdev)
 	return dssdev->state == OMAP_DSS_DISPLAY_ACTIVE;
 }
 
-struct omap_dss_device *
-omapdss_of_find_connected_device(struct device_node *node, unsigned int port);
-
 enum dss_writeback_channel {
 	DSS_WB_LCD1_MGR =	0,
 	DSS_WB_LCD2_MGR =	1,
-- 
Regards,

Laurent Pinchart

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 166+ messages in thread

* Re: [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
  2019-07-07 18:14   ` Laurent Pinchart
@ 2019-07-07 18:21     ` Ilia Mirkin
  0 siblings, 0 replies; 166+ messages in thread
From: Ilia Mirkin @ 2019-07-07 18:21 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Bartlomiej Zolnierkiewicz, Maxime Ripard, Sebastian Reichel,
	dri-devel, Tomi Valkeinen, Sean Paul

On Sun, Jul 7, 2019 at 2:15 PM Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Sorry, forgot to CC Bartlomiej on this patch.
>
> On Sun, Jul 07, 2019 at 09:07:54PM +0300, Laurent Pinchart wrote:
> > The hdmi_avi_infoframe_init() never needs to return an error, change its
> > return type to void.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/drm_edid.c | 5 +----
> >  drivers/video/hdmi.c       | 9 ++-------
> >  include/linux/hdmi.h       | 2 +-
> >  3 files changed, 4 insertions(+), 12 deletions(-)
> >
> > diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
> > index b939bc28d886..54fb7cf11d1a 100644
> > --- a/drivers/video/hdmi.c
> > +++ b/drivers/video/hdmi.c
> > @@ -56,15 +56,13 @@ static void hdmi_infoframe_set_checksum(void *buffer, size_t size)
> >   *
> >   * Returns 0 on success or a negative error code on failure.

Probably want to adjust this text too, then?

[I have no opinion on whether this patch is good or bad, just happened
to notice the inconsistency.]

Cheers,

  -ilia

> >   */
> > -int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
> > +void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
> >  {
> >       memset(frame, 0, sizeof(*frame));
> >
> >       frame->type = HDMI_INFOFRAME_TYPE_AVI;
> >       frame->version = 2;
> >       frame->length = HDMI_AVI_INFOFRAME_SIZE;
> > -
> > -     return 0;
> >  }
> >  EXPORT_SYMBOL(hdmi_avi_infoframe_init);
> >
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel
  2019-07-07 18:18   ` [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel Laurent Pinchart
@ 2019-07-08 18:51     ` Sam Ravnborg
  2019-07-09  0:56       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-08 18:51 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

Good to move omapdrm to a more standard way to do things.

> new file mode 100644
> index 000000000000..d8a8c3a3a8c5
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-lg-lb035q02.c
> @@ -0,0 +1,235 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * LG.Philips LB035Q02 LCD Panel Driver

Looks like a typo. As far as I know LG and Philips are not the same.
But I can see this is used in several places, so I need to check up on
actual status here and driver is likely OK.
Google... this is fine. Some joint venture in 2001.

> + * Based on the omapdrm-specific panel-lg-lb035q02 driver
Will we have two drivers with the same name, or are this above already
disabled from the build?

> +	unsigned int i;
index to arrays are default "int" IIRC.
Not that it matters but noticed it.

> +	int ret;
> +
> +	for (i = 0; i < ARRAY_SIZE(init_data); ++i) {
> +		ret = lb035q02_write(lcd, init_data[i].index,
> +				     init_data[i].value);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_display_mode lb035q02_mode = {
> +	.clock = 6500,
> +	.hdisplay = 320,
> +	.hsync_start = 320 + 20,
> +	.hsync_end = 320 + 20 + 2,
> +	.htotal = 320 + 20 + 2 + 68,
> +	.vdisplay = 240,
> +	.vsync_start = 240 + 4,
> +	.vsync_end = 240 + 4 + 2,
> +	.vtotal = 240 + 4 + 2 + 18,
> +	.vrefresh = 60,
> +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> +};
We already specify all the timing details.
Consider to use display_mode to specify the width/height too.
So the panel specificatiosn are hardcoded only in one place.

> +
> +static int lb035q02_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_duplicate(panel->drm, &lb035q02_mode);
> +	if (!mode)
> +		return -ENOMEM;
> +
> +	drm_mode_set_name(mode);
> +	drm_mode_probed_add(connector, mode);
> +
> +	connector->display_info.width_mm = 70;
> +	connector->display_info.height_mm = 53;
So we avoid hardcoding height/width here, but do it with the timing
above.
> +	/*
> +	 * FIXME: According to the datasheet pixel data is sampled on the
> +	 * rising edge of the clock, but the code running on the Gumstix Overo
> +	 * Palo35 indicates sampling on the negative edge. This should be
> +	 * tested on a real device.
> +	 */
> +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> +					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
> +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> +
> +	return 1;
> +}
> +
> +static const struct drm_panel_funcs lb035q02_funcs = {
> +	.disable = lb035q02_disable,
> +	.enable = lb035q02_enable,
> +	.get_modes = lb035q02_get_modes,
> +};
> +
> +static int lb035q02_probe(struct spi_device *spi)
> +{
> +	struct lb035q02_device *lcd;
> +	int ret;
> +
> +	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> +	if (lcd == NULL)
> +		return -ENOMEM;
> +
> +	spi_set_drvdata(spi, lcd);
> +	lcd->spi = spi;
> +
> +	lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->enable_gpio)) {
> +		dev_err(&spi->dev, "failed to parse enable gpio\n");
> +		return PTR_ERR(lcd->enable_gpio);
> +	}
> +
> +	ret = lb035q02_init(lcd);
> +	if (ret < 0)
> +		return ret;
> +
> +	drm_panel_init(&lcd->panel);
> +	lcd->panel.dev = &lcd->spi->dev;
> +	lcd->panel.funcs = &lb035q02_funcs;
> +
> +	return drm_panel_add(&lcd->panel);
> +}
> +
> +static int lb035q02_remove(struct spi_device *spi)
> +{
> +	struct lb035q02_device *lcd = spi_get_drvdata(spi);
> +
> +	drm_panel_remove(&lcd->panel);
> +	lb035q02_disable(&lcd->panel);
Use drm_panel_disable() so the driver will benefit when we move more
functionality to the drm_panel_disable() function.

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id lb035q02_of_match[] = {
> +	{ .compatible = "lgphilips,lb035q02", },
> +	{},
Some drivers use { /* sentinel */ }, to document this is on purpose the
last entry.

> +};
> +
> +MODULE_DEVICE_TABLE(of, lb035q02_of_match);
> +
> +static struct spi_driver lb035q02_driver = {
> +	.probe		= lb035q02_probe,
> +	.remove		= lb035q02_remove,
> +	.driver		= {
> +		.name	= "panel-lg-lb035q02",
> +		.of_match_table = lb035q02_of_match,
> +	},
> +};
> +
> +module_spi_driver(lb035q02_driver);
> +
> +MODULE_ALIAS("spi:lgphilips,lb035q02");
> +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
> +MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver");
> +MODULE_LICENSE("GPL");
This should be "GPL v2" if I read https://www.kernel.org/doc/html/latest/process/license-rules.html
correct. See "MODULE_LICENSE" table.

With the above comments addressed/considered:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix
  2019-07-07 18:18   ` [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix Laurent Pinchart
@ 2019-07-08 19:00     ` Rob Herring
  2019-07-09  1:00       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Rob Herring @ 2019-07-08 19:00 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	dri-devel, Tomi Valkeinen, Sean Paul

On Sun, Jul 7, 2019 at 12:25 PM Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> The 'toppoly' vendor prefix is in use and refers to TPO, whose DT vendor
> prefix is already defined as 'tpo'. Add 'toppoly' as an alternative and
> document it as legacy.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> index 2514463f2c63..d78527eb8254 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> @@ -867,6 +867,8 @@ patternProperties:
>      description: Tecon Microprocessor Technologies, LLC.
>    "^topeet,.*":
>      description: Topeet
> +  "^toppoly,.*":
> +    description: TPO (legacy prefix, see 'tpo')

Add 'deprecated: true' also. That's a new property in json-schema
draft8. It's not used for anything yet other than documentation.

Rob
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel
  2019-07-07 18:18   ` [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel Laurent Pinchart
@ 2019-07-08 19:10     ` Sam Ravnborg
  2019-07-08 19:26       ` Sam Ravnborg
  2019-08-08 15:17       ` Laurent Pinchart
  0 siblings, 2 replies; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-08 19:10 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

Thanks for keeping me busy :-)

On Sun, Jul 07, 2019 at 09:18:57PM +0300, Laurent Pinchart wrote:
> This panel is used on the Zoom2/3/3630 SDP boards.
This information may be good to have in the Kconfig help entry too.

Maybe tell in the changelog where this code originates from.

> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
> @@ -0,0 +1,249 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * NEC NL8048HL11 Panel Driver
> + *
> + * Copyright (C) 2019 Texas Instruments Incorporated
> + *
> + * Based on the omapdrm-specific panel-nec-nl8048hl11 driver
> + *
> + * Copyright (C) 2010 Texas Instruments Incorporated
> + * Author: Erik Gilling <konkers@android.com>
> + */
No added copyright from you?
(Apply to all panel drivers)

> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/spi/spi.h>
> +
> +#include <drm/drm_connector.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_panel.h>

Good to see panel drivers that are NOT using drmP.h :-)

> +
> +struct nl8048_device {
> +	struct drm_panel panel;
> +
> +	struct spi_device *spi;
> +	struct gpio_desc *reset_gpio;
> +};
Naming bikeshedding. This is a nl8048_panel, not a device.


> +
> +static const struct drm_display_mode nl8048_mode = {
> +	/*  NEC PIX Clock Ratings MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz */
> +	.clock	= 23800,
> +	.hdisplay = 800,
> +	.hsync_start = 800 + 6,
> +	.hsync_end = 800 + 6 + 1,
> +	.htotal = 800 + 6 + 1 + 4,
> +	.vdisplay = 480,
> +	.vsync_start = 480 + 3,
> +	.vsync_end = 480 + 3 + 1,
> +	.vtotal = 480 + 3 + 1 + 4,
> +	.vrefresh = 60,
> +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> +};
Same comment as for previous patch on height/width.

> +
> +static int nl8048_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_duplicate(panel->drm, &nl8048_mode);
> +	if (!mode)
> +		return -ENOMEM;
> +
> +	drm_mode_set_name(mode);
> +	drm_mode_probed_add(connector, mode);
> +
> +	connector->display_info.width_mm = 89;
> +	connector->display_info.height_mm = 53;
> +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> +					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
> +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> +
> +	return 1;
> +}
This should be moved to drm_panle_helper.c (which we do not have yet.
But the function is used in some many drivers it makes sense.
On my TODO list.
I have yet to find a good way to specify the bus_flags though.

> +#ifdef CONFIG_PM_SLEEP

Use __maybe_unused, and loose the #ifdef
And why does this panel need suspend/resume?
The panel is supposed to be turned off in disable()

To simple solution would be to move the write to "2" to
the enable and disable functions.
And then this driver is alinged with the rest.


> +static int nl8048_suspend(struct device *dev)
> +{
> +	struct nl8048_device *lcd = dev_get_drvdata(dev);
> +
> +	nl8048_write(lcd, 2, 0x01);
> +	msleep(40);
This sleep puzzle me. What is the puspose?
And the write is to a display that is already reset??
> +
> +	return 0;
> +}
> +
> +static int nl8048_resume(struct device *dev)
> +{
> +	struct nl8048_device *lcd = dev_get_drvdata(dev);
> +
> +	/* Reinitialize the panel. */
> +	spi_setup(lcd->spi);
> +	nl8048_write(lcd, 2, 0x00);
> +	nl8048_init(lcd);
> +
> +	return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(nl8048_pm_ops, nl8048_suspend, nl8048_resume);
> +#endif
> +
> +static int nl8048_probe(struct spi_device *spi)
> +{
> +	struct nl8048_device *lcd;
> +	int ret;
> +
> +	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> +	if (lcd == NULL)
> +		return -ENOMEM;
> +
> +	spi_set_drvdata(spi, lcd);
> +	lcd->spi = spi;
> +
> +	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->reset_gpio)) {
> +		dev_err(&spi->dev, "failed to parse reset gpio\n");
> +		return PTR_ERR(lcd->reset_gpio);
> +	}
> +
> +	spi->mode = SPI_MODE_0;
> +	spi->bits_per_word = 32;
> +
> +	ret = spi_setup(spi);
> +	if (ret < 0) {
> +		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = nl8048_init(lcd);
> +	if (ret < 0)
> +		return ret;
> +
> +	drm_panel_init(&lcd->panel);
> +	lcd->panel.dev = &lcd->spi->dev;
> +	lcd->panel.funcs = &nl8048_funcs;
> +
> +	return drm_panel_add(&lcd->panel);
> +}
> +
> +static int nl8048_remove(struct spi_device *spi)
> +{
> +	struct nl8048_device *lcd = spi_get_drvdata(spi);
> +
> +	drm_panel_remove(&lcd->panel);
> +	nl8048_disable(&lcd->panel);
Use drm_panel_disable() - same comment as other panel driver.

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id nl8048_of_match[] = {
> +	{ .compatible = "nec,nl8048hl11", },
> +	{},
{ /* sentinel */ },?

> +};
> +
> +MODULE_DEVICE_TABLE(of, nl8048_of_match);
> +
> +static struct spi_driver nl8048_driver = {
> +	.probe		= nl8048_probe,
> +	.remove		= nl8048_remove,
> +	.driver		= {
> +		.name	= "panel-nec-nl8048hl11",
> +#ifdef CONFIG_PM_SLEEP
> +		.pm	= &nl8048_pm_ops,
> +#endif
> +		.of_match_table = nl8048_of_match,
> +	},
> +};
> +
> +module_spi_driver(nl8048_driver);
> +
> +MODULE_ALIAS("spi:nec,nl8048hl11");
> +MODULE_AUTHOR("Erik Gilling <konkers@android.com>");
> +MODULE_DESCRIPTION("NEC-NL8048HL11 Driver");
> +MODULE_LICENSE("GPL");
"GPL v2"?

The suspend/resume thing needs to be sorted out before I can add my r-b
on this.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel
  2019-07-08 19:10     ` Sam Ravnborg
@ 2019-07-08 19:26       ` Sam Ravnborg
  2019-08-08 15:17       ` Laurent Pinchart
  1 sibling, 0 replies; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-08 19:26 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

> 
> Use __maybe_unused, and loose the #ifdef
> And why does this panel need suspend/resume?
> The panel is supposed to be turned off in disable()

Sorry - the panel is supposed to be turned off in unprepare.
disable() is mainly for backlight and such.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel
  2019-07-07 18:18   ` [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel Laurent Pinchart
@ 2019-07-08 19:44     ` Sam Ravnborg
  2019-07-08 19:47       ` Sam Ravnborg
  2019-08-08 15:31       ` Laurent Pinchart
  0 siblings, 2 replies; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-08 19:44 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

Third panel driver in line for review.
Review comments that are duplicates from the first two will have only a
brief remark - if any.

On Sun, Jul 07, 2019 at 09:18:58PM +0300, Laurent Pinchart wrote:
> This panel is used on the SDP3430.
Add a little more context and put it in Kconfig help.
Maybe this is the TI board, and maybe it is something else.

> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/panel/Kconfig                 |   7 +
>  drivers/gpu/drm/panel/Makefile                |   1 +
>  .../gpu/drm/panel/panel-sharp-ls037v7dw01.c   | 231 ++++++++++++++++++
>  3 files changed, 239 insertions(+)
>  create mode 100644 drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
> 
> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index da613c04b835..04fd152efe4c 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -271,6 +271,13 @@ config DRM_PANEL_SHARP_LS043T1LE01
>  	  Say Y here if you want to enable support for Sharp LS043T1LE01 qHD
>  	  (540x960) DSI panel as found on the Qualcomm APQ8074 Dragonboard
>  
> +config DRM_PANEL_SHARP_LS037V7DW01
> +	tristate "Sharp LS037V7DW01 VGA LCD panel"
> +	depends on GPIOLIB && OF && REGULATOR
> +	help
> +	  Say Y here if you want to enable support for Sharp LS037V7DW01 VGA
> +	  (480x640) LCD panel.
> +
Alphabetical order, so it comes before DRM_PANEL_SHARP_LS043T1LE01

>  config DRM_PANEL_SITRONIX_ST7701
>  	tristate "Sitronix ST7701 panel driver"
>  	depends on OF
> diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> index e81ed1535024..12dcd76eb87c 100644
> --- a/drivers/gpu/drm/panel/Makefile
> +++ b/drivers/gpu/drm/panel/Makefile
> @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o
>  obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o
>  obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o
>  obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
> +obj-$(CONFIG_DRM_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
>  obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o
And here it is right.

> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
> @@ -0,0 +1,231 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sharp LS037V7DW01 LCD Panel Driver
> + *
> + * Copyright (C) 2019 Texas Instruments Incorporated
> + *
> + * Based on the omapdrm-specific panel-sharp-ls037v7dw01 driver
> + *
> + * Copyright (C) 2013 Texas Instruments Incorporated
> + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
Add your copyright?

> +struct ls037v7dw01_device {
> +	struct drm_panel panel;
> +	struct platform_device *pdev;
> +
> +	struct regulator *vcc;
The property is named envdd - should they use the same name?

> +	struct gpio_desc *resb_gpio;	/* low = reset active min 20 us */
> +	struct gpio_desc *ini_gpio;	/* high = power on */
> +	struct gpio_desc *mo_gpio;	/* low = 480x640, high = 240x320 */
> +	struct gpio_desc *lr_gpio;	/* high = conventional horizontal scanning */
> +	struct gpio_desc *ud_gpio;	/* high = conventional vertical scanning */
> +};
device versus panel, but bikeshedding, so feel free to ignore.

> +
> +static int ls037v7dw01_disable(struct drm_panel *panel)
> +{
> +	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
> +
> +	gpiod_set_value_cansleep(lcd->ini_gpio, 0);
> +	gpiod_set_value_cansleep(lcd->resb_gpio, 0);
> +
> +	/* Wait at least 5 vsyncs after disabling the LCD. */
> +	msleep(100);
> +
> +	return 0;
> +}
> +
> +static int ls037v7dw01_unprepare(struct drm_panel *panel)
> +{
> +	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
> +
> +	if (lcd->vcc)
> +		regulator_disable(lcd->vcc);
Why is the if (lcd-vcc) needed?
If I read the probe code correct then we either get a regulator or we
error out.

Same goes for all other checks of lcd->vcc

> +static const struct drm_display_mode ls037v7dw01_mode = {
> +	.clock = 19200,
> +	.hdisplay = 480,
> +	.hsync_start = 480 + 1,
> +	.hsync_end = 480 + 1 + 2,
> +	.htotal = 480 + 1 + 2 + 28,
> +	.vdisplay = 640,
> +	.vsync_start = 640 + 1,
> +	.vsync_end = 640 + 1 + 1,
> +	.vtotal = 640 + 1 + 1 + 1,
> +	.vrefresh = 58,
> +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> +};
> +
> +static int ls037v7dw01_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_duplicate(panel->drm, &ls037v7dw01_mode);
> +	if (!mode)
> +		return -ENOMEM;
> +
> +	drm_mode_set_name(mode);
> +	drm_mode_probed_add(connector, mode);
> +
> +	connector->display_info.width_mm = 56;
> +	connector->display_info.height_mm = 75;
> +	/*
> +	 * FIXME: According to the datasheet pixel data is sampled on the
> +	 * rising edge of the clock, but the code running on the SDP3430
> +	 * indicates sampling on the negative edge. This should be tested on a
> +	 * real device.
> +	 */
> +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> +					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
> +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> +
> +	return 1;
> +}
> +
> +static const struct drm_panel_funcs ls037v7dw01_funcs = {
> +	.disable = ls037v7dw01_disable,
> +	.unprepare = ls037v7dw01_unprepare,
> +	.prepare = ls037v7dw01_prepare,
> +	.enable = ls037v7dw01_enable,
> +	.get_modes = ls037v7dw01_get_modes,
> +};
> +
> +static int ls037v7dw01_probe(struct platform_device *pdev)
> +{
> +	struct ls037v7dw01_device *lcd;
> +
> +	lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL);
> +	if (lcd == NULL)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, lcd);
> +	lcd->pdev = pdev;
> +
> +	lcd->vcc = devm_regulator_get(&pdev->dev, "envdd");
> +	if (IS_ERR(lcd->vcc)) {
> +		dev_err(&pdev->dev, "failed to get regulator\n");
> +		return PTR_ERR(lcd->vcc);
> +	}
> +
> +	lcd->ini_gpio = devm_gpiod_get_index(&pdev->dev, "enable", 0,
> +					    GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->ini_gpio)) {
> +		dev_err(&pdev->dev, "failed to get enable gpio\n");
> +		return PTR_ERR(lcd->ini_gpio);
> +	}
I fail to see why the _index() variant is used here.
But then I did not check the binding, so it may originate from that.
Same goes for ireset gpio

> +
> +	lcd->resb_gpio = devm_gpiod_get_index(&pdev->dev, "reset", 0,
> +					     GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->resb_gpio)) {
> +		dev_err(&pdev->dev, "failed to get reset gpio\n");
> +		return PTR_ERR(lcd->resb_gpio);
> +	}
> +
> +	lcd->mo_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 0,
> +					   GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->mo_gpio)) {
> +		dev_err(&pdev->dev, "failed to get mode[0] gpio\n");
> +		return PTR_ERR(lcd->mo_gpio);
> +	}
> +
> +	lcd->lr_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 1,
> +					   GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->lr_gpio)) {
> +		dev_err(&pdev->dev, "failed to get mode[1] gpio\n");
> +		return PTR_ERR(lcd->lr_gpio);
> +	}
> +
> +	lcd->ud_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 2,
> +					   GPIOD_OUT_LOW);
> +	if (IS_ERR(lcd->ud_gpio)) {
> +		dev_err(&pdev->dev, "failed to get mode[2] gpio\n");
> +		return PTR_ERR(lcd->ud_gpio);
> +	}
Do we set mo, lr ,ud gpio when we call devm_gpiod_get, or are they
unused?

> +
> +	drm_panel_init(&lcd->panel);
> +	lcd->panel.dev = &pdev->dev;
> +	lcd->panel.funcs = &ls037v7dw01_funcs;
> +
> +	return drm_panel_add(&lcd->panel);
> +}
> +
> +static int ls037v7dw01_remove(struct platform_device *pdev)
> +{
> +	struct ls037v7dw01_device *lcd = platform_get_drvdata(pdev);
> +
> +	drm_panel_remove(&lcd->panel);
> +	ls037v7dw01_disable(&lcd->panel);
> +	ls037v7dw01_unprepare(&lcd->panel);
Use drm_panel_disable(), drm_panel_unprepare()

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ls037v7dw01_of_match[] = {
> +	{ .compatible = "sharp,ls037v7dw01", },
> +	{},
{ /* sentinel */ },

> +};
> +
> +MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match);
> +
> +static struct platform_driver ls037v7dw01_driver = {
> +	.probe		= ls037v7dw01_probe,
> +	.remove		= __exit_p(ls037v7dw01_remove),
> +	.driver		= {
> +		.name = "panel-sharp-ls037v7dw01",
> +		.of_match_table = ls037v7dw01_of_match,
> +	},
> +};
> +
> +module_platform_driver(ls037v7dw01_driver);
> +
> +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
> +MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
> +MODULE_LICENSE("GPL");
"GPL v2"?

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel
  2019-07-08 19:44     ` Sam Ravnborg
@ 2019-07-08 19:47       ` Sam Ravnborg
  2019-08-08 15:31       ` Laurent Pinchart
  1 sibling, 0 replies; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-08 19:47 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

> > +
> > +MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match);
> > +
> > +static struct platform_driver ls037v7dw01_driver = {
> > +	.probe		= ls037v7dw01_probe,
> > +	.remove		= __exit_p(ls037v7dw01_remove),

I hope _exit_p() is not needed.
No other panel drivers use it as far as I could see.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel
  2019-07-08 18:51     ` Sam Ravnborg
@ 2019-07-09  0:56       ` Laurent Pinchart
  2019-07-09  5:47         ` Sam Ravnborg
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-09  0:56 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Sam,

On Mon, Jul 08, 2019 at 08:51:29PM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> Good to move omapdrm to a more standard way to do things.

I hope it will help defining the next step for the standard ;-)

> > new file mode 100644
> > index 000000000000..d8a8c3a3a8c5
> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-lg-lb035q02.c
> > @@ -0,0 +1,235 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * LG.Philips LB035Q02 LCD Panel Driver
> 
> Looks like a typo. As far as I know LG and Philips are not the same.
> But I can see this is used in several places, so I need to check up on
> actual status here and driver is likely OK.
> Google... this is fine. Some joint venture in 2001.
> 
> > + * Based on the omapdrm-specific panel-lg-lb035q02 driver
> 
> Will we have two drivers with the same name, or are this above already
> disabled from the build?

The omapdrm-specific driver is called panel-lgphilips-lb035q02.c. I'll
update the comment.

> > +	unsigned int i;
> 
> index to arrays are default "int" IIRC.
> Not that it matters but noticed it.

Are they ? I've always advocated for unsigned indexes to use unsigned
int.

> > +	int ret;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(init_data); ++i) {
> > +		ret = lb035q02_write(lcd, init_data[i].index,
> > +				     init_data[i].value);
> > +		if (ret < 0)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct drm_display_mode lb035q02_mode = {
> > +	.clock = 6500,
> > +	.hdisplay = 320,
> > +	.hsync_start = 320 + 20,
> > +	.hsync_end = 320 + 20 + 2,
> > +	.htotal = 320 + 20 + 2 + 68,
> > +	.vdisplay = 240,
> > +	.vsync_start = 240 + 4,
> > +	.vsync_end = 240 + 4 + 2,
> > +	.vtotal = 240 + 4 + 2 + 18,
> > +	.vrefresh = 60,
> > +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> > +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> > +};
> 
> We already specify all the timing details.
> Consider to use display_mode to specify the width/height too.
> So the panel specificatiosn are hardcoded only in one place.

I didn't know drm_display_mode had width_mm and height_mm fields. I'll
fix this.

> > +
> > +static int lb035q02_get_modes(struct drm_panel *panel)
> > +{
> > +	struct drm_connector *connector = panel->connector;
> > +	struct drm_display_mode *mode;
> > +
> > +	mode = drm_mode_duplicate(panel->drm, &lb035q02_mode);
> > +	if (!mode)
> > +		return -ENOMEM;
> > +
> > +	drm_mode_set_name(mode);
> > +	drm_mode_probed_add(connector, mode);
> > +
> > +	connector->display_info.width_mm = 70;
> > +	connector->display_info.height_mm = 53;
> 
> So we avoid hardcoding height/width here, but do it with the timing
> above.
> 
> > +	/*
> > +	 * FIXME: According to the datasheet pixel data is sampled on the
> > +	 * rising edge of the clock, but the code running on the Gumstix Overo
> > +	 * Palo35 indicates sampling on the negative edge. This should be
> > +	 * tested on a real device.
> > +	 */
> > +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> > +					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
> > +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> > +
> > +	return 1;
> > +}
> > +
> > +static const struct drm_panel_funcs lb035q02_funcs = {
> > +	.disable = lb035q02_disable,
> > +	.enable = lb035q02_enable,
> > +	.get_modes = lb035q02_get_modes,
> > +};
> > +
> > +static int lb035q02_probe(struct spi_device *spi)
> > +{
> > +	struct lb035q02_device *lcd;
> > +	int ret;
> > +
> > +	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> > +	if (lcd == NULL)
> > +		return -ENOMEM;
> > +
> > +	spi_set_drvdata(spi, lcd);
> > +	lcd->spi = spi;
> > +
> > +	lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->enable_gpio)) {
> > +		dev_err(&spi->dev, "failed to parse enable gpio\n");
> > +		return PTR_ERR(lcd->enable_gpio);
> > +	}
> > +
> > +	ret = lb035q02_init(lcd);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	drm_panel_init(&lcd->panel);
> > +	lcd->panel.dev = &lcd->spi->dev;
> > +	lcd->panel.funcs = &lb035q02_funcs;
> > +
> > +	return drm_panel_add(&lcd->panel);
> > +}
> > +
> > +static int lb035q02_remove(struct spi_device *spi)
> > +{
> > +	struct lb035q02_device *lcd = spi_get_drvdata(spi);
> > +
> > +	drm_panel_remove(&lcd->panel);
> > +	lb035q02_disable(&lcd->panel);
> 
> Use drm_panel_disable() so the driver will benefit when we move more
> functionality to the drm_panel_disable() function.

Will do.

> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id lb035q02_of_match[] = {
> > +	{ .compatible = "lgphilips,lb035q02", },
> > +	{},
> 
> Some drivers use { /* sentinel */ }, to document this is on purpose the
> last entry.

I don't mind either way so I'll change it.

> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, lb035q02_of_match);
> > +
> > +static struct spi_driver lb035q02_driver = {
> > +	.probe		= lb035q02_probe,
> > +	.remove		= lb035q02_remove,
> > +	.driver		= {
> > +		.name	= "panel-lg-lb035q02",
> > +		.of_match_table = lb035q02_of_match,
> > +	},
> > +};
> > +
> > +module_spi_driver(lb035q02_driver);
> > +
> > +MODULE_ALIAS("spi:lgphilips,lb035q02");
> > +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
> > +MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver");
> > +MODULE_LICENSE("GPL");
> 
> This should be "GPL v2" if I read https://www.kernel.org/doc/html/latest/process/license-rules.html
> correct. See "MODULE_LICENSE" table.

According to that table, "GPL v2" is defined as "Same as “GPL”. It
exists for historic reasons.". My understanding is that "GPL v2" exists
for historical reasons and should not be used in new code.

> With the above comments addressed/considered:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

Thank you.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix
  2019-07-08 19:00     ` Rob Herring
@ 2019-07-09  1:00       ` Laurent Pinchart
  2019-07-09  1:35         ` Rob Herring
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-07-09  1:00 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	dri-devel, Tomi Valkeinen, Sean Paul

Hi Rob,

On Mon, Jul 08, 2019 at 01:00:35PM -0600, Rob Herring wrote:
> On Sun, Jul 7, 2019 at 12:25 PM Laurent Pinchart wrote:
> >
> > The 'toppoly' vendor prefix is in use and refers to TPO, whose DT vendor
> > prefix is already defined as 'tpo'. Add 'toppoly' as an alternative and
> > document it as legacy.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
> >  1 file changed, 2 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > index 2514463f2c63..d78527eb8254 100644
> > --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > @@ -867,6 +867,8 @@ patternProperties:
> >      description: Tecon Microprocessor Technologies, LLC.
> >    "^topeet,.*":
> >      description: Topeet
> > +  "^toppoly,.*":
> > +    description: TPO (legacy prefix, see 'tpo')
> 
> Add 'deprecated: true' also. That's a new property in json-schema
> draft8. It's not used for anything yet other than documentation.

Thank you for the pointer.

By the way this series conflicts with your patches that move all panel
bindings to yaml. I'll rebase it on top if yours gets merged first.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix
  2019-07-09  1:00       ` Laurent Pinchart
@ 2019-07-09  1:35         ` Rob Herring
  0 siblings, 0 replies; 166+ messages in thread
From: Rob Herring @ 2019-07-09  1:35 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	dri-devel, Tomi Valkeinen, Sean Paul

On Mon, Jul 8, 2019 at 7:00 PM Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Rob,
>
> On Mon, Jul 08, 2019 at 01:00:35PM -0600, Rob Herring wrote:
> > On Sun, Jul 7, 2019 at 12:25 PM Laurent Pinchart wrote:
> > >
> > > The 'toppoly' vendor prefix is in use and refers to TPO, whose DT vendor
> > > prefix is already defined as 'tpo'. Add 'toppoly' as an alternative and
> > > document it as legacy.
> > >
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > >  Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
> > >  1 file changed, 2 insertions(+)
> > >
> > > diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > > index 2514463f2c63..d78527eb8254 100644
> > > --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > > +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > > @@ -867,6 +867,8 @@ patternProperties:
> > >      description: Tecon Microprocessor Technologies, LLC.
> > >    "^topeet,.*":
> > >      description: Topeet
> > > +  "^toppoly,.*":
> > > +    description: TPO (legacy prefix, see 'tpo')
> >
> > Add 'deprecated: true' also. That's a new property in json-schema
> > draft8. It's not used for anything yet other than documentation.
>
> Thank you for the pointer.
>
> By the way this series conflicts with your patches that move all panel
> bindings to yaml. I'll rebase it on top if yours gets merged first.

I committed the series today, but I don't see what conflicts though.

Rob
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel
  2019-07-09  0:56       ` Laurent Pinchart
@ 2019-07-09  5:47         ` Sam Ravnborg
  0 siblings, 0 replies; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-09  5:47 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

> 
> > > +	unsigned int i;
> > 
> > index to arrays are default "int" IIRC.
> > Not that it matters but noticed it.
> 
> Are they ? I've always advocated for unsigned indexes to use unsigned
> int.

I did not dig up anything authorative - but found this:
https://stackoverflow.com/questions/8111357/type-of-array-index-in-c

There is some confusion betwwen the type of array and the type of the
index.
But the part that looks to answer the questions say that index can be
negative, so the integral type is default int.
Again, nothing to worry about, as code wokrs and unsigen int is used for
index in many places.

> > > +MODULE_LICENSE("GPL");
> > 
> > This should be "GPL v2" if I read https://www.kernel.org/doc/html/latest/process/license-rules.html
> > correct. See "MODULE_LICENSE" table.
> 
> According to that table, "GPL v2" is defined as "Same as “GPL”. It
> exists for historic reasons.". My understanding is that "GPL v2" exists
> for historical reasons and should not be used in new code.
Re-reading the link you are right. module license is to be specified as
"GPL" and then one has to visit the file.
So ignore that comment in following reviews.
Seems simple to remember, will keep in mind for future reviews.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks
  2019-07-07 18:07 ` [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks Laurent Pinchart
@ 2019-07-09 13:20   ` Andrzej Hajda
  2019-07-10 15:41     ` Daniel Vetter
  2019-07-10 15:59   ` Ville Syrjälä
  1 sibling, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 13:20 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:07, Laurent Pinchart wrote:
> The drm_display_info structure contains many fields related to HDMI
> sinks, but none that identifies if a sink compliant with CEA-861 (EDID)
> shall be treated as an HDMI sink or a DVI sink. Add such a flag, and
> populate it according to section 8.3.3 ("DVI/HDMI Device
> Discrimination") of the HDMI v1.3 specification.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


It looks like it can replace drm_detect_hdmi_monitor usage in most cases.

Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej



> ---
>  drivers/gpu/drm/drm_edid.c  | 3 +++
>  include/drm/drm_connector.h | 5 +++++
>  2 files changed, 8 insertions(+)
>
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> index 82a4ceed3fcf..d2e7a5334c3f 100644
> --- a/drivers/gpu/drm/drm_edid.c
> +++ b/drivers/gpu/drm/drm_edid.c
> @@ -4559,6 +4559,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
>  	struct drm_display_info *info = &connector->display_info;
>  	u8 len = cea_db_payload_len(db);
>  
> +	info->is_hdmi = true;
> +
>  	if (len >= 6)
>  		info->dvi_dual = db[6] & 1;
>  	if (len >= 7)
> @@ -4627,6 +4629,7 @@ drm_reset_display_info(struct drm_connector *connector)
>  	info->cea_rev = 0;
>  	info->max_tmds_clock = 0;
>  	info->dvi_dual = false;
> +	info->is_hdmi = false;
>  	info->has_hdmi_infoframe = false;
>  	info->rgb_quant_range_selectable = false;
>  	memset(&info->hdmi, 0, sizeof(info->hdmi));
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index ca745d9feaf5..e80ca0d149e5 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -426,6 +426,11 @@ struct drm_display_info {
>  	 */
>  	bool dvi_dual;
>  
> +	/**
> +	 * @is_hdmi: True if the sink is an HDMI device.
> +	 */
> +	bool is_hdmi;
> +
>  	/**
>  	 * @has_hdmi_infoframe: Does the sink support the HDMI infoframe?
>  	 */


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
  2019-07-07 18:07 ` [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void Laurent Pinchart
  2019-07-07 18:14   ` Laurent Pinchart
@ 2019-07-09 13:22   ` Andrzej Hajda
  1 sibling, 0 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 13:22 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:07, Laurent Pinchart wrote:
> The hdmi_avi_infoframe_init() never needs to return an error, change its
> return type to void.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 03/60] drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge
  2019-07-07 18:07 ` [PATCH 03/60] drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge Laurent Pinchart
@ 2019-07-09 13:34   ` Andrzej Hajda
  0 siblings, 0 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 13:34 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:07, Laurent Pinchart wrote:
> The dumb-vga-dac driver is a simple DRM bridge driver for simple VGA
> DACs that don't require configuration. Other non-VGA bridges fall in a
> similar category, and would benefit from a common driver. Prepare for
> this by renaming the internal symbols from dumb-vga-dac to
> simple-bridge.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej



> ---
>  drivers/gpu/drm/bridge/dumb-vga-dac.c | 149 +++++++++++++-------------
>  1 file changed, 75 insertions(+), 74 deletions(-)
>
> diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c
> index d32885b906ae..d46e461ae039 100644
> --- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
> +++ b/drivers/gpu/drm/bridge/dumb-vga-dac.c
> @@ -16,7 +16,7 @@
>  #include <drm/drm_print.h>
>  #include <drm/drm_probe_helper.h>
>  
> -struct dumb_vga {
> +struct simple_bridge {
>  	struct drm_bridge	bridge;
>  	struct drm_connector	connector;
>  
> @@ -24,28 +24,28 @@ struct dumb_vga {
>  	struct regulator	*vdd;
>  };
>  
> -static inline struct dumb_vga *
> -drm_bridge_to_dumb_vga(struct drm_bridge *bridge)
> +static inline struct simple_bridge *
> +drm_bridge_to_simple_bridge(struct drm_bridge *bridge)
>  {
> -	return container_of(bridge, struct dumb_vga, bridge);
> +	return container_of(bridge, struct simple_bridge, bridge);
>  }
>  
> -static inline struct dumb_vga *
> -drm_connector_to_dumb_vga(struct drm_connector *connector)
> +static inline struct simple_bridge *
> +drm_connector_to_simple_bridge(struct drm_connector *connector)
>  {
> -	return container_of(connector, struct dumb_vga, connector);
> +	return container_of(connector, struct simple_bridge, connector);
>  }
>  
> -static int dumb_vga_get_modes(struct drm_connector *connector)
> +static int simple_bridge_get_modes(struct drm_connector *connector)
>  {
> -	struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
> +	struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
>  	struct edid *edid;
>  	int ret;
>  
> -	if (IS_ERR(vga->ddc))
> +	if (IS_ERR(sbridge->ddc))
>  		goto fallback;
>  
> -	edid = drm_get_edid(connector, vga->ddc);
> +	edid = drm_get_edid(connector, sbridge->ddc);
>  	if (!edid) {
>  		DRM_INFO("EDID readout failed, falling back to standard modes\n");
>  		goto fallback;
> @@ -69,14 +69,14 @@ static int dumb_vga_get_modes(struct drm_connector *connector)
>  	return ret;
>  }
>  
> -static const struct drm_connector_helper_funcs dumb_vga_con_helper_funcs = {
> -	.get_modes	= dumb_vga_get_modes,
> +static const struct drm_connector_helper_funcs simple_bridge_con_helper_funcs = {
> +	.get_modes	= simple_bridge_get_modes,
>  };
>  
>  static enum drm_connector_status
> -dumb_vga_connector_detect(struct drm_connector *connector, bool force)
> +simple_bridge_connector_detect(struct drm_connector *connector, bool force)
>  {
> -	struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
> +	struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
>  
>  	/*
>  	 * Even if we have an I2C bus, we can't assume that the cable
> @@ -84,14 +84,14 @@ dumb_vga_connector_detect(struct drm_connector *connector, bool force)
>  	 * wire the DDC pins, or the I2C bus might not be working at
>  	 * all.
>  	 */
> -	if (!IS_ERR(vga->ddc) && drm_probe_ddc(vga->ddc))
> +	if (!IS_ERR(sbridge->ddc) && drm_probe_ddc(sbridge->ddc))
>  		return connector_status_connected;
>  
>  	return connector_status_unknown;
>  }
>  
> -static const struct drm_connector_funcs dumb_vga_con_funcs = {
> -	.detect			= dumb_vga_connector_detect,
> +static const struct drm_connector_funcs simple_bridge_con_funcs = {
> +	.detect			= simple_bridge_connector_detect,
>  	.fill_modes		= drm_helper_probe_single_connector_modes,
>  	.destroy		= drm_connector_cleanup,
>  	.reset			= drm_atomic_helper_connector_reset,
> @@ -99,9 +99,9 @@ static const struct drm_connector_funcs dumb_vga_con_funcs = {
>  	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int dumb_vga_attach(struct drm_bridge *bridge)
> +static int simple_bridge_attach(struct drm_bridge *bridge)
>  {
> -	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
> +	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
>  	int ret;
>  
>  	if (!bridge->encoder) {
> @@ -109,48 +109,49 @@ static int dumb_vga_attach(struct drm_bridge *bridge)
>  		return -ENODEV;
>  	}
>  
> -	drm_connector_helper_add(&vga->connector,
> -				 &dumb_vga_con_helper_funcs);
> -	ret = drm_connector_init(bridge->dev, &vga->connector,
> -				 &dumb_vga_con_funcs, DRM_MODE_CONNECTOR_VGA);
> +	drm_connector_helper_add(&sbridge->connector,
> +				 &simple_bridge_con_helper_funcs);
> +	ret = drm_connector_init(bridge->dev, &sbridge->connector,
> +				 &simple_bridge_con_funcs,
> +				 DRM_MODE_CONNECTOR_VGA);
>  	if (ret) {
>  		DRM_ERROR("Failed to initialize connector\n");
>  		return ret;
>  	}
>  
> -	drm_connector_attach_encoder(&vga->connector,
> +	drm_connector_attach_encoder(&sbridge->connector,
>  					  bridge->encoder);
>  
>  	return 0;
>  }
>  
> -static void dumb_vga_enable(struct drm_bridge *bridge)
> +static void simple_bridge_enable(struct drm_bridge *bridge)
>  {
> -	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
> +	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
>  	int ret = 0;
>  
> -	if (vga->vdd)
> -		ret = regulator_enable(vga->vdd);
> +	if (sbridge->vdd)
> +		ret = regulator_enable(sbridge->vdd);
>  
>  	if (ret)
>  		DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
>  }
>  
> -static void dumb_vga_disable(struct drm_bridge *bridge)
> +static void simple_bridge_disable(struct drm_bridge *bridge)
>  {
> -	struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
> +	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
>  
> -	if (vga->vdd)
> -		regulator_disable(vga->vdd);
> +	if (sbridge->vdd)
> +		regulator_disable(sbridge->vdd);
>  }
>  
> -static const struct drm_bridge_funcs dumb_vga_bridge_funcs = {
> -	.attach		= dumb_vga_attach,
> -	.enable		= dumb_vga_enable,
> -	.disable	= dumb_vga_disable,
> +static const struct drm_bridge_funcs simple_bridge_bridge_funcs = {
> +	.attach		= simple_bridge_attach,
> +	.enable		= simple_bridge_enable,
> +	.disable	= simple_bridge_disable,
>  };
>  
> -static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
> +static struct i2c_adapter *simple_bridge_retrieve_ddc(struct device *dev)
>  {
>  	struct device_node *phandle, *remote;
>  	struct i2c_adapter *ddc;
> @@ -172,52 +173,52 @@ static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
>  	return ddc;
>  }
>  
> -static int dumb_vga_probe(struct platform_device *pdev)
> +static int simple_bridge_probe(struct platform_device *pdev)
>  {
> -	struct dumb_vga *vga;
> +	struct simple_bridge *sbridge;
>  
> -	vga = devm_kzalloc(&pdev->dev, sizeof(*vga), GFP_KERNEL);
> -	if (!vga)
> +	sbridge = devm_kzalloc(&pdev->dev, sizeof(*sbridge), GFP_KERNEL);
> +	if (!sbridge)
>  		return -ENOMEM;
> -	platform_set_drvdata(pdev, vga);
> +	platform_set_drvdata(pdev, sbridge);
>  
> -	vga->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
> -	if (IS_ERR(vga->vdd)) {
> -		int ret = PTR_ERR(vga->vdd);
> +	sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
> +	if (IS_ERR(sbridge->vdd)) {
> +		int ret = PTR_ERR(sbridge->vdd);
>  		if (ret == -EPROBE_DEFER)
>  			return -EPROBE_DEFER;
> -		vga->vdd = NULL;
> +		sbridge->vdd = NULL;
>  		dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
>  	}
>  
> -	vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
> -	if (IS_ERR(vga->ddc)) {
> -		if (PTR_ERR(vga->ddc) == -ENODEV) {
> +	sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
> +	if (IS_ERR(sbridge->ddc)) {
> +		if (PTR_ERR(sbridge->ddc) == -ENODEV) {
>  			dev_dbg(&pdev->dev,
>  				"No i2c bus specified. Disabling EDID readout\n");
>  		} else {
>  			dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
> -			return PTR_ERR(vga->ddc);
> +			return PTR_ERR(sbridge->ddc);
>  		}
>  	}
>  
> -	vga->bridge.funcs = &dumb_vga_bridge_funcs;
> -	vga->bridge.of_node = pdev->dev.of_node;
> -	vga->bridge.timings = of_device_get_match_data(&pdev->dev);
> +	sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
> +	sbridge->bridge.of_node = pdev->dev.of_node;
> +	sbridge->bridge.timings = of_device_get_match_data(&pdev->dev);
>  
> -	drm_bridge_add(&vga->bridge);
> +	drm_bridge_add(&sbridge->bridge);
>  
>  	return 0;
>  }
>  
> -static int dumb_vga_remove(struct platform_device *pdev)
> +static int simple_bridge_remove(struct platform_device *pdev)
>  {
> -	struct dumb_vga *vga = platform_get_drvdata(pdev);
> +	struct simple_bridge *sbridge = platform_get_drvdata(pdev);
>  
> -	drm_bridge_remove(&vga->bridge);
> +	drm_bridge_remove(&sbridge->bridge);
>  
> -	if (!IS_ERR(vga->ddc))
> -		i2c_put_adapter(vga->ddc);
> +	if (!IS_ERR(sbridge->ddc))
> +		i2c_put_adapter(sbridge->ddc);
>  
>  	return 0;
>  }
> @@ -228,7 +229,7 @@ static int dumb_vga_remove(struct platform_device *pdev)
>   * NOTE: the ADV7123EP seems to have other timings and need a new timings
>   * set if used.
>   */
> -static const struct drm_bridge_timings default_dac_timings = {
> +static const struct drm_bridge_timings default_bridge_timings = {
>  	/* Timing specifications, datasheet page 7 */
>  	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
>  	.setup_time_ps = 500,
> @@ -239,7 +240,7 @@ static const struct drm_bridge_timings default_dac_timings = {
>   * Information taken from the THS8134, THS8134A, THS8134B datasheet named
>   * "SLVS205D", dated May 1990, revised March 2000.
>   */
> -static const struct drm_bridge_timings ti_ths8134_dac_timings = {
> +static const struct drm_bridge_timings ti_ths8134_bridge_timings = {
>  	/* From timing diagram, datasheet page 9 */
>  	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
>  	/* From datasheet, page 12 */
> @@ -252,7 +253,7 @@ static const struct drm_bridge_timings ti_ths8134_dac_timings = {
>   * Information taken from the THS8135 datasheet named "SLAS343B", dated
>   * May 2001, revised April 2013.
>   */
> -static const struct drm_bridge_timings ti_ths8135_dac_timings = {
> +static const struct drm_bridge_timings ti_ths8135_bridge_timings = {
>  	/* From timing diagram, datasheet page 14 */
>  	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
>  	/* From datasheet, page 16 */
> @@ -260,37 +261,37 @@ static const struct drm_bridge_timings ti_ths8135_dac_timings = {
>  	.hold_time_ps = 500,
>  };
>  
> -static const struct of_device_id dumb_vga_match[] = {
> +static const struct of_device_id simple_bridge_match[] = {
>  	{
>  		.compatible = "dumb-vga-dac",
>  		.data = NULL,
>  	},
>  	{
>  		.compatible = "adi,adv7123",
> -		.data = &default_dac_timings,
> +		.data = &default_bridge_timings,
>  	},
>  	{
>  		.compatible = "ti,ths8135",
> -		.data = &ti_ths8135_dac_timings,
> +		.data = &ti_ths8135_bridge_timings,
>  	},
>  	{
>  		.compatible = "ti,ths8134",
> -		.data = &ti_ths8134_dac_timings,
> +		.data = &ti_ths8134_bridge_timings,
>  	},
>  	{},
>  };
> -MODULE_DEVICE_TABLE(of, dumb_vga_match);
> +MODULE_DEVICE_TABLE(of, simple_bridge_match);
>  
> -static struct platform_driver dumb_vga_driver = {
> -	.probe	= dumb_vga_probe,
> -	.remove	= dumb_vga_remove,
> +static struct platform_driver simple_bridge_driver = {
> +	.probe	= simple_bridge_probe,
> +	.remove	= simple_bridge_remove,
>  	.driver		= {
>  		.name		= "dumb-vga-dac",
> -		.of_match_table	= dumb_vga_match,
> +		.of_match_table	= simple_bridge_match,
>  	},
>  };
> -module_platform_driver(dumb_vga_driver);
> +module_platform_driver(simple_bridge_driver);
>  
>  MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
> -MODULE_DESCRIPTION("Dumb VGA DAC bridge driver");
> +MODULE_DESCRIPTION("Simple DRM bridge driver");
>  MODULE_LICENSE("GPL");


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver to simple-bridge
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
                     ` (55 preceding siblings ...)
  2019-07-07 18:19   ` [PATCH 60/60] drm/omap: dss: Remove unused omapdss_of_find_connected_device() function Laurent Pinchart
@ 2019-07-09 13:35   ` Andrzej Hajda
  56 siblings, 0 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 13:35 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Simon Horman, Maxime Ripard, Sebastian Reichel, Russell King,
	Chen-Yu Tsai, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:18, Laurent Pinchart wrote:
> The dumb-vga-dac driver can support simple DRM bridges without being
> limited to VGA DACs. Rename it to simple-bridge.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej


> ---
>  arch/arm/configs/davinci_all_defconfig           |  2 +-
>  arch/arm/configs/integrator_defconfig            |  2 +-
>  arch/arm/configs/multi_v7_defconfig              |  2 +-
>  arch/arm/configs/shmobile_defconfig              |  2 +-
>  arch/arm/configs/sunxi_defconfig                 |  2 +-
>  arch/arm/configs/versatile_defconfig             |  2 +-
>  drivers/gpu/drm/bridge/Kconfig                   | 16 ++++++++--------
>  drivers/gpu/drm/bridge/Makefile                  |  2 +-
>  .../bridge/{dumb-vga-dac.c => simple-bridge.c}   |  2 +-
>  9 files changed, 16 insertions(+), 16 deletions(-)
>  rename drivers/gpu/drm/bridge/{dumb-vga-dac.c => simple-bridge.c} (99%)
>
> diff --git a/arch/arm/configs/davinci_all_defconfig b/arch/arm/configs/davinci_all_defconfig
> index 4a8cad4d3707..f422d34a4e4e 100644
> --- a/arch/arm/configs/davinci_all_defconfig
> +++ b/arch/arm/configs/davinci_all_defconfig
> @@ -154,7 +154,7 @@ CONFIG_VIDEO_TVP514X=m
>  CONFIG_VIDEO_ADV7343=m
>  CONFIG_DRM=m
>  CONFIG_DRM_TILCDC=m
> -CONFIG_DRM_DUMB_VGA_DAC=m
> +CONFIG_DRM_SIMPLE_BRIDGE=m
>  CONFIG_DRM_TINYDRM=m
>  CONFIG_TINYDRM_ST7586=m
>  CONFIG_FB=y
> diff --git a/arch/arm/configs/integrator_defconfig b/arch/arm/configs/integrator_defconfig
> index 747550c7af2f..4d265a689655 100644
> --- a/arch/arm/configs/integrator_defconfig
> +++ b/arch/arm/configs/integrator_defconfig
> @@ -55,7 +55,7 @@ CONFIG_SMC91X=y
>  # CONFIG_KEYBOARD_ATKBD is not set
>  # CONFIG_SERIO_SERPORT is not set
>  CONFIG_DRM=y
> -CONFIG_DRM_DUMB_VGA_DAC=y
> +CONFIG_DRM_SIMPLE_BRIDGE=y
>  CONFIG_DRM_PL111=y
>  CONFIG_FB_MODE_HELPERS=y
>  CONFIG_FB_MATROX=y
> diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
> index 6b748f214eae..634e029a5736 100644
> --- a/arch/arm/configs/multi_v7_defconfig
> +++ b/arch/arm/configs/multi_v7_defconfig
> @@ -643,11 +643,11 @@ CONFIG_DRM_PANEL_ORISETECH_OTM8009A=m
>  CONFIG_DRM_PANEL_RAYDIUM_RM68200=m
>  CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03=m
>  CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0=m
> -CONFIG_DRM_DUMB_VGA_DAC=m
>  CONFIG_DRM_NXP_PTN3460=m
>  CONFIG_DRM_PARADE_PS8622=m
>  CONFIG_DRM_SII902X=m
>  CONFIG_DRM_SII9234=m
> +CONFIG_DRM_SIMPLE_BRIDGE=m
>  CONFIG_DRM_TOSHIBA_TC358764=m
>  CONFIG_DRM_I2C_ADV7511=m
>  CONFIG_DRM_I2C_ADV7511_AUDIO=y
> diff --git a/arch/arm/configs/shmobile_defconfig b/arch/arm/configs/shmobile_defconfig
> index eb02ba9ec6e6..771074e399fb 100644
> --- a/arch/arm/configs/shmobile_defconfig
> +++ b/arch/arm/configs/shmobile_defconfig
> @@ -125,8 +125,8 @@ CONFIG_VIDEO_ADV7604=y
>  CONFIG_VIDEO_ML86V7667=y
>  CONFIG_DRM=y
>  CONFIG_DRM_RCAR_DU=y
> -CONFIG_DRM_DUMB_VGA_DAC=y
>  CONFIG_DRM_SII902X=y
> +CONFIG_DRM_SIMPLE_BRIDGE=y
>  CONFIG_DRM_I2C_ADV7511=y
>  CONFIG_DRM_I2C_ADV7511_AUDIO=y
>  CONFIG_FB_SH_MOBILE_LCDC=y
> diff --git a/arch/arm/configs/sunxi_defconfig b/arch/arm/configs/sunxi_defconfig
> index df433abfcb02..19cccae84a19 100644
> --- a/arch/arm/configs/sunxi_defconfig
> +++ b/arch/arm/configs/sunxi_defconfig
> @@ -99,7 +99,7 @@ CONFIG_RC_DEVICES=y
>  CONFIG_IR_SUNXI=y
>  CONFIG_DRM=y
>  CONFIG_DRM_SUN4I=y
> -CONFIG_DRM_DUMB_VGA_DAC=y
> +CONFIG_DRM_SIMPLE_BRIDGE=y
>  CONFIG_FB_SIMPLE=y
>  CONFIG_SOUND=y
>  CONFIG_SND=y
> diff --git a/arch/arm/configs/versatile_defconfig b/arch/arm/configs/versatile_defconfig
> index 5282324c7cef..afc44c99e7f9 100644
> --- a/arch/arm/configs/versatile_defconfig
> +++ b/arch/arm/configs/versatile_defconfig
> @@ -59,7 +59,7 @@ CONFIG_GPIO_PL061=y
>  CONFIG_DRM=y
>  CONFIG_DRM_PANEL_ARM_VERSATILE=y
>  CONFIG_DRM_PANEL_SIMPLE=y
> -CONFIG_DRM_DUMB_VGA_DAC=y
> +CONFIG_DRM_SIMPLE_BRIDGE=y
>  CONFIG_DRM_PL111=y
>  CONFIG_FB_MODE_HELPERS=y
>  CONFIG_BACKLIGHT_LCD_SUPPORT=y
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index ee777469293a..a78392e2dbb9 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -37,14 +37,6 @@ config DRM_CDNS_DSI
>  	  Support Cadence DPI to DSI bridge. This is an internal
>  	  bridge and is meant to be directly embedded in a SoC.
>  
> -config DRM_DUMB_VGA_DAC
> -	tristate "Dumb VGA DAC Bridge support"
> -	depends on OF
> -	select DRM_KMS_HELPER
> -	help
> -	  Support for non-programmable RGB to VGA DAC bridges, such as ADI
> -	  ADV7123, TI THS8134 and THS8135 or passive resistor ladder DACs.
> -
>  config DRM_LVDS_ENCODER
>  	tristate "Transparent parallel to LVDS encoder support"
>  	depends on OF
> @@ -108,6 +100,14 @@ config DRM_SII9234
>  	  It is an I2C driver, that detects connection of MHL bridge
>  	  and starts encapsulation of HDMI signal.
>  
> +config DRM_SIMPLE_BRIDGE
> +	tristate "Simple DRM bridge support"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	help
> +	  Support for non-programmable DRM bridges, such as ADI ADV7123, TI
> +	  THS8134 and THS8135 or passive resistor ladder DACs.
> +
>  config DRM_THINE_THC63LVD1024
>  	tristate "Thine THC63LVD1024 LVDS decoder bridge"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 4934fcf5a6f8..6ff7f2adbb0e 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,7 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> -obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
>  obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
>  obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> @@ -9,6 +8,7 @@ obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
>  obj-$(CONFIG_DRM_SII9234) += sii9234.o
> +obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
>  obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
>  obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
>  obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
> diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/simple-bridge.c
> similarity index 99%
> rename from drivers/gpu/drm/bridge/dumb-vga-dac.c
> rename to drivers/gpu/drm/bridge/simple-bridge.c
> index d46e461ae039..da5479bd5878 100644
> --- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -286,7 +286,7 @@ static struct platform_driver simple_bridge_driver = {
>  	.probe	= simple_bridge_probe,
>  	.remove	= simple_bridge_remove,
>  	.driver		= {
> -		.name		= "dumb-vga-dac",
> +		.name		= "simple-bridge",
>  		.of_match_table	= simple_bridge_match,
>  	},
>  };


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges
  2019-07-07 18:18   ` [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges Laurent Pinchart
@ 2019-07-09 14:08     ` Andrzej Hajda
  2019-07-26 13:24     ` Stefan Agner
  1 sibling, 0 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 14:08 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:18, Laurent Pinchart wrote:
> Create a new simple_bridge_info structure that stores information about
> the bridge model, and store the bridge timings in there, along with the
> connector type. Use that new structure for of_device_id data. This
> enables support for non-VGA bridges.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej


> ---
>  drivers/gpu/drm/bridge/simple-bridge.c | 41 ++++++++++++++++++--------
>  1 file changed, 29 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
> index da5479bd5878..bff240cf283d 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -16,10 +16,17 @@
>  #include <drm/drm_print.h>
>  #include <drm/drm_probe_helper.h>
>  
> +struct simple_bridge_info {
> +	const struct drm_bridge_timings *timings;
> +	unsigned int type;
> +};
> +
>  struct simple_bridge {
>  	struct drm_bridge	bridge;
>  	struct drm_connector	connector;
>  
> +	const struct simple_bridge_info *info;
> +
>  	struct i2c_adapter	*ddc;
>  	struct regulator	*vdd;
>  };
> @@ -113,7 +120,7 @@ static int simple_bridge_attach(struct drm_bridge *bridge)
>  				 &simple_bridge_con_helper_funcs);
>  	ret = drm_connector_init(bridge->dev, &sbridge->connector,
>  				 &simple_bridge_con_funcs,
> -				 DRM_MODE_CONNECTOR_VGA);
> +				 sbridge->info->type);
>  	if (ret) {
>  		DRM_ERROR("Failed to initialize connector\n");
>  		return ret;
> @@ -182,6 +189,8 @@ static int simple_bridge_probe(struct platform_device *pdev)
>  		return -ENOMEM;
>  	platform_set_drvdata(pdev, sbridge);
>  
> +	sbridge->info = of_device_get_match_data(&pdev->dev);
> +
>  	sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
>  	if (IS_ERR(sbridge->vdd)) {
>  		int ret = PTR_ERR(sbridge->vdd);
> @@ -204,7 +213,7 @@ static int simple_bridge_probe(struct platform_device *pdev)
>  
>  	sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
>  	sbridge->bridge.of_node = pdev->dev.of_node;
> -	sbridge->bridge.timings = of_device_get_match_data(&pdev->dev);
> +	sbridge->bridge.timings = sbridge->info->timings;
>  
>  	drm_bridge_add(&sbridge->bridge);
>  
> @@ -264,19 +273,27 @@ static const struct drm_bridge_timings ti_ths8135_bridge_timings = {
>  static const struct of_device_id simple_bridge_match[] = {
>  	{
>  		.compatible = "dumb-vga-dac",
> -		.data = NULL,
> -	},
> -	{
> +		.data = &(const struct simple_bridge_info) {
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
> +	}, {
>  		.compatible = "adi,adv7123",
> -		.data = &default_bridge_timings,
> -	},
> -	{
> +		.data = &(const struct simple_bridge_info) {
> +			.timings = &default_bridge_timings,
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
> +	}, {
>  		.compatible = "ti,ths8135",
> -		.data = &ti_ths8135_bridge_timings,
> -	},
> -	{
> +		.data = &(const struct simple_bridge_info) {
> +			.timings = &ti_ths8135_bridge_timings,
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
> +	}, {
>  		.compatible = "ti,ths8134",
> -		.data = &ti_ths8134_bridge_timings,
> +		.data = &(const struct simple_bridge_info) {
> +			.timings = &ti_ths8134_bridge_timings,
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
>  	},
>  	{},
>  };


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO
  2019-07-07 18:18   ` [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO Laurent Pinchart
@ 2019-07-09 14:32     ` Andrzej Hajda
  2019-07-26 13:19     ` Stefan Agner
  1 sibling, 0 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 14:32 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:18, Laurent Pinchart wrote:
> If an enable GPIO is declared in the firmware, assert it when enabling
> the bridge and deassert it when disabling it.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>


Hmm, simple becomes less simple. I guess we will end-up with sth similar
to panel-simple. And then we can merge both :)


Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej


> ---
>  drivers/gpu/drm/bridge/simple-bridge.c | 22 ++++++++++++++++++----
>  1 file changed, 18 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
> index bff240cf283d..a7edf3c39627 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -6,6 +6,7 @@
>   * Maxime Ripard <maxime.ripard@free-electrons.com>
>   */
>  
> +#include <linux/gpio/consumer.h>
>  #include <linux/module.h>
>  #include <linux/of_device.h>
>  #include <linux/of_graph.h>
> @@ -29,6 +30,7 @@ struct simple_bridge {
>  
>  	struct i2c_adapter	*ddc;
>  	struct regulator	*vdd;
> +	struct gpio_desc	*enable;
>  };
>  
>  static inline struct simple_bridge *
> @@ -135,19 +137,23 @@ static int simple_bridge_attach(struct drm_bridge *bridge)
>  static void simple_bridge_enable(struct drm_bridge *bridge)
>  {
>  	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
> -	int ret = 0;
> +	int ret;
>  
> -	if (sbridge->vdd)
> +	if (sbridge->vdd) {
>  		ret = regulator_enable(sbridge->vdd);
> +		if (ret)
> +			DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
> +	}
>  
> -	if (ret)
> -		DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
> +	gpiod_set_value_cansleep(sbridge->enable, 1);
>  }
>  
>  static void simple_bridge_disable(struct drm_bridge *bridge)
>  {
>  	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
>  
> +	gpiod_set_value_cansleep(sbridge->enable, 0);
> +
>  	if (sbridge->vdd)
>  		regulator_disable(sbridge->vdd);
>  }
> @@ -200,6 +206,14 @@ static int simple_bridge_probe(struct platform_device *pdev)
>  		dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
>  	}
>  
> +	sbridge->enable = devm_gpiod_get_optional(&pdev->dev, "enable",
> +						  GPIOD_OUT_LOW);
> +	if (IS_ERR(sbridge->enable)) {
> +		if (PTR_ERR(sbridge->enable) != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "Unable to retrieve enable GPIO\n");
> +		return PTR_ERR(sbridge->enable);
> +	}
> +
>  	sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
>  	if (IS_ERR(sbridge->ddc)) {
>  		if (PTR_ERR(sbridge->ddc) == -ENODEV) {


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362
  2019-07-07 18:18   ` [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362 Laurent Pinchart
@ 2019-07-09 14:32     ` Andrzej Hajda
  2019-08-27  6:16     ` Tomi Valkeinen
  1 sibling, 0 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-09 14:32 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:18, Laurent Pinchart wrote:
> The TI OP362 is an analog video amplifier controlled through a GPIO. Add
> support for it to the simple-bridge driver.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>

 --
Regards
Andrzej

> ---
>  drivers/gpu/drm/bridge/simple-bridge.c | 5 +++++
>  1 file changed, 5 insertions(+)
>
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
> index a7edf3c39627..7495b9bef865 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -296,6 +296,11 @@ static const struct of_device_id simple_bridge_match[] = {
>  			.timings = &default_bridge_timings,
>  			.type = DRM_MODE_CONNECTOR_VGA,
>  		},
> +	}, {
> +		.compatible = "ti,opa362",
> +		.data = &(const struct simple_bridge_info) {
> +			.type = DRM_MODE_CONNECTOR_Composite,
> +		},
>  	}, {
>  		.compatible = "ti,ths8135",
>  		.data = &(const struct simple_bridge_info) {


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel
  2019-07-07 18:19   ` [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel Laurent Pinchart
@ 2019-07-10  7:48     ` Sam Ravnborg
  2019-08-08 15:43       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-10  7:48 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

This driver looks very good.

On Sun, Jul 07, 2019 at 09:19:00PM +0300, Laurent Pinchart wrote:
> This panel is used on the OpenMoko Neo FreeRunner and Neo 1973.
Add info in Kconfig help entry?

> 
> +config DRM_PANEL_TPO_TD028TTEC1
> +	tristate "TPO TD028TTEC1 panel driver"
Maybe spell out TPO like "TPO (Topology) TD028..."

> +	depends on OF && SPI
> +	depends on BACKLIGHT_CLASS_DEVICE
> +	help
> +	  Say Y here if you want to enable support for TPO TD028TTEC1 480x640
> +	  2.8" panel.
> +
>  config DRM_PANEL_TPO_TPG110
>  	tristate "TPO TPG 800x400 panel"
>  	depends on OF && SPI && GPIOLIB
>  obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
> diff --git a/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
> new file mode 100644
> index 000000000000..05af9ea6339c
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
> +
> +static int jbt_ret_write_0(struct td028ttec1_device *lcd, u8 reg, int *err)
> +{
> +	struct spi_device *spi = lcd->spi;
> +	u16 tx_buf = JBT_COMMAND | reg;
> +	int ret;
> +
> +	if (err && *err)
> +		return *err;
> +
> +	ret = spi_write(spi, (u8 *)&tx_buf, sizeof(tx_buf));
> +	if (ret < 0) {
> +		dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret);
> +		if (err)
> +			*err = ret;
> +	}
> +
> +	return ret;
> +}
I like the way *err is used here.
So if one call fails, the remaining calls are ignored.

The way the code is written above it will only work on a little endian
box, as the values are stored in an u16 that is later seen as an array of
bytes.
This is also true for the remaing similar functions and may be OK.
We do not see any real demands for big endian anyway.

> +static int td028ttec1_enable(struct drm_panel *panel)
> +{
> +	struct td028ttec1_device *lcd = to_td028ttec1_device(panel);
> +	unsigned int i;
> +	int ret = 0;
> +
> +	/* Three times command zero */
> +	for (i = 0; i < 3; ++i) {
> +		jbt_ret_write_0(lcd, 0x00, &ret);
> +		usleep_range(1000, 2000);
> +	}
> +
> +	if (ret)
> +		return ret;
This if (ret) is really not needed.
It somehow short-circuit the principle used in the rest of the function
here. All jbt_reg_write() will be nop if ret != 0.

> +
> +	/* deep standby out */
> +	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x17, &ret);
> +
> +	/* RGB I/F on, RAM write off, QVGA through, SIGCON enable */
> +	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE, 0x80, &ret);
> +
> +	/* Quad mode off */
> +	jbt_reg_write_1(lcd, JBT_REG_QUAD_RATE, 0x00, &ret);
> +
> +	/* AVDD on, XVDD on */
> +	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x16, &ret);
> +
> +	/* Output control */
> +	jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, 0xfff9, &ret);
> +
> +	/* Sleep mode off */
> +	jbt_ret_write_0(lcd, JBT_REG_SLEEP_OUT, &ret);
> +
> +	/* at this point we have like 50% grey */
> +
> +	/* initialize register set */
> +	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE1, 0x01, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE2, 0x00, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_RGB_FORMAT, 0x60, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_DRIVE_SYSTEM, 0x10, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_OP, 0x56, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_MODE, 0x33, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_OPAMP_SYSCLK, 0x02, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_VSC_VOLTAGE, 0x2b, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_VCOM_VOLTAGE, 0x40, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_EXT_DISPL, 0x03, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_DCCLK_DCEV, 0x04, &ret);
> +	/*
> +	 * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement
> +	 * to avoid red / blue flicker
> +	 */
> +	jbt_reg_write_1(lcd, JBT_REG_ASW_SLEW, 0x04, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_DUMMY_DISPLAY, 0x00, &ret);
> +
> +	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_A, 0x11, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_B, 0x11, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_C, 0x11, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0, &ret);
> +
> +	jbt_reg_write_2(lcd, JBT_REG_GAMMA1_FINE_1, 0x5533, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_FINE_2, 0x00, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_INCLINATION, 0x00, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00, &ret);
> +
> +	jbt_reg_write_2(lcd, JBT_REG_HCLOCK_VGA, 0x1f0, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_BLANK_CONTROL, 0x02, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_BLANK_TH_TV, 0x0804, &ret);
> +
> +	jbt_reg_write_1(lcd, JBT_REG_CKV_ON_OFF, 0x01, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_CKV_1_2, 0x0000, &ret);
> +
> +	jbt_reg_write_2(lcd, JBT_REG_OEV_TIMING, 0x0d0e, &ret);
> +	jbt_reg_write_2(lcd, JBT_REG_ASW_TIMING_1, 0x11a4, &ret);
> +	jbt_reg_write_1(lcd, JBT_REG_ASW_TIMING_2, 0x0e, &ret);
> +
> +	jbt_ret_write_0(lcd, JBT_REG_DISPLAY_ON, &ret);
> +
> +	if (ret)
> +		return ret;
> +
> +	backlight_enable(lcd->backlight);
> +
> +	return 0;
> +}
> +
> +static const struct drm_display_mode td028ttec1_mode = {
> +	.clock = 22153,
> +	.hdisplay = 480,
> +	.hsync_start = 480 + 24,
> +	.hsync_end = 480 + 24 + 8,
> +	.htotal = 480 + 24 + 8 + 8,
> +	.vdisplay = 640,
> +	.vsync_start = 640 + 4,
> +	.vsync_end = 640 + 4 + 2,
> +	.vtotal = 640 + 4 + 2 + 2,
> +	.vrefresh = 66,
> +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> +};
Add width_mm + height_mm.

> +static int td028ttec1_remove(struct spi_device *spi)
> +{
> +	struct td028ttec1_device *lcd = spi_get_drvdata(spi);
> +
> +	drm_panel_remove(&lcd->panel);
> +	td028ttec1_disable(&lcd->panel);
Use drm_panel_disable();

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id td028ttec1_of_match[] = {
> +	{ .compatible = "tpo,td028ttec1", },
> +	/* DT backward compatibility. */
> +	{ .compatible = "toppoly,td028ttec1", },
> +	{},
{ /* sentinel */ },

With the above nits fixed/considered:
Reviewed-by: Sam Ravnborg <sam@ravnborg.org>

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-07 18:18   ` [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data Laurent Pinchart
@ 2019-07-10 12:12     ` Andrzej Hajda
  2019-07-11  7:35       ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-10 12:12 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

Hi Laurent,


I like the approach, current practice when almost every bridge should
optionally implement connector, or alternatively downstream bridge or
panel is very painful.
More comments inlined.

On 07.07.2019 20:18, Laurent Pinchart wrote:
> To support implementation of DRM connectors on top of DRM bridges
> instead of by bridges, the drm_bridge needs to expose new operations and
> data:
>
> - Output detection, hot-plug notification, mode retrieval and EDID
>   retrieval operations
> - Bitmask of supported operations


Why do we need these bitmask at all? Why cannot we rely on presence of
operation's callback?


> - Bridge output type
>
> Add and document these.
>
> Three new bridge helper functions are also added to handle hot plug
> notification in a way that is as transparent as possible for the
> bridges.


Documentation of new opses does not explain how it should cooperate with
bridge chaining, I suppose they should be chained explicitly, am I
right? More comments about it later.


>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>  2 files changed, 261 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index 519577f363e3..3c2a255df7af 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>   */
>  void drm_bridge_add(struct drm_bridge *bridge)
>  {
> +	mutex_init(&bridge->hpd_mutex);
> +
>  	mutex_lock(&bridge_lock);
>  	list_add_tail(&bridge->list, &bridge_list);
>  	mutex_unlock(&bridge_lock);
> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>  	mutex_lock(&bridge_lock);
>  	list_del_init(&bridge->list);
>  	mutex_unlock(&bridge_lock);
> +
> +	mutex_destroy(&bridge->hpd_mutex);
>  }
>  EXPORT_SYMBOL(drm_bridge_remove);
>  
> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>  }
>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>  
> +/**
> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> + * @bridge: bridge control structure
> + * @cb: hot-plug detection callback
> + * @data: data to be passed to the hot-plug detection callback
> + *
> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> + * hot plug notification callback. From now on the @cb will be called with
> + * @data when an output status change is detected by the bridge, until hot plug
> + * notification gets disabled with drm_bridge_hpd_disable().
> + *
> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> + * bridge->ops. This function shall not be called when the flag is not set.
> + *
> + * Only one hot plug detection callback can be registered at a time, it is an
> + * error to call this function when hot plug detection is already enabled for
> + * the bridge.
> + */


To simplify architecture maybe would be better to enable hpd just on
bridge attach:

bridge->hpd_cb = cb;

bridge->hpd_data = data;

ret = drm_bridge_attach(...);


This way we could avoid adding new callbacks hpd_(enable|disable)
without big sacrifices.


One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
notifies about sink status change, how it translates to this cb?


> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> +			   void (*cb)(void *data,
> +				      enum drm_connector_status status),
> +			   void *data)
> +{
> +	if (!bridge || !bridge->funcs->hpd_enable)
> +		return;
> +
> +	mutex_lock(&bridge->hpd_mutex);
> +
> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> +		goto unlock;
> +
> +	bridge->hpd_cb = cb;
> +	bridge->hpd_data = data;
> +
> +	bridge->funcs->hpd_enable(bridge);
> +
> +unlock:
> +	mutex_unlock(&bridge->hpd_mutex);
> +}
> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> +
> +/**
> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> + * @bridge: bridge control structure
> + *
> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> + * function returns the callback will not be called by the bridge when an
> + * output status change occurs.
> + *
> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> + * bridge->ops. This function shall not be called when the flag is not set.
> + */
> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> +{
> +	if (!bridge || !bridge->funcs->hpd_disable)
> +		return;
> +
> +	mutex_lock(&bridge->hpd_mutex);
> +	bridge->funcs->hpd_disable(bridge);
> +
> +	bridge->hpd_cb = NULL;
> +	bridge->hpd_data = NULL;
> +	mutex_unlock(&bridge->hpd_mutex);
> +}
> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> +
> +/**
> + * drm_bridge_hpd_notify - notify hot plug detection events
> + * @bridge: bridge control structure
> + * @status: output connection status
> + *
> + * Bridge drivers shall call this function to report hot plug events when they
> + * detect a change in the output status, when hot plug detection has been
> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> + *
> + * This function shall be called in a context that can sleep.
> + */
> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> +			   enum drm_connector_status status)
> +{
> +	mutex_lock(&bridge->hpd_mutex);
> +	if (bridge->hpd_cb)
> +		bridge->hpd_cb(bridge->hpd_data, status);
> +	mutex_unlock(&bridge->hpd_mutex);
> +}
> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> +
>  #ifdef CONFIG_OF
>  /**
>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> index 08dc15f93ded..b9445aa5b1ef 100644
> --- a/include/drm/drm_bridge.h
> +++ b/include/drm/drm_bridge.h
> @@ -23,8 +23,9 @@
>  #ifndef __DRM_BRIDGE_H__
>  #define __DRM_BRIDGE_H__
>  
> -#include <linux/list.h>
>  #include <linux/ctype.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
>  #include <drm/drm_mode_object.h>
>  #include <drm/drm_modes.h>
>  
> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>  	 */
>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>  				    struct drm_atomic_state *state);
> +
> +	/**
> +	 * @detect:
> +	 *
> +	 * Check if anything is attached to the bridge output.
> +	 *
> +	 * This callback is optional, if not implemented the bridge will be
> +	 * considered as always having a component attached to its output.
> +	 * Bridges that implement this callback shall set the
> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> +	 *
> +	 * RETURNS:
> +	 *
> +	 * drm_connector_status indicating the bridge output status.
> +	 */
> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> +
> +	/**
> +	 * @get_modes:
> +	 *
> +	 * Fill all modes currently valid for the sink into the &drm_connector
> +	 * with drm_mode_probed_add().
> +	 *
> +	 * The @get_modes callback is mostly intended to support non-probable
> +	 * displays such as many fixed panels. Bridges that support reading
> +	 * EDID shall leave @get_modes unimplemented and implement the
> +	 * &drm_bridge_funcs->get_edid callback instead.
> +	 *
> +	 * This callback is optional. Bridges that implement it shall set the
> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> +	 *
> +	 * RETURNS:
> +	 *
> +	 * The number of modes added by calling drm_mode_probed_add().
> +	 */
> +	int (*get_modes)(struct drm_bridge *bridge,
> +			 struct drm_connector *connector);
> +
> +	/**
> +	 * @get_edid:
> +	 *
> +	 * Read and parse the EDID data of the connected display.
> +	 *
> +	 * The @get_edid callback is the preferred way of reporting mode
> +	 * information for a display connected to the bridge output. Bridges
> +	 * that support readind EDID shall implement this callback and leave
> +	 * the @get_modes callback unimplemented.
> +	 *
> +	 * The caller of this operation shall first verify the output
> +	 * connection status and refrain from reading EDID from a disconnected
> +	 * output.
> +	 *
> +	 * This callback is optional. Bridges that implement it shall set the
> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> +	 *
> +	 * RETURNS:
> +	 *
> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> +	 * success, or NULL otherwise. The caller is responsible for freeing
> +	 * the returned edid structure with kfree().
> +	 */
> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> +				 struct drm_connector *connector);


It overlaps with get_modes, I guess presence of one ops should disallow
presence of another one?

I am not really convinced we need this op at all, cannot we just assign
some helper function to .get_modes cb, which will do the same?


Regards

Andrzej


> +
> +	/**
> +	 * @lost_hotplug:
> +	 *
> +	 * Notify the bridge of display disconnection.
> +	 *
> +	 * This callback is optional, it may be implemented by bridges that
> +	 * need to be notified of display disconnection for internal reasons.
> +	 * One use case is to reset the internal state of CEC controllers for
> +	 * HDMI bridges.
> +	 */
> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> +
> +	/**
> +	 * @hpd_enable:
> +	 *
> +	 * Enable hot plug detection. From now on the bridge shall call
> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> +	 * connection status, until hot plug detection gets disabled with
> +	 * @hpd_disable.
> +	 *
> +	 * This callback is optional and shall only be implemented by bridges
> +	 * that support hot-plug notification without polling. Bridges that
> +	 * implement it shall also implement the @hpd_disable callback and set
> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> +	 */
> +	void (*hpd_enable)(struct drm_bridge *bridge);
> +
> +	/**
> +	 * @hpd_disable:
> +	 *
> +	 * Disable hot plug detection. Once this function returns the bridge
> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> +	 * connection status occurs.
> +	 *
> +	 * This callback is optional and shall only be implemented by bridges
> +	 * that support hot-plug notification without polling. Bridges that
> +	 * implement it shall also implement the @hpd_enable callback and set
> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> +	 */
> +	void (*hpd_disable)(struct drm_bridge *bridge);
>  };
>  
>  /**
> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>  	bool dual_link;
>  };
>  
> +/**
> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> + */
> +enum drm_bridge_ops {
> +	/**
> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> +	 * its output. Bridges that set this flag shall implement the
> +	 * &drm_bridge_funcs->detect callback.
> +	 */
> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> +	/**
> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> +	 * connected to its output. Bridges that set this flag shall implement
> +	 * the &drm_bridge_funcs->get_edid callback.
> +	 */
> +	DRM_BRIDGE_OP_EDID = BIT(1),
> +	/**
> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> +	 * without requiring polling. Bridges that set this flag shall
> +	 * implement the &drm_bridge_funcs->hpd_enable and
> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> +	 */
> +	DRM_BRIDGE_OP_HPD = BIT(2),
> +	/**
> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> +	 * by the display at its output. This does not include readind EDID
> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> +	 */
> +	DRM_BRIDGE_OP_MODES = BIT(3),
> +};
> +
>  /**
>   * struct drm_bridge - central DRM bridge control structure
>   */
> @@ -398,6 +535,29 @@ struct drm_bridge {
>  	const struct drm_bridge_funcs *funcs;
>  	/** @driver_private: pointer to the bridge driver's internal context */
>  	void *driver_private;
> +	/** @ops: bitmask of operations supported by the bridge */
> +	enum drm_bridge_ops ops;
> +	/**
> +	 * @type: Type of the connection at the bridge output
> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> +	 * identifies the type of connected display.
> +	 */
> +	int type;
> +	/** private: */
> +	/**
> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> +	 */
> +	struct mutex hpd_mutex;
> +	/**
> +	 * @hpd_cb: Hot plug detection callback, registered with
> +	 * drm_bridge_hpd_enable().
> +	 */
> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> +	/**
> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> +	 * @hpd_cb.
> +	 */
> +	void *hpd_data;
>  };
>  
>  void drm_bridge_add(struct drm_bridge *bridge);
> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>  			      struct drm_atomic_state *state);
>  
> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> +			   void (*cb)(void *data,
> +				      enum drm_connector_status status),
> +			   void *data);
> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> +			   enum drm_connector_status status);
> +
>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel
  2019-07-07 18:19   ` [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel Laurent Pinchart
@ 2019-07-10 13:09     ` Sam Ravnborg
  2019-08-08 15:54       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-10 13:09 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

I had assumed this driver would look like the other Topology driver, but
they differ a lot. So it makes sense to have different drivers.

This driver implements suspend/resume.
But the correct way would be to implment prepare/unprepare.

The power_on(), power_off() functions would then be embedded in
the prepare(), unprepare() functions as there would be only one user.

See additional comments in the following.

	Sam

On Sun, Jul 07, 2019 at 09:19:01PM +0300, Laurent Pinchart wrote:
> This panel is used on the OMAP3 Pandora.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/panel/Kconfig                |   7 +
>  drivers/gpu/drm/panel/Makefile               |   1 +
>  drivers/gpu/drm/panel/panel-tpo-td043mtea1.c | 510 +++++++++++++++++++
>  3 files changed, 518 insertions(+)
>  create mode 100644 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> 
> diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> index b7099d211061..8f3660c73044 100644
> --- a/drivers/gpu/drm/panel/Kconfig
> +++ b/drivers/gpu/drm/panel/Kconfig
> @@ -312,6 +312,13 @@ config DRM_PANEL_TPO_TD028TTEC1
>  	  Say Y here if you want to enable support for TPO TD028TTEC1 480x640
>  	  2.8" panel.
>  
> +config DRM_PANEL_TPO_TD043MTEA1
> +	tristate "TPO TD043MTEA1 panel driver"
Spell out TPO?
> +	depends on GPIOLIB && OF && REGULATOR && SPI
> +	help
> +	  Say Y here if you want to enable support for TPO TD043MTEA1 800x480
> +	  4.3" panel.
Maybe tell it is used on OMAP3 Pandora

> +
>  config DRM_PANEL_TPO_TPG110
>  	tristate "TPO TPG 800x400 panel"
>  	depends on OF && SPI && GPIOLIB
> diff --git a/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> new file mode 100644
> index 000000000000..6b17e47582b8
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> @@ -0,0 +1,510 @@
> +// SPDX-License-Identifier: GPL-2.0+
Just noticed, this is a different license than the others.
But I guess this comes from the original file.

> +/*
> + * Toppology TD043MTEA1 Panel Driver
So I actually asked Google this time - the correct spelling is
"Toppoly".
See: http://www.innolux.com/Pages/EN/AboutUs/Company_Overview_EN.html


> +struct td043mtea1_device {
> +	struct drm_panel panel;
> +
> +	struct spi_device *spi;
> +	struct regulator *vcc_reg;
> +	struct gpio_desc *reset_gpio;
> +
> +	unsigned int mode;
> +	u16 gamma[12];
> +	bool vmirror;
> +	bool powered_on;
This flag will not be needed when prepare(), unprepare() are used.

> +	bool spi_suspended;
> +	bool power_on_resume;
> +};
> +
> +

> +static void td043mtea1_write_gamma(struct td043mtea1_device *lcd)
> +{
> +	const u16 *gamma = lcd->gamma;
> +	unsigned int i;
> +	u8 val;
> +
> +	/* gamma bits [9:8] */
> +	for (val = i = 0; i < 4; i++)
> +		val |= (gamma[i] & 0x300) >> ((i + 1) * 2);
> +	td043mtea1_write(lcd, 0x11, val);
> +
> +	for (val = i = 0; i < 4; i++)
> +		val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2);
Spaces around operators.

> +	td043mtea1_write(lcd, 0x12, val);
> +
> +	for (val = i = 0; i < 4; i++)
> +		val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2);
Same here.

> +	td043mtea1_write(lcd, 0x13, val);
> +
> +	/* gamma bits [7:0] */
> +	for (val = i = 0; i < 12; i++)
> +		td043mtea1_write(lcd, 0x14 + i, gamma[i] & 0xff);
> +}
This function (td043mtea1_write_gamma()) fails to check the result of
the write operations. Maybe on purpose. But looks strange we do it in
some places but not all.

> +
> +static int td043mtea1_write_mirror(struct td043mtea1_device *lcd)
> +{
> +	u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V |
> +		TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H;
> +	if (lcd->vmirror)
> +		reg4 &= ~TPO_R04_NFLIP_V;
> +
> +	return td043mtea1_write(lcd, 4, reg4);
> +}
Add a:
#define TPO_R4	4
And then use it here?
Looks bad that the register number is hardcoded.

Same goes for several other calls to the write() function.

> +
> +static int td043mtea1_power_on(struct td043mtea1_device *lcd)
> +{
> +	int ret;
> +
> +	if (lcd->powered_on)
> +		return 0;
> +
> +	ret = regulator_enable(lcd->vcc_reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Wait for the panel to stabilize. */
> +	msleep(160);
> +
> +	gpiod_set_value(lcd->reset_gpio, 0);
> +
> +	td043mtea1_write(lcd, 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING);
> +	td043mtea1_write(lcd, 3, TPO_R03_VAL_NORMAL);
> +	td043mtea1_write(lcd, 0x20, 0xf0);
> +	td043mtea1_write(lcd, 0x21, 0xf0);
> +	td043mtea1_write_mirror(lcd);
> +	td043mtea1_write_gamma(lcd);
> +
> +	lcd->powered_on = true;
> +
> +	return 0;
> +}
The above should be part of prepare()

> +
> +static void td043mtea1_power_off(struct td043mtea1_device *lcd)
> +{
> +	if (!lcd->powered_on)
> +		return;
> +
> +	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
> +
> +	gpiod_set_value(lcd->reset_gpio, 1);
> +
> +	/* wait for at least 2 vsyncs before cutting off power */
> +	msleep(50);
> +
> +	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY);
> +
> +	regulator_disable(lcd->vcc_reg);
> +
> +	lcd->powered_on = false;
> +}
The above should be part of unprepare()

> +
> +/* -----------------------------------------------------------------------------
> + * sysfs
> + */
> +
> +static ssize_t vmirror_show(struct device *dev, struct device_attribute *attr,
> +			    char *buf)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +
> +	return snprintf(buf, PAGE_SIZE, "%d\n", lcd->vmirror);
> +}
> +
> +static ssize_t vmirror_store(struct device *dev, struct device_attribute *attr,
> +			     const char *buf, size_t count)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +	int val;
> +	int ret;
> +
> +	ret = kstrtoint(buf, 0, &val);
> +	if (ret < 0)
> +		return ret;
> +
> +	lcd->vmirror = !!val;
> +
> +	ret = td043mtea1_write_mirror(lcd);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
> +			 char *buf)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +
> +	return snprintf(buf, PAGE_SIZE, "%d\n", lcd->mode);
> +}
> +
> +static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
> +			  const char *buf, size_t count)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +	long val;
> +	int ret;
> +
> +	ret = kstrtol(buf, 0, &val);
> +	if (ret != 0 || val & ~7)
> +		return -EINVAL;
> +
> +	lcd->mode = val;
> +
> +	val |= TPO_R02_NCLK_RISING;
> +	td043mtea1_write(lcd, 2, val);
> +
> +	return count;
> +}
> +
> +static ssize_t gamma_show(struct device *dev, struct device_attribute *attr,
> +			  char *buf)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +	ssize_t len = 0;
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) {
> +		ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
> +				lcd->gamma[i]);
> +		if (ret < 0)
> +			return ret;
> +		len += ret;
> +	}
> +	buf[len - 1] = '\n';
> +
> +	return len;
> +}
> +
> +static ssize_t gamma_store(struct device *dev, struct device_attribute *attr,
> +			   const char *buf, size_t count)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +	unsigned int g[12];
> +	unsigned int i;
> +	int ret;
> +
> +	ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u",
> +			&g[0], &g[1], &g[2], &g[3], &g[4], &g[5],
> +			&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
> +	if (ret != 12)
> +		return -EINVAL;
> +
> +	for (i = 0; i < 12; i++)
> +		lcd->gamma[i] = g[i];
> +
> +	td043mtea1_write_gamma(lcd);
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(vmirror);
> +static DEVICE_ATTR_RW(mode);
> +static DEVICE_ATTR_RW(gamma);
> +
> +static struct attribute *td043mtea1_attrs[] = {
> +	&dev_attr_vmirror.attr,
> +	&dev_attr_mode.attr,
> +	&dev_attr_gamma.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group td043mtea1_attr_group = {
> +	.attrs = td043mtea1_attrs,
> +};
I see what is done with mirror, mode and gamma - but the question is if
they are really needed?
And if needed, is it the right way to configure the panel?
This is likely questiosn that are not easy to answer definitive, so best
to keep this as it was before.


> +
> +/* -----------------------------------------------------------------------------
> + * Bridge Operations
> + */

Panel operations, not bridge operations?

> +
> +static int td043mtea1_disable(struct drm_panel *panel)
> +{
> +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> +
> +	if (!lcd->spi_suspended)
> +		td043mtea1_power_off(lcd);
> +
> +	return 0;
> +}
> +
> +static int td043mtea1_enable(struct drm_panel *panel)
> +{
> +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> +	int ret;
> +
> +	/*
> +	 * If we are resuming from system suspend, SPI might not be enabled
> +	 * yet, so we'll program the LCD from SPI PM resume callback.
> +	 */
> +	if (lcd->spi_suspended)
> +		return 0;
I do not recall this is needed in other panel drivers, so look at what
other spi based panels do here.
I think this is something that today is not required.

> +
> +	ret = td043mtea1_power_on(lcd);
> +	if (ret) {
> +		dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n",
> +			__func__, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_display_mode td043mtea1_mode = {
> +	.clock = 36000,
> +	.hdisplay = 800,
> +	.hsync_start = 800 + 68,
> +	.hsync_end = 800 + 68 + 1,
> +	.htotal = 800 + 68 + 1 + 214,
> +	.vdisplay = 480,
> +	.vsync_start = 480 + 39,
> +	.vsync_end = 480 + 39 + 1,
> +	.vtotal = 480 + 39 + 1 + 34,
> +	.vrefresh = 60,
> +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> +};
height_mm, width_mm

> +
> +static int td043mtea1_get_modes(struct drm_panel *panel)
> +{
> +	struct drm_connector *connector = panel->connector;
> +	struct drm_display_mode *mode;
> +
> +	mode = drm_mode_duplicate(panel->drm, &td043mtea1_mode);
> +	if (!mode)
> +		return -ENOMEM;
> +
> +	drm_mode_set_name(mode);
> +	drm_mode_probed_add(connector, mode);
> +
> +	connector->display_info.width_mm = 94;
> +	connector->display_info.height_mm = 56;
> +	/*
> +	 * FIXME: According to the datasheet sync signals are sampled on the
> +	 * rising edge of the clock, but the code running on the OMAP3 Pandora
> +	 * indicates sampling on the falling edge. This should be tested on a
> +	 * real device.
> +	 */
> +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> +					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
> +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
> +
> +	return 1;
> +}
> +
> +static const struct drm_panel_funcs td043mtea1_funcs = {
> +	.disable = td043mtea1_disable,
> +	.enable = td043mtea1_enable,
> +	.get_modes = td043mtea1_get_modes,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Power Management, Probe and Remove
> + */
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int td043mtea1_suspend(struct device *dev)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +
> +	if (lcd->powered_on) {
> +		td043mtea1_power_off(lcd);
> +		lcd->powered_on = true;
> +	}
> +
> +	lcd->spi_suspended = true;
> +
> +	return 0;
> +}
> +
> +static int td043mtea1_resume(struct device *dev)
> +{
> +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> +	int ret;
> +
> +	lcd->spi_suspended = false;
> +
> +	if (lcd->powered_on) {
> +		lcd->powered_on = false;
> +		ret = td043mtea1_power_on(lcd);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend,
> +			 td043mtea1_resume);
> +#endif
> +
> +static int td043mtea1_probe(struct spi_device *spi)
> +{
> +	struct td043mtea1_device *lcd;
> +	int ret;
> +
> +	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> +	if (lcd == NULL)
> +		return -ENOMEM;
> +
> +	spi_set_drvdata(spi, lcd);
> +	lcd->spi = spi;
> +	lcd->mode = TPO_R02_MODE_800x480;
> +	memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma));
> +
> +	lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
> +	if (IS_ERR(lcd->vcc_reg)) {
> +		dev_err(&spi->dev, "failed to get VCC regulator\n");
> +		return PTR_ERR(lcd->vcc_reg);
> +	}
> +
> +	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(lcd->reset_gpio)) {
> +		dev_err(&spi->dev, "failed to get reset GPIO\n");
> +		return PTR_ERR(lcd->reset_gpio);
> +	}
> +
> +	spi->bits_per_word = 16;
> +	spi->mode = SPI_MODE_0;
> +
> +	ret = spi_setup(spi);
> +	if (ret < 0) {
> +		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = sysfs_create_group(&spi->dev.kobj, &td043mtea1_attr_group);
> +	if (ret < 0) {
> +		dev_err(&spi->dev, "failed to create sysfs files\n");
> +		return ret;
> +	}
> +
> +	drm_panel_init(&lcd->panel);
> +	lcd->panel.dev = &lcd->spi->dev;
> +	lcd->panel.funcs = &td043mtea1_funcs;
> +
> +	ret = drm_panel_add(&lcd->panel);
> +	if (ret < 0) {
> +		sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int td043mtea1_remove(struct spi_device *spi)
> +{
> +	struct td043mtea1_device *lcd = spi_get_drvdata(spi);
> +
> +	drm_panel_remove(&lcd->panel);
> +	td043mtea1_disable(&lcd->panel);
> +
> +	sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id td043mtea1_of_match[] = {
> +	{ .compatible = "tpo,td043mtea1", },
> +	{},
{ /* sentinel */ },

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks
  2019-07-09 13:20   ` Andrzej Hajda
@ 2019-07-10 15:41     ` Daniel Vetter
  0 siblings, 0 replies; 166+ messages in thread
From: Daniel Vetter @ 2019-07-10 15:41 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On Tue, Jul 09, 2019 at 03:20:58PM +0200, Andrzej Hajda wrote:
> On 07.07.2019 20:07, Laurent Pinchart wrote:
> > The drm_display_info structure contains many fields related to HDMI
> > sinks, but none that identifies if a sink compliant with CEA-861 (EDID)
> > shall be treated as an HDMI sink or a DVI sink. Add such a flag, and
> > populate it according to section 8.3.3 ("DVI/HDMI Device
> > Discrimination") of the HDMI v1.3 specification.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> 
> It looks like it can replace drm_detect_hdmi_monitor usage in most cases.

Yeah I think kerneldoc should at least between these too, i.e. from
info.is_hdmi to drm_detect_hdmi_monitor() and back.

Plus ideally a refactor task in todo.rst, this is ideal getting started
fodder I think. And I like if we standardize as much as possible on
drm_display_info.

With that: Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
> 
> Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
> 
>  --
> Regards
> Andrzej
> 
> 
> 
> > ---
> >  drivers/gpu/drm/drm_edid.c  | 3 +++
> >  include/drm/drm_connector.h | 5 +++++
> >  2 files changed, 8 insertions(+)
> >
> > diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> > index 82a4ceed3fcf..d2e7a5334c3f 100644
> > --- a/drivers/gpu/drm/drm_edid.c
> > +++ b/drivers/gpu/drm/drm_edid.c
> > @@ -4559,6 +4559,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
> >  	struct drm_display_info *info = &connector->display_info;
> >  	u8 len = cea_db_payload_len(db);
> >  
> > +	info->is_hdmi = true;
> > +
> >  	if (len >= 6)
> >  		info->dvi_dual = db[6] & 1;
> >  	if (len >= 7)
> > @@ -4627,6 +4629,7 @@ drm_reset_display_info(struct drm_connector *connector)
> >  	info->cea_rev = 0;
> >  	info->max_tmds_clock = 0;
> >  	info->dvi_dual = false;
> > +	info->is_hdmi = false;
> >  	info->has_hdmi_infoframe = false;
> >  	info->rgb_quant_range_selectable = false;
> >  	memset(&info->hdmi, 0, sizeof(info->hdmi));
> > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > index ca745d9feaf5..e80ca0d149e5 100644
> > --- a/include/drm/drm_connector.h
> > +++ b/include/drm/drm_connector.h
> > @@ -426,6 +426,11 @@ struct drm_display_info {
> >  	 */
> >  	bool dvi_dual;
> >  
> > +	/**
> > +	 * @is_hdmi: True if the sink is an HDMI device.
> > +	 */
> > +	bool is_hdmi;
> > +
> >  	/**
> >  	 * @has_hdmi_infoframe: Does the sink support the HDMI infoframe?
> >  	 */
> 
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks
  2019-07-07 18:07 ` [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks Laurent Pinchart
  2019-07-09 13:20   ` Andrzej Hajda
@ 2019-07-10 15:59   ` Ville Syrjälä
  1 sibling, 0 replies; 166+ messages in thread
From: Ville Syrjälä @ 2019-07-10 15:59 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

On Sun, Jul 07, 2019 at 09:07:53PM +0300, Laurent Pinchart wrote:
> The drm_display_info structure contains many fields related to HDMI
> sinks, but none that identifies if a sink compliant with CEA-861 (EDID)
> shall be treated as an HDMI sink or a DVI sink. Add such a flag, and
> populate it according to section 8.3.3 ("DVI/HDMI Device
> Discrimination") of the HDMI v1.3 specification.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/drm_edid.c  | 3 +++
>  include/drm/drm_connector.h | 5 +++++
>  2 files changed, 8 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> index 82a4ceed3fcf..d2e7a5334c3f 100644
> --- a/drivers/gpu/drm/drm_edid.c
> +++ b/drivers/gpu/drm/drm_edid.c
> @@ -4559,6 +4559,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
>  	struct drm_display_info *info = &connector->display_info;
>  	u8 len = cea_db_payload_len(db);
>  
> +	info->is_hdmi = true;
> +

Almost missed this one since it was hidden inside a rather big series.

I was pondering if we should set this in drm_parse_hdmi_forum_vsdb()
too, but looks like the spec says we don't have to:
"An H14b VSDB shall always be included, regardless of the inclusion of
 an HF-VSDB, to ensure correct functioning of DVI/HDMI discrimination..."

Also we don't check for the HF-VSDB in drm_detect_hdmi_monitor() either.
Unfortunately we can't simply replace drm_detect_hdmi_monitor() in
i915 with a check for this flag because we populate display_info way
too late.

Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com>

>  	if (len >= 6)
>  		info->dvi_dual = db[6] & 1;
>  	if (len >= 7)
> @@ -4627,6 +4629,7 @@ drm_reset_display_info(struct drm_connector *connector)
>  	info->cea_rev = 0;
>  	info->max_tmds_clock = 0;
>  	info->dvi_dual = false;
> +	info->is_hdmi = false;
>  	info->has_hdmi_infoframe = false;
>  	info->rgb_quant_range_selectable = false;
>  	memset(&info->hdmi, 0, sizeof(info->hdmi));
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index ca745d9feaf5..e80ca0d149e5 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -426,6 +426,11 @@ struct drm_display_info {
>  	 */
>  	bool dvi_dual;
>  
> +	/**
> +	 * @is_hdmi: True if the sink is an HDMI device.
> +	 */
> +	bool is_hdmi;
> +
>  	/**
>  	 * @has_hdmi_infoframe: Does the sink support the HDMI infoframe?
>  	 */
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Ville Syrjälä
Intel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-10 12:12     ` Andrzej Hajda
@ 2019-07-11  7:35       ` Daniel Vetter
  2019-07-11 12:41         ` Andrzej Hajda
  2019-08-08 18:19         ` Laurent Pinchart
  0 siblings, 2 replies; 166+ messages in thread
From: Daniel Vetter @ 2019-07-11  7:35 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> Hi Laurent,
> 
> 
> I like the approach, current practice when almost every bridge should
> optionally implement connector, or alternatively downstream bridge or
> panel is very painful.

Yeah I think this looks mostly reasonable. Some api design comments on top
of Andrzej', with the fair warning that I didn't bother to read up on how
it's all used in the end. I probably should go and do that, at least to
get a feeling for what your hpd_cb usually does.

> More comments inlined.
> 
> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > To support implementation of DRM connectors on top of DRM bridges
> > instead of by bridges, the drm_bridge needs to expose new operations and
> > data:
> >
> > - Output detection, hot-plug notification, mode retrieval and EDID
> >   retrieval operations
> > - Bitmask of supported operations
> 
> 
> Why do we need these bitmask at all? Why cannot we rely on presence of
> operation's callback?

Yeah also not a huge fan of these bitmasks. Smells like
DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
add, generally good excuse to not have to think through the design between
different parts of drivers - "just" add another flag.
> 
> 
> > - Bridge output type
> >
> > Add and document these.
> >
> > Three new bridge helper functions are also added to handle hot plug
> > notification in a way that is as transparent as possible for the
> > bridges.
> 
> 
> Documentation of new opses does not explain how it should cooperate with
> bridge chaining, I suppose they should be chained explicitly, am I
> right? More comments about it later.
> 
> 
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >  2 files changed, 261 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > index 519577f363e3..3c2a255df7af 100644
> > --- a/drivers/gpu/drm/drm_bridge.c
> > +++ b/drivers/gpu/drm/drm_bridge.c
> > @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >   */
> >  void drm_bridge_add(struct drm_bridge *bridge)
> >  {
> > +	mutex_init(&bridge->hpd_mutex);
> > +
> >  	mutex_lock(&bridge_lock);
> >  	list_add_tail(&bridge->list, &bridge_list);
> >  	mutex_unlock(&bridge_lock);
> > @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >  	mutex_lock(&bridge_lock);
> >  	list_del_init(&bridge->list);
> >  	mutex_unlock(&bridge_lock);
> > +
> > +	mutex_destroy(&bridge->hpd_mutex);
> >  }
> >  EXPORT_SYMBOL(drm_bridge_remove);
> >  
> > @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >  }
> >  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >  
> > +/**
> > + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > + * @bridge: bridge control structure
> > + * @cb: hot-plug detection callback
> > + * @data: data to be passed to the hot-plug detection callback
> > + *
> > + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > + * hot plug notification callback. From now on the @cb will be called with
> > + * @data when an output status change is detected by the bridge, until hot plug
> > + * notification gets disabled with drm_bridge_hpd_disable().
> > + *
> > + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > + * bridge->ops. This function shall not be called when the flag is not set.
> > + *
> > + * Only one hot plug detection callback can be registered at a time, it is an
> > + * error to call this function when hot plug detection is already enabled for
> > + * the bridge.
> > + */
> 
> 
> To simplify architecture maybe would be better to enable hpd just on
> bridge attach:
> 
> bridge->hpd_cb = cb;
> 
> bridge->hpd_data = data;
> 
> ret = drm_bridge_attach(...);

Yeah I like this more. The other problem here is, what if you need more
than 1 callback registers on the same bridge hdp signal?


> This way we could avoid adding new callbacks hpd_(enable|disable)
> without big sacrifices.
> 
> 
> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> notifies about sink status change, how it translates to this cb?
> 
> 
> > +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > +			   void (*cb)(void *data,
> > +				      enum drm_connector_status status),
> > +			   void *data)
> > +{
> > +	if (!bridge || !bridge->funcs->hpd_enable)
> > +		return;
> > +
> > +	mutex_lock(&bridge->hpd_mutex);
> > +
> > +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > +		goto unlock;
> > +
> > +	bridge->hpd_cb = cb;
> > +	bridge->hpd_data = data;
> > +
> > +	bridge->funcs->hpd_enable(bridge);
> > +
> > +unlock:
> > +	mutex_unlock(&bridge->hpd_mutex);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > +
> > +/**
> > + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > + * @bridge: bridge control structure
> > + *
> > + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > + * function returns the callback will not be called by the bridge when an
> > + * output status change occurs.
> > + *
> > + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > + * bridge->ops. This function shall not be called when the flag is not set.
> > + */
> > +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > +{
> > +	if (!bridge || !bridge->funcs->hpd_disable)
> > +		return;
> > +
> > +	mutex_lock(&bridge->hpd_mutex);
> > +	bridge->funcs->hpd_disable(bridge);
> > +
> > +	bridge->hpd_cb = NULL;
> > +	bridge->hpd_data = NULL;
> > +	mutex_unlock(&bridge->hpd_mutex);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > +
> > +/**
> > + * drm_bridge_hpd_notify - notify hot plug detection events
> > + * @bridge: bridge control structure
> > + * @status: output connection status
> > + *
> > + * Bridge drivers shall call this function to report hot plug events when they
> > + * detect a change in the output status, when hot plug detection has been
> > + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > + *
> > + * This function shall be called in a context that can sleep.
> > + */
> > +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > +			   enum drm_connector_status status)
> > +{
> > +	mutex_lock(&bridge->hpd_mutex);
> > +	if (bridge->hpd_cb)
> > +		bridge->hpd_cb(bridge->hpd_data, status);

So this isn't quite what I had in mind. Instead something like this:

	/* iterates over all bridges in the chain containing @bridge */
	for_each_bridge(tmp_bridge, bridge) {
		if (tmp_bridge == bridge)
			continue;
		if (bridge->hpd_notify);
			bridge->hpd_notify(tmp_bridge, bridge, status);
	}

	encoder = encoder_for_bridge(bridge);
	if (encoder->helper_private->bridge_hpd_notify)
		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);

	dev = bridge->dev
	if (dev->mode_config.helper_private->bridge_hpd_notify)
		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)

No register callback needed, no locking needed, everyone gets exactly the
hpd they want/need.

> > +	mutex_unlock(&bridge->hpd_mutex);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > +
> >  #ifdef CONFIG_OF
> >  /**
> >   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > index 08dc15f93ded..b9445aa5b1ef 100644
> > --- a/include/drm/drm_bridge.h
> > +++ b/include/drm/drm_bridge.h
> > @@ -23,8 +23,9 @@
> >  #ifndef __DRM_BRIDGE_H__
> >  #define __DRM_BRIDGE_H__
> >  
> > -#include <linux/list.h>
> >  #include <linux/ctype.h>
> > +#include <linux/list.h>
> > +#include <linux/mutex.h>
> >  #include <drm/drm_mode_object.h>
> >  #include <drm/drm_modes.h>
> >  
> > @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >  	 */
> >  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >  				    struct drm_atomic_state *state);
> > +
> > +	/**
> > +	 * @detect:
> > +	 *
> > +	 * Check if anything is attached to the bridge output.
> > +	 *
> > +	 * This callback is optional, if not implemented the bridge will be
> > +	 * considered as always having a component attached to its output.
> > +	 * Bridges that implement this callback shall set the
> > +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > +	 *
> > +	 * RETURNS:
> > +	 *
> > +	 * drm_connector_status indicating the bridge output status.
> > +	 */
> > +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > +
> > +	/**
> > +	 * @get_modes:
> > +	 *
> > +	 * Fill all modes currently valid for the sink into the &drm_connector
> > +	 * with drm_mode_probed_add().
> > +	 *
> > +	 * The @get_modes callback is mostly intended to support non-probable
> > +	 * displays such as many fixed panels. Bridges that support reading
> > +	 * EDID shall leave @get_modes unimplemented and implement the
> > +	 * &drm_bridge_funcs->get_edid callback instead.
> > +	 *
> > +	 * This callback is optional. Bridges that implement it shall set the
> > +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > +	 *
> > +	 * RETURNS:
> > +	 *
> > +	 * The number of modes added by calling drm_mode_probed_add().
> > +	 */
> > +	int (*get_modes)(struct drm_bridge *bridge,
> > +			 struct drm_connector *connector);
> > +
> > +	/**
> > +	 * @get_edid:
> > +	 *
> > +	 * Read and parse the EDID data of the connected display.
> > +	 *
> > +	 * The @get_edid callback is the preferred way of reporting mode
> > +	 * information for a display connected to the bridge output. Bridges
> > +	 * that support readind EDID shall implement this callback and leave
> > +	 * the @get_modes callback unimplemented.
> > +	 *
> > +	 * The caller of this operation shall first verify the output
> > +	 * connection status and refrain from reading EDID from a disconnected
> > +	 * output.
> > +	 *
> > +	 * This callback is optional. Bridges that implement it shall set the
> > +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > +	 *
> > +	 * RETURNS:
> > +	 *
> > +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > +	 * success, or NULL otherwise. The caller is responsible for freeing
> > +	 * the returned edid structure with kfree().
> > +	 */
> > +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > +				 struct drm_connector *connector);
> 
> 
> It overlaps with get_modes, I guess presence of one ops should disallow
> presence of another one?
> 
> I am not really convinced we need this op at all, cannot we just assign
> some helper function to .get_modes cb, which will do the same?

Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
case, and require that if it has an edid it must fill out connector->info
and connector->edid correctly.

Btw if a hpd happens, who's responible for making sure the edid/mode list
in the connector is up-to-date? With your current callback design that's
up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
should guarantee that it'll first walk the connectors to update status and
edid/mode list for the final drm_connector. And then instead of just
passing the simple "status", it'll pass the connector, with everything
correctly updated.

Otherwise everyone interested in that hpd signal will go and re-fetch the
edid, which is not so awesome :-)
-Daniel

> 
> 
> Regards
> 
> Andrzej
> 
> 
> > +
> > +	/**
> > +	 * @lost_hotplug:
> > +	 *
> > +	 * Notify the bridge of display disconnection.
> > +	 *
> > +	 * This callback is optional, it may be implemented by bridges that
> > +	 * need to be notified of display disconnection for internal reasons.
> > +	 * One use case is to reset the internal state of CEC controllers for
> > +	 * HDMI bridges.
> > +	 */
> > +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > +
> > +	/**
> > +	 * @hpd_enable:
> > +	 *
> > +	 * Enable hot plug detection. From now on the bridge shall call
> > +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > +	 * connection status, until hot plug detection gets disabled with
> > +	 * @hpd_disable.
> > +	 *
> > +	 * This callback is optional and shall only be implemented by bridges
> > +	 * that support hot-plug notification without polling. Bridges that
> > +	 * implement it shall also implement the @hpd_disable callback and set
> > +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > +	 */
> > +	void (*hpd_enable)(struct drm_bridge *bridge);
> > +
> > +	/**
> > +	 * @hpd_disable:
> > +	 *
> > +	 * Disable hot plug detection. Once this function returns the bridge
> > +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > +	 * connection status occurs.
> > +	 *
> > +	 * This callback is optional and shall only be implemented by bridges
> > +	 * that support hot-plug notification without polling. Bridges that
> > +	 * implement it shall also implement the @hpd_enable callback and set
> > +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > +	 */
> > +	void (*hpd_disable)(struct drm_bridge *bridge);
> >  };
> >  
> >  /**
> > @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >  	bool dual_link;
> >  };
> >  
> > +/**
> > + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > + */
> > +enum drm_bridge_ops {
> > +	/**
> > +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > +	 * its output. Bridges that set this flag shall implement the
> > +	 * &drm_bridge_funcs->detect callback.
> > +	 */
> > +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > +	/**
> > +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > +	 * connected to its output. Bridges that set this flag shall implement
> > +	 * the &drm_bridge_funcs->get_edid callback.
> > +	 */
> > +	DRM_BRIDGE_OP_EDID = BIT(1),
> > +	/**
> > +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > +	 * without requiring polling. Bridges that set this flag shall
> > +	 * implement the &drm_bridge_funcs->hpd_enable and
> > +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > +	 */
> > +	DRM_BRIDGE_OP_HPD = BIT(2),
> > +	/**
> > +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > +	 * by the display at its output. This does not include readind EDID
> > +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > +	 */
> > +	DRM_BRIDGE_OP_MODES = BIT(3),
> > +};
> > +
> >  /**
> >   * struct drm_bridge - central DRM bridge control structure
> >   */
> > @@ -398,6 +535,29 @@ struct drm_bridge {
> >  	const struct drm_bridge_funcs *funcs;
> >  	/** @driver_private: pointer to the bridge driver's internal context */
> >  	void *driver_private;
> > +	/** @ops: bitmask of operations supported by the bridge */
> > +	enum drm_bridge_ops ops;
> > +	/**
> > +	 * @type: Type of the connection at the bridge output
> > +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > +	 * identifies the type of connected display.
> > +	 */
> > +	int type;
> > +	/** private: */
> > +	/**
> > +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > +	 */
> > +	struct mutex hpd_mutex;
> > +	/**
> > +	 * @hpd_cb: Hot plug detection callback, registered with
> > +	 * drm_bridge_hpd_enable().
> > +	 */
> > +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > +	/**
> > +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > +	 * @hpd_cb.
> > +	 */
> > +	void *hpd_data;
> >  };
> >  
> >  void drm_bridge_add(struct drm_bridge *bridge);
> > @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >  			      struct drm_atomic_state *state);
> >  
> > +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > +			   void (*cb)(void *data,
> > +				      enum drm_connector_status status),
> > +			   void *data);
> > +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > +			   enum drm_connector_status status);
> > +
> >  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >  					u32 connector_type);
> 
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel
  2019-07-07 18:07 [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Laurent Pinchart
                   ` (3 preceding siblings ...)
  2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
@ 2019-07-11  7:37 ` Daniel Vetter
  2019-07-11 11:59   ` Sebastian Reichel
  2019-08-08 14:31   ` Laurent Pinchart
  4 siblings, 2 replies; 166+ messages in thread
From: Daniel Vetter @ 2019-07-11  7:37 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Sun, Jul 07, 2019 at 09:07:52PM +0300, Laurent Pinchart wrote:
> Hello,
> 
> This patch series (nearly, see [1]) completes the rework of the omapdrm
> driver to move to drm_bridge and drm_panel.
> 
> What a journey. This work was started more than a year ago, and this
> last piece is perhaps the one that will generate the most bikeshedding
> as it touches the DRM core. I'm braced for the impact, but please be
> gentle :-)
> 
> Let's start with some context to understand the problem. omapdrm
> contains custom drivers for external encoders, panels and connectors
> (collectively referred to as display drivers). It combines them to
> create output pipelines abstracted by a drm_encoder and a drm_connector,
> with the ability to delegate the encoder and connector operations to the
> component in the pipeline that implements them. For instance, for an
> HDMI output pipeline, the hot plug detection and the EDID read can be
> implemented by two different components, when they are handled by two
> different devices at the hardware level.
> 
> DRM/KMS uses drm_bridge and drm_panel to abstract external encoders and
> panels. The model is however simpler than what omapdrm provides, as
> bridges were designed to be simple add-ons at the output of a
> drm_encoder. The ability to chain bridges exists, but a bridge driver
> hardcodes in its design its position in the pipeline : bridges that
> expect to terminate the pipeline create a drm_connector, while bridges
> that expect to be an intermediate component in the pipeline do not
> create a connector. In addition to not supporting bridges that can be
> either internal or a termination point in the pipeline depending on the
> hardware design, implementing the drm_connector inside a bridge driver
> makes it impossible to support hardware where bridge operations are
> handled by different hardware components, as explained above.
> 
> The omapdrm driver has received support for drm_bridge and drm_panel,
> but these issues prevented completely moving away from the omapdrm
> custom display drivers. This patch series thus first reworks the
> drm_bridge infrastructure to support the omapdrm use cases, and then
> transitions the omapdrm driver.
> 
> The series starts by 01/60 that adds a new flag to the drm_display_info
> structure to identify HDMI sinks. This is a feature needed by the OMAP4
> and OMAP5 HDMI encoders, and I believe it can be useful to other HDMI
> encoders as well. 02/60 is then a small drive-by cleanup.
> 
> The first sizeable change follows with the rename of the dumb-vga-dac
> driver to simple-bridge (03/60 and 04/60) and support for non-VGA
> bridges (05/60). This doesn't change the spirit of the driver that still
> focusses on transparent bridges, but prepares it to support an analog
> video amplifier. Patches 06/60 then add support for an enable GPIO, and
> 07/60 support for the OPA362 video amplifier itself.
> 
> The next two patches address the drm_bridge issues explained above.
> Patch 08/60 makes it possible to attach to a bridge without having the
> bridge create a connector. The connector is expected to be created by
> the display controller driver. Patch 09/60 adds connector-related
> operations to drm_bridge to make this possible.
> 
> The approach taken here is slightly intrusive as path 08/60 adds a
> parameter to tbe bridge .attach() operation, and thus touches all bridge
> drivers, even if the changes are very simple (as a consequence I haven't
> CC'ed all the individual bridge maintainers as the CC list was too
> large). Other options may be possible, what matters most to me is the
> feature, not so much its implementation. Please note that I envision the
> parameter to be removed down the road once all bridge drivers will be
> converted to the new model (but this will likely take time, and both
> models can co-exist for as long as necessary).
> 
> The next six patches make use of these new features: patches 10/60 and
> 11/60 add new bridge drivers for display connectors and for the TI
> TPD12S015 HDMI level shifter respectively, patch 12/60 supports the new
> API in the panel bridge driver, and patches 13/60 to 15/60 do the same
> in the ti-tfp410 driver.
> 
> The nine patches that follow add support for six new panels, with the
> related DT bindings (16/60 to 18/60) and the drm_panel drivers (19/60 to
> 24/60). The code originates from the corresponding omapdrm-specific
> panel drivers (which explains why only three DT patches are needed as
> most of the bindings are already present).
> 
> Patch 25/60 is possibly the most remarkable one in the series, with the
> drm_bridge operations extension, as it provides a helper for display
> controller drivers to construct a drm_connector entirerly backed by a
> chain of bridges. This offsets the complexity of the additional bridge
> operations by handling it all in a single place. An example usage for
> omapdrm can be found in patch 43/60. Don't let its diffstat mislead you,
> usage of the helper would remove lots of code if it wasn't for the fact
> that the legacy implementation still has to be kept for the DSI panel
> (see [1]). Down the road this helper and the new operation paradigm
> should remove code from both display controller and bridge drivers.
> 
> The rest of the series is omapdrm-focussed, slowly preparing the driver
> for the switch to drm_bridge drivers using the new helper (43/60), the
> removal of the omapdrm-specific display drivers (44/60 and 50/60), and
> lots of simplification and code removal in the other patches.

git branch pls, thanks.

> [1] The only notable exception is the omapdrm-specific DSI panel driver
> that implements a large number of custom operations. This should be
> addressed separately.

DSI tends to be fairly custom in all drivers, I think that's totally fine.
Maybe not forever, but we have a lot worse crimes in our codebase than
that :-)

-Daniel

> 
> Laurent Pinchart (60):
>   drm/edid: Add flag to drm_display_info to identify HDMI sinks
>   video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
>   drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge
>   drm/bridge: dumb-vga-dac: Rename driver to simple-bridge
>   drm/bridge: simple-bridge: Add support for non-VGA bridges
>   drm/bridge: simple-bridge: Add support for enable GPIO
>   drm/bridge: simple-bridge: Add support for the TI OP362
>   drm/bridge: Extend bridge API to disable connector creation
>   drm/bridge: Add connector-related bridge operations and data
>   drm/bridge: Add bridge driver for display connectors
>   drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter
>   drm/bridge: panel: Implement bridge connector operations
>   drm/bridge: tfp410: Don't include drmP.h
>   drm/bridge: tfp410: Replace manual connector handling with bridge
>   drm/bridge: tfp410: Allow operation without drm_connector
>   dt-bindings: Add vendor prefix for LG Display
>   dt-bindings: Add legacy 'toppoly' vendor prefix
>   dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel
>   drm/panel: Add driver for the LG Philips LB035Q02 panel
>   drm/panel: Add driver for the NEC NL8048HL11 panel
>   drm/panel: Add driver for the Sharp LS037V7DW01 panel
>   drm/panel: Add driver for the Sony ACX565AKM panel
>   drm/panel: Add driver for the Toppology TD028TTEC1 panel
>   drm/panel: Add driver for the Toppology TD043MTEA1 panel
>   drm: Add helper to create a connector for a chain of bridges
>   drm/omap: Detach from panels at remove time
>   drm/omap: Simplify HDMI mode and infoframe configuration
>   drm/omap: Factor out display type to connector type conversion
>   drm/omap: Use the drm_panel_bridge API
>   drm/omap: dss: Fix output next device lookup in DT
>   drm/omap: Add infrastructure to support drm_bridge local to DSS
>     outputs
>   drm/omap: dss: Make omap_dss_device_ops optional
>   drm/omap: hdmi: Allocate EDID in the .read_edid() operation
>   drm/omap: hdmi4: Rework EDID read to isolate data read
>   drm/omap: hdmi5: Rework EDID read to isolate data read
>   drm/omap: hdmi4: Register a drm_bridge for EDID read
>   drm/omap: hdmi5: Register a drm_bridge for EDID read
>   drm/omap: hdmi4: Move mode set, enable and disable operations to
>     bridge
>   drm/omap: hdmi5: Move mode set, enable and disable operations to
>     bridge
>   drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation
>   drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations
>   drm/omap: venc: Register a drm_bridge
>   drm/omap: Create connector for bridges
>   drm/omap: Switch the HDMI and VENC outputs to drm_bridge
>   drm/omap: Remove HPD, detect and EDID omapdss operations
>   drm/omap: hdmi: Remove omap_dss_device operations
>   drm/omap: venc: Remove omap_dss_device operations
>   drm/omap: hdmi4: Simplify EDID read
>   drm/omap: hdmi5: Simplify EDID read
>   drm/omap: displays: Remove unused panel drivers
>   drm/omap: dpi: Sort includes alphabetically
>   drm/omap: dpi: Reorder functions in sections
>   drm/omap: dpi: Simplify clock setting API
>   drm/omap: dpi: Register a drm_bridge
>   drm/omap: sdi: Sort includes alphabetically
>   drm/omap: sdi: Register a drm_bridge
>   drm/omap: Simplify connector implementation
>   drm/omap: dss: Remove unused omap_dss_device operations
>   drm/omap: dss: Inline the omapdss_display_get() function
>   drm/omap: dss: Remove unused omapdss_of_find_connected_device()
>     function
> 
>  .../bindings/display/panel/nec,nl8048hl11.txt |  38 +
>  .../devicetree/bindings/vendor-prefixes.yaml  |   4 +
>  arch/arm/configs/davinci_all_defconfig        |   2 +-
>  arch/arm/configs/integrator_defconfig         |   2 +-
>  arch/arm/configs/multi_v7_defconfig           |   2 +-
>  arch/arm/configs/shmobile_defconfig           |   2 +-
>  arch/arm/configs/sunxi_defconfig              |   2 +-
>  arch/arm/configs/versatile_defconfig          |   2 +-
>  drivers/gpu/drm/Makefile                      |   3 +-
>  drivers/gpu/drm/arc/arcpgu_hdmi.c             |   2 +-
>  .../gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c  |   2 +-
>  drivers/gpu/drm/bridge/Kconfig                |  29 +-
>  drivers/gpu/drm/bridge/Makefile               |   4 +-
>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c  |   6 +-
>  drivers/gpu/drm/bridge/analogix-anx78xx.c     |   6 +-
>  .../drm/bridge/analogix/analogix_dp_core.c    |   8 +-
>  drivers/gpu/drm/bridge/cdns-dsi.c             |   6 +-
>  drivers/gpu/drm/bridge/display-connector.c    | 327 ++++++++
>  drivers/gpu/drm/bridge/dumb-vga-dac.c         | 296 -------
>  drivers/gpu/drm/bridge/lvds-encoder.c         |   4 +-
>  .../bridge/megachips-stdpxxxx-ge-b850v3-fw.c  |   6 +-
>  drivers/gpu/drm/bridge/nxp-ptn3460.c          |   6 +-
>  drivers/gpu/drm/bridge/panel.c                |  21 +-
>  drivers/gpu/drm/bridge/parade-ps8622.c        |   5 +-
>  drivers/gpu/drm/bridge/sii902x.c              |   6 +-
>  drivers/gpu/drm/bridge/sil-sii8620.c          |   2 +-
>  drivers/gpu/drm/bridge/simple-bridge.c        | 337 ++++++++
>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   8 +-
>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c |   8 +-
>  drivers/gpu/drm/bridge/tc358764.c             |   5 +-
>  drivers/gpu/drm/bridge/tc358767.c             |   5 +-
>  drivers/gpu/drm/bridge/thc63lvd1024.c         |   5 +-
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c         |   5 +-
>  drivers/gpu/drm/bridge/ti-tfp410.c            | 202 ++---
>  drivers/gpu/drm/bridge/ti-tpd12s015.c         | 204 +++++
>  drivers/gpu/drm/drm_bridge.c                  |  97 ++-
>  drivers/gpu/drm/drm_bridge_connector.c        | 385 +++++++++
>  drivers/gpu/drm/drm_edid.c                    |   8 +-
>  drivers/gpu/drm/drm_simple_kms_helper.c       |   2 +-
>  drivers/gpu/drm/exynos/exynos_dp.c            |   3 +-
>  drivers/gpu/drm/exynos/exynos_drm_dsi.c       |   4 +-
>  drivers/gpu/drm/exynos/exynos_hdmi.c          |   2 +-
>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c     |   2 +-
>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c  |   2 +-
>  drivers/gpu/drm/i2c/tda998x_drv.c             |   8 +-
>  drivers/gpu/drm/imx/imx-ldb.c                 |   2 +-
>  drivers/gpu/drm/imx/parallel-display.c        |   2 +-
>  drivers/gpu/drm/mcde/mcde_dsi.c               |   6 +-
>  drivers/gpu/drm/mediatek/mtk_dpi.c            |   2 +-
>  drivers/gpu/drm/mediatek/mtk_dsi.c            |   2 +-
>  drivers/gpu/drm/mediatek/mtk_hdmi.c           |   8 +-
>  drivers/gpu/drm/msm/dsi/dsi_manager.c         |   4 +-
>  drivers/gpu/drm/msm/edp/edp_bridge.c          |   2 +-
>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c        |   2 +-
>  drivers/gpu/drm/omapdrm/displays/Kconfig      |  60 --
>  drivers/gpu/drm/omapdrm/displays/Makefile     |  10 -
>  .../omapdrm/displays/connector-analog-tv.c    | 100 ---
>  .../gpu/drm/omapdrm/displays/connector-hdmi.c | 186 -----
>  .../gpu/drm/omapdrm/displays/encoder-opa362.c | 140 ----
>  .../drm/omapdrm/displays/encoder-tpd12s015.c  | 220 -----
>  .../gpu/drm/omapdrm/displays/panel-dsi-cm.c   |   2 +-
>  .../displays/panel-lgphilips-lb035q02.c       | 254 ------
>  .../omapdrm/displays/panel-nec-nl8048hl11.c   | 271 -------
>  .../displays/panel-sharp-ls037v7dw01.c        | 265 ------
>  .../omapdrm/displays/panel-sony-acx565akm.c   | 766 ------------------
>  .../omapdrm/displays/panel-tpo-td028ttec1.c   | 401 ---------
>  .../omapdrm/displays/panel-tpo-td043mtea1.c   | 513 ------------
>  drivers/gpu/drm/omapdrm/dss/Makefile          |   2 +-
>  drivers/gpu/drm/omapdrm/dss/base.c            |  72 +-
>  drivers/gpu/drm/omapdrm/dss/display.c         |   9 -
>  drivers/gpu/drm/omapdrm/dss/dpi.c             | 336 ++++----
>  drivers/gpu/drm/omapdrm/dss/dsi.c             |   4 +-
>  drivers/gpu/drm/omapdrm/dss/dss-of.c          |  28 -
>  drivers/gpu/drm/omapdrm/dss/dss.c             |   3 +-
>  drivers/gpu/drm/omapdrm/dss/hdmi.h            |   4 +-
>  drivers/gpu/drm/omapdrm/dss/hdmi4.c           | 321 ++++----
>  drivers/gpu/drm/omapdrm/dss/hdmi4_core.c      |  59 +-
>  drivers/gpu/drm/omapdrm/dss/hdmi4_core.h      |   4 +-
>  drivers/gpu/drm/omapdrm/dss/hdmi5.c           | 303 +++----
>  drivers/gpu/drm/omapdrm/dss/hdmi5_core.c      |  48 +-
>  drivers/gpu/drm/omapdrm/dss/hdmi5_core.h      |   5 +-
>  .../gpu/drm/omapdrm/dss/omapdss-boot-init.c   |  12 -
>  drivers/gpu/drm/omapdrm/dss/omapdss.h         |  47 +-
>  drivers/gpu/drm/omapdrm/dss/output.c          |  55 +-
>  drivers/gpu/drm/omapdrm/dss/sdi.c             | 187 +++--
>  drivers/gpu/drm/omapdrm/dss/venc.c            | 269 +++---
>  drivers/gpu/drm/omapdrm/omap_connector.c      | 246 +-----
>  drivers/gpu/drm/omapdrm/omap_connector.h      |   3 -
>  drivers/gpu/drm/omapdrm/omap_drv.c            |  98 ++-
>  drivers/gpu/drm/omapdrm/omap_encoder.c        |  83 +-
>  drivers/gpu/drm/panel/Kconfig                 |  44 +
>  drivers/gpu/drm/panel/Makefile                |   6 +
>  drivers/gpu/drm/panel/panel-lg-lb035q02.c     | 235 ++++++
>  drivers/gpu/drm/panel/panel-nec-nl8048hl11.c  | 249 ++++++
>  .../gpu/drm/panel/panel-sharp-ls037v7dw01.c   | 231 ++++++
>  drivers/gpu/drm/panel/panel-sony-acx565akm.c  | 691 ++++++++++++++++
>  drivers/gpu/drm/panel/panel-tpo-td028ttec1.c  | 382 +++++++++
>  drivers/gpu/drm/panel/panel-tpo-td043mtea1.c  | 510 ++++++++++++
>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c     |   2 +-
>  drivers/gpu/drm/rcar-du/rcar_lvds.c           |   7 +-
>  drivers/gpu/drm/rockchip/rockchip_lvds.c      |   2 +-
>  drivers/gpu/drm/rockchip/rockchip_rgb.c       |   2 +-
>  drivers/gpu/drm/sti/sti_dvo.c                 |   2 +-
>  drivers/gpu/drm/sti/sti_hda.c                 |   2 +-
>  drivers/gpu/drm/sti/sti_hdmi.c                |   2 +-
>  drivers/gpu/drm/stm/ltdc.c                    |   2 +-
>  drivers/gpu/drm/sun4i/sun4i_lvds.c            |   2 +-
>  drivers/gpu/drm/sun4i/sun4i_rgb.c             |   2 +-
>  drivers/gpu/drm/tilcdc/tilcdc_external.c      |   2 +-
>  drivers/gpu/drm/vc4/vc4_dpi.c                 |   2 +-
>  drivers/gpu/drm/vc4/vc4_dsi.c                 |   2 +-
>  drivers/video/hdmi.c                          |   9 +-
>  include/drm/drm_bridge.h                      | 174 +++-
>  include/drm/drm_bridge_connector.h            |  18 +
>  include/drm/drm_connector.h                   |   5 +
>  include/linux/hdmi.h                          |   2 +-
>  116 files changed, 5188 insertions(+), 4900 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt
>  create mode 100644 drivers/gpu/drm/bridge/display-connector.c
>  delete mode 100644 drivers/gpu/drm/bridge/dumb-vga-dac.c
>  create mode 100644 drivers/gpu/drm/bridge/simple-bridge.c
>  create mode 100644 drivers/gpu/drm/bridge/ti-tpd12s015.c
>  create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
>  delete mode 100644 drivers/gpu/drm/omapdrm/dss/dss-of.c
>  create mode 100644 drivers/gpu/drm/panel/panel-lg-lb035q02.c
>  create mode 100644 drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
>  create mode 100644 drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
>  create mode 100644 drivers/gpu/drm/panel/panel-sony-acx565akm.c
>  create mode 100644 drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
>  create mode 100644 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
>  create mode 100644 include/drm/drm_bridge_connector.h
> 
> -- 
> Regards,
> 
> Laurent Pinchart
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel
  2019-07-11  7:37 ` [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Daniel Vetter
@ 2019-07-11 11:59   ` Sebastian Reichel
  2019-08-08 14:26     ` Laurent Pinchart
  2019-08-08 14:31   ` Laurent Pinchart
  1 sibling, 1 reply; 166+ messages in thread
From: Sebastian Reichel @ 2019-07-11 11:59 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, dri-devel, Tomi Valkeinen, Laurent Pinchart, Sean Paul


[-- Attachment #1.1: Type: text/plain, Size: 921 bytes --]

Hi,

On Thu, Jul 11, 2019 at 09:37:26AM +0200, Daniel Vetter wrote:
> > [1] The only notable exception is the omapdrm-specific DSI panel driver
> > that implements a large number of custom operations. This should be
> > addressed separately.
> 
> DSI tends to be fairly custom in all drivers, I think that's totally fine.
> Maybe not forever, but we have a lot worse crimes in our codebase than
> that :-)

I have a WIP branch, which moves omapdrm DSI to mipi_dsi_driver and
drm_panel:

https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-n900.git/log/?h=omapdrm-5.2-with-dsi-untested-work-branch

The name is a bit misleading, since it is tested now. HEAD~2, which
moves the last custom operation (panel update for DSI command mode)
from the panel driver to the DSI core unfortunatley does not yet work.
I'm still investigating the reason. Anyways - this is being worked
on :)

-- Sebastian

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-11  7:35       ` Daniel Vetter
@ 2019-07-11 12:41         ` Andrzej Hajda
  2019-07-11 13:18           ` Daniel Vetter
  2019-08-08 18:19         ` Laurent Pinchart
  1 sibling, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-11 12:41 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On 11.07.2019 09:35, Daniel Vetter wrote:
> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>> Hi Laurent,
>>
>>
>> I like the approach, current practice when almost every bridge should
>> optionally implement connector, or alternatively downstream bridge or
>> panel is very painful.
> Yeah I think this looks mostly reasonable. Some api design comments on top
> of Andrzej', with the fair warning that I didn't bother to read up on how
> it's all used in the end. I probably should go and do that, at least to
> get a feeling for what your hpd_cb usually does.
>
>> More comments inlined.
>>
>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>> To support implementation of DRM connectors on top of DRM bridges
>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>> data:
>>>
>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>   retrieval operations
>>> - Bitmask of supported operations
>>
>> Why do we need these bitmask at all? Why cannot we rely on presence of
>> operation's callback?
> Yeah also not a huge fan of these bitmasks. Smells like
> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> add, generally good excuse to not have to think through the design between
> different parts of drivers - "just" add another flag.
>>
>>> - Bridge output type
>>>
>>> Add and document these.
>>>
>>> Three new bridge helper functions are also added to handle hot plug
>>> notification in a way that is as transparent as possible for the
>>> bridges.
>>
>> Documentation of new opses does not explain how it should cooperate with
>> bridge chaining, I suppose they should be chained explicitly, am I
>> right? More comments about it later.
>>
>>
>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> ---
>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>> index 519577f363e3..3c2a255df7af 100644
>>> --- a/drivers/gpu/drm/drm_bridge.c
>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>   */
>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>  {
>>> +	mutex_init(&bridge->hpd_mutex);
>>> +
>>>  	mutex_lock(&bridge_lock);
>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>  	mutex_unlock(&bridge_lock);
>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>  	mutex_lock(&bridge_lock);
>>>  	list_del_init(&bridge->list);
>>>  	mutex_unlock(&bridge_lock);
>>> +
>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>  }
>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>  
>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>  }
>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>  
>>> +/**
>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>> + * @bridge: bridge control structure
>>> + * @cb: hot-plug detection callback
>>> + * @data: data to be passed to the hot-plug detection callback
>>> + *
>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>> + * hot plug notification callback. From now on the @cb will be called with
>>> + * @data when an output status change is detected by the bridge, until hot plug
>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>> + *
>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>> + *
>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>> + * error to call this function when hot plug detection is already enabled for
>>> + * the bridge.
>>> + */
>>
>> To simplify architecture maybe would be better to enable hpd just on
>> bridge attach:
>>
>> bridge->hpd_cb = cb;
>>
>> bridge->hpd_data = data;
>>
>> ret = drm_bridge_attach(...);
> Yeah I like this more. The other problem here is, what if you need more
> than 1 callback registers on the same bridge hdp signal?
>
>
>> This way we could avoid adding new callbacks hpd_(enable|disable)
>> without big sacrifices.
>>
>>
>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>> notifies about sink status change, how it translates to this cb?
>>
>>
>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>> +			   void (*cb)(void *data,
>>> +				      enum drm_connector_status status),
>>> +			   void *data)
>>> +{
>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>> +		return;
>>> +
>>> +	mutex_lock(&bridge->hpd_mutex);
>>> +
>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>> +		goto unlock;
>>> +
>>> +	bridge->hpd_cb = cb;
>>> +	bridge->hpd_data = data;
>>> +
>>> +	bridge->funcs->hpd_enable(bridge);
>>> +
>>> +unlock:
>>> +	mutex_unlock(&bridge->hpd_mutex);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>> +
>>> +/**
>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>> + * @bridge: bridge control structure
>>> + *
>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>> + * function returns the callback will not be called by the bridge when an
>>> + * output status change occurs.
>>> + *
>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>> + */
>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>> +{
>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>> +		return;
>>> +
>>> +	mutex_lock(&bridge->hpd_mutex);
>>> +	bridge->funcs->hpd_disable(bridge);
>>> +
>>> +	bridge->hpd_cb = NULL;
>>> +	bridge->hpd_data = NULL;
>>> +	mutex_unlock(&bridge->hpd_mutex);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>> +
>>> +/**
>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>> + * @bridge: bridge control structure
>>> + * @status: output connection status
>>> + *
>>> + * Bridge drivers shall call this function to report hot plug events when they
>>> + * detect a change in the output status, when hot plug detection has been
>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>> + *
>>> + * This function shall be called in a context that can sleep.
>>> + */
>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>> +			   enum drm_connector_status status)
>>> +{
>>> +	mutex_lock(&bridge->hpd_mutex);
>>> +	if (bridge->hpd_cb)
>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> So this isn't quite what I had in mind. Instead something like this:
>
> 	/* iterates over all bridges in the chain containing @bridge */
> 	for_each_bridge(tmp_bridge, bridge) {
> 		if (tmp_bridge == bridge)
> 			continue;
> 		if (bridge->hpd_notify);
> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> 	}
>
> 	encoder = encoder_for_bridge(bridge);
> 	if (encoder->helper_private->bridge_hpd_notify)
> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>
> 	dev = bridge->dev
> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>
> No register callback needed, no locking needed, everyone gets exactly the
> hpd they want/need.


As I understand you want to notify every member of the pipeline.

I think it should be enough to notify only the source, and then source
should decide if/when the hpd should be propagated upstream.

It looks more generic for me.


Regards

Andrzej


>
>>> +	mutex_unlock(&bridge->hpd_mutex);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>> +
>>>  #ifdef CONFIG_OF
>>>  /**
>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>> --- a/include/drm/drm_bridge.h
>>> +++ b/include/drm/drm_bridge.h
>>> @@ -23,8 +23,9 @@
>>>  #ifndef __DRM_BRIDGE_H__
>>>  #define __DRM_BRIDGE_H__
>>>  
>>> -#include <linux/list.h>
>>>  #include <linux/ctype.h>
>>> +#include <linux/list.h>
>>> +#include <linux/mutex.h>
>>>  #include <drm/drm_mode_object.h>
>>>  #include <drm/drm_modes.h>
>>>  
>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>  	 */
>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>  				    struct drm_atomic_state *state);
>>> +
>>> +	/**
>>> +	 * @detect:
>>> +	 *
>>> +	 * Check if anything is attached to the bridge output.
>>> +	 *
>>> +	 * This callback is optional, if not implemented the bridge will be
>>> +	 * considered as always having a component attached to its output.
>>> +	 * Bridges that implement this callback shall set the
>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>> +	 *
>>> +	 * RETURNS:
>>> +	 *
>>> +	 * drm_connector_status indicating the bridge output status.
>>> +	 */
>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>> +
>>> +	/**
>>> +	 * @get_modes:
>>> +	 *
>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>> +	 * with drm_mode_probed_add().
>>> +	 *
>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>> +	 * displays such as many fixed panels. Bridges that support reading
>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>> +	 *
>>> +	 * This callback is optional. Bridges that implement it shall set the
>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>> +	 *
>>> +	 * RETURNS:
>>> +	 *
>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>> +	 */
>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>> +			 struct drm_connector *connector);
>>> +
>>> +	/**
>>> +	 * @get_edid:
>>> +	 *
>>> +	 * Read and parse the EDID data of the connected display.
>>> +	 *
>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>> +	 * information for a display connected to the bridge output. Bridges
>>> +	 * that support readind EDID shall implement this callback and leave
>>> +	 * the @get_modes callback unimplemented.
>>> +	 *
>>> +	 * The caller of this operation shall first verify the output
>>> +	 * connection status and refrain from reading EDID from a disconnected
>>> +	 * output.
>>> +	 *
>>> +	 * This callback is optional. Bridges that implement it shall set the
>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>> +	 *
>>> +	 * RETURNS:
>>> +	 *
>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>> +	 * the returned edid structure with kfree().
>>> +	 */
>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>> +				 struct drm_connector *connector);
>>
>> It overlaps with get_modes, I guess presence of one ops should disallow
>> presence of another one?
>>
>> I am not really convinced we need this op at all, cannot we just assign
>> some helper function to .get_modes cb, which will do the same?
> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> case, and require that if it has an edid it must fill out connector->info
> and connector->edid correctly.
>
> Btw if a hpd happens, who's responible for making sure the edid/mode list
> in the connector is up-to-date? With your current callback design that's
> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> should guarantee that it'll first walk the connectors to update status and
> edid/mode list for the final drm_connector. And then instead of just
> passing the simple "status", it'll pass the connector, with everything
> correctly updated.
>
> Otherwise everyone interested in that hpd signal will go and re-fetch the
> edid, which is not so awesome :-)
> -Daniel
>
>>
>> Regards
>>
>> Andrzej
>>
>>
>>> +
>>> +	/**
>>> +	 * @lost_hotplug:
>>> +	 *
>>> +	 * Notify the bridge of display disconnection.
>>> +	 *
>>> +	 * This callback is optional, it may be implemented by bridges that
>>> +	 * need to be notified of display disconnection for internal reasons.
>>> +	 * One use case is to reset the internal state of CEC controllers for
>>> +	 * HDMI bridges.
>>> +	 */
>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>> +
>>> +	/**
>>> +	 * @hpd_enable:
>>> +	 *
>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>> +	 * connection status, until hot plug detection gets disabled with
>>> +	 * @hpd_disable.
>>> +	 *
>>> +	 * This callback is optional and shall only be implemented by bridges
>>> +	 * that support hot-plug notification without polling. Bridges that
>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>> +	 */
>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>> +
>>> +	/**
>>> +	 * @hpd_disable:
>>> +	 *
>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>> +	 * connection status occurs.
>>> +	 *
>>> +	 * This callback is optional and shall only be implemented by bridges
>>> +	 * that support hot-plug notification without polling. Bridges that
>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>> +	 */
>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>  };
>>>  
>>>  /**
>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>  	bool dual_link;
>>>  };
>>>  
>>> +/**
>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>> + */
>>> +enum drm_bridge_ops {
>>> +	/**
>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>> +	 * its output. Bridges that set this flag shall implement the
>>> +	 * &drm_bridge_funcs->detect callback.
>>> +	 */
>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>> +	/**
>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>> +	 * connected to its output. Bridges that set this flag shall implement
>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>> +	 */
>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>> +	/**
>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>> +	 * without requiring polling. Bridges that set this flag shall
>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>> +	 */
>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>> +	/**
>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>> +	 * by the display at its output. This does not include readind EDID
>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>> +	 */
>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>> +};
>>> +
>>>  /**
>>>   * struct drm_bridge - central DRM bridge control structure
>>>   */
>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>  	const struct drm_bridge_funcs *funcs;
>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>  	void *driver_private;
>>> +	/** @ops: bitmask of operations supported by the bridge */
>>> +	enum drm_bridge_ops ops;
>>> +	/**
>>> +	 * @type: Type of the connection at the bridge output
>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>> +	 * identifies the type of connected display.
>>> +	 */
>>> +	int type;
>>> +	/** private: */
>>> +	/**
>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>> +	 */
>>> +	struct mutex hpd_mutex;
>>> +	/**
>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>> +	 * drm_bridge_hpd_enable().
>>> +	 */
>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>> +	/**
>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>> +	 * @hpd_cb.
>>> +	 */
>>> +	void *hpd_data;
>>>  };
>>>  
>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>  			      struct drm_atomic_state *state);
>>>  
>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>> +			   void (*cb)(void *data,
>>> +				      enum drm_connector_status status),
>>> +			   void *data);
>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>> +			   enum drm_connector_status status);
>>> +
>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>  					u32 connector_type);
>>

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-11 12:41         ` Andrzej Hajda
@ 2019-07-11 13:18           ` Daniel Vetter
  2019-07-11 15:12             ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-07-11 13:18 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> On 11.07.2019 09:35, Daniel Vetter wrote:
> > On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >> Hi Laurent,
> >>
> >>
> >> I like the approach, current practice when almost every bridge should
> >> optionally implement connector, or alternatively downstream bridge or
> >> panel is very painful.
> > Yeah I think this looks mostly reasonable. Some api design comments on top
> > of Andrzej', with the fair warning that I didn't bother to read up on how
> > it's all used in the end. I probably should go and do that, at least to
> > get a feeling for what your hpd_cb usually does.
> >
> >> More comments inlined.
> >>
> >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>> To support implementation of DRM connectors on top of DRM bridges
> >>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>> data:
> >>>
> >>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>   retrieval operations
> >>> - Bitmask of supported operations
> >>
> >> Why do we need these bitmask at all? Why cannot we rely on presence of
> >> operation's callback?
> > Yeah also not a huge fan of these bitmasks. Smells like
> > DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > add, generally good excuse to not have to think through the design between
> > different parts of drivers - "just" add another flag.
> >>
> >>> - Bridge output type
> >>>
> >>> Add and document these.
> >>>
> >>> Three new bridge helper functions are also added to handle hot plug
> >>> notification in a way that is as transparent as possible for the
> >>> bridges.
> >>
> >> Documentation of new opses does not explain how it should cooperate with
> >> bridge chaining, I suppose they should be chained explicitly, am I
> >> right? More comments about it later.
> >>
> >>
> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> ---
> >>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>> index 519577f363e3..3c2a255df7af 100644
> >>> --- a/drivers/gpu/drm/drm_bridge.c
> >>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>   */
> >>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>  {
> >>> +	mutex_init(&bridge->hpd_mutex);
> >>> +
> >>>  	mutex_lock(&bridge_lock);
> >>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>  	mutex_unlock(&bridge_lock);
> >>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>  	mutex_lock(&bridge_lock);
> >>>  	list_del_init(&bridge->list);
> >>>  	mutex_unlock(&bridge_lock);
> >>> +
> >>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>  }
> >>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>  
> >>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>  }
> >>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>  
> >>> +/**
> >>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>> + * @bridge: bridge control structure
> >>> + * @cb: hot-plug detection callback
> >>> + * @data: data to be passed to the hot-plug detection callback
> >>> + *
> >>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>> + * hot plug notification callback. From now on the @cb will be called with
> >>> + * @data when an output status change is detected by the bridge, until hot plug
> >>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>> + *
> >>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>> + *
> >>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>> + * error to call this function when hot plug detection is already enabled for
> >>> + * the bridge.
> >>> + */
> >>
> >> To simplify architecture maybe would be better to enable hpd just on
> >> bridge attach:
> >>
> >> bridge->hpd_cb = cb;
> >>
> >> bridge->hpd_data = data;
> >>
> >> ret = drm_bridge_attach(...);
> > Yeah I like this more. The other problem here is, what if you need more
> > than 1 callback registers on the same bridge hdp signal?
> >
> >
> >> This way we could avoid adding new callbacks hpd_(enable|disable)
> >> without big sacrifices.
> >>
> >>
> >> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >> notifies about sink status change, how it translates to this cb?
> >>
> >>
> >>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>> +			   void (*cb)(void *data,
> >>> +				      enum drm_connector_status status),
> >>> +			   void *data)
> >>> +{
> >>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>> +		return;
> >>> +
> >>> +	mutex_lock(&bridge->hpd_mutex);
> >>> +
> >>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>> +		goto unlock;
> >>> +
> >>> +	bridge->hpd_cb = cb;
> >>> +	bridge->hpd_data = data;
> >>> +
> >>> +	bridge->funcs->hpd_enable(bridge);
> >>> +
> >>> +unlock:
> >>> +	mutex_unlock(&bridge->hpd_mutex);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>> +
> >>> +/**
> >>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>> + * @bridge: bridge control structure
> >>> + *
> >>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>> + * function returns the callback will not be called by the bridge when an
> >>> + * output status change occurs.
> >>> + *
> >>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>> + */
> >>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>> +{
> >>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>> +		return;
> >>> +
> >>> +	mutex_lock(&bridge->hpd_mutex);
> >>> +	bridge->funcs->hpd_disable(bridge);
> >>> +
> >>> +	bridge->hpd_cb = NULL;
> >>> +	bridge->hpd_data = NULL;
> >>> +	mutex_unlock(&bridge->hpd_mutex);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>> +
> >>> +/**
> >>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>> + * @bridge: bridge control structure
> >>> + * @status: output connection status
> >>> + *
> >>> + * Bridge drivers shall call this function to report hot plug events when they
> >>> + * detect a change in the output status, when hot plug detection has been
> >>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>> + *
> >>> + * This function shall be called in a context that can sleep.
> >>> + */
> >>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>> +			   enum drm_connector_status status)
> >>> +{
> >>> +	mutex_lock(&bridge->hpd_mutex);
> >>> +	if (bridge->hpd_cb)
> >>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > So this isn't quite what I had in mind. Instead something like this:
> >
> > 	/* iterates over all bridges in the chain containing @bridge */
> > 	for_each_bridge(tmp_bridge, bridge) {
> > 		if (tmp_bridge == bridge)
> > 			continue;
> > 		if (bridge->hpd_notify);
> > 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > 	}
> >
> > 	encoder = encoder_for_bridge(bridge);
> > 	if (encoder->helper_private->bridge_hpd_notify)
> > 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >
> > 	dev = bridge->dev
> > 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >
> > No register callback needed, no locking needed, everyone gets exactly the
> > hpd they want/need.
> 
> 
> As I understand you want to notify every member of the pipeline.
> 
> I think it should be enough to notify only the source, and then source
> should decide if/when the hpd should be propagated upstream.
> 
> It looks more generic for me.

I'm not parsing ... do you think my idea is more generic and useful, or
the one from Laurent? Kinda confused here.
-Daniel

> 
> 
> Regards
> 
> Andrzej
> 
> 
> >
> >>> +	mutex_unlock(&bridge->hpd_mutex);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>> +
> >>>  #ifdef CONFIG_OF
> >>>  /**
> >>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>> --- a/include/drm/drm_bridge.h
> >>> +++ b/include/drm/drm_bridge.h
> >>> @@ -23,8 +23,9 @@
> >>>  #ifndef __DRM_BRIDGE_H__
> >>>  #define __DRM_BRIDGE_H__
> >>>  
> >>> -#include <linux/list.h>
> >>>  #include <linux/ctype.h>
> >>> +#include <linux/list.h>
> >>> +#include <linux/mutex.h>
> >>>  #include <drm/drm_mode_object.h>
> >>>  #include <drm/drm_modes.h>
> >>>  
> >>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>  	 */
> >>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>  				    struct drm_atomic_state *state);
> >>> +
> >>> +	/**
> >>> +	 * @detect:
> >>> +	 *
> >>> +	 * Check if anything is attached to the bridge output.
> >>> +	 *
> >>> +	 * This callback is optional, if not implemented the bridge will be
> >>> +	 * considered as always having a component attached to its output.
> >>> +	 * Bridges that implement this callback shall set the
> >>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>> +	 *
> >>> +	 * RETURNS:
> >>> +	 *
> >>> +	 * drm_connector_status indicating the bridge output status.
> >>> +	 */
> >>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>> +
> >>> +	/**
> >>> +	 * @get_modes:
> >>> +	 *
> >>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>> +	 * with drm_mode_probed_add().
> >>> +	 *
> >>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>> +	 * displays such as many fixed panels. Bridges that support reading
> >>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>> +	 *
> >>> +	 * This callback is optional. Bridges that implement it shall set the
> >>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>> +	 *
> >>> +	 * RETURNS:
> >>> +	 *
> >>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>> +	 */
> >>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>> +			 struct drm_connector *connector);
> >>> +
> >>> +	/**
> >>> +	 * @get_edid:
> >>> +	 *
> >>> +	 * Read and parse the EDID data of the connected display.
> >>> +	 *
> >>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>> +	 * information for a display connected to the bridge output. Bridges
> >>> +	 * that support readind EDID shall implement this callback and leave
> >>> +	 * the @get_modes callback unimplemented.
> >>> +	 *
> >>> +	 * The caller of this operation shall first verify the output
> >>> +	 * connection status and refrain from reading EDID from a disconnected
> >>> +	 * output.
> >>> +	 *
> >>> +	 * This callback is optional. Bridges that implement it shall set the
> >>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>> +	 *
> >>> +	 * RETURNS:
> >>> +	 *
> >>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>> +	 * the returned edid structure with kfree().
> >>> +	 */
> >>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>> +				 struct drm_connector *connector);
> >>
> >> It overlaps with get_modes, I guess presence of one ops should disallow
> >> presence of another one?
> >>
> >> I am not really convinced we need this op at all, cannot we just assign
> >> some helper function to .get_modes cb, which will do the same?
> > Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > case, and require that if it has an edid it must fill out connector->info
> > and connector->edid correctly.
> >
> > Btw if a hpd happens, who's responible for making sure the edid/mode list
> > in the connector is up-to-date? With your current callback design that's
> > up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > should guarantee that it'll first walk the connectors to update status and
> > edid/mode list for the final drm_connector. And then instead of just
> > passing the simple "status", it'll pass the connector, with everything
> > correctly updated.
> >
> > Otherwise everyone interested in that hpd signal will go and re-fetch the
> > edid, which is not so awesome :-)
> > -Daniel
> >
> >>
> >> Regards
> >>
> >> Andrzej
> >>
> >>
> >>> +
> >>> +	/**
> >>> +	 * @lost_hotplug:
> >>> +	 *
> >>> +	 * Notify the bridge of display disconnection.
> >>> +	 *
> >>> +	 * This callback is optional, it may be implemented by bridges that
> >>> +	 * need to be notified of display disconnection for internal reasons.
> >>> +	 * One use case is to reset the internal state of CEC controllers for
> >>> +	 * HDMI bridges.
> >>> +	 */
> >>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>> +
> >>> +	/**
> >>> +	 * @hpd_enable:
> >>> +	 *
> >>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>> +	 * connection status, until hot plug detection gets disabled with
> >>> +	 * @hpd_disable.
> >>> +	 *
> >>> +	 * This callback is optional and shall only be implemented by bridges
> >>> +	 * that support hot-plug notification without polling. Bridges that
> >>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>> +	 */
> >>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>> +
> >>> +	/**
> >>> +	 * @hpd_disable:
> >>> +	 *
> >>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>> +	 * connection status occurs.
> >>> +	 *
> >>> +	 * This callback is optional and shall only be implemented by bridges
> >>> +	 * that support hot-plug notification without polling. Bridges that
> >>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>> +	 */
> >>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>  };
> >>>  
> >>>  /**
> >>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>  	bool dual_link;
> >>>  };
> >>>  
> >>> +/**
> >>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>> + */
> >>> +enum drm_bridge_ops {
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>> +	 * its output. Bridges that set this flag shall implement the
> >>> +	 * &drm_bridge_funcs->detect callback.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>> +	 * connected to its output. Bridges that set this flag shall implement
> >>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>> +	 * without requiring polling. Bridges that set this flag shall
> >>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>> +	 * by the display at its output. This does not include readind EDID
> >>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>> +};
> >>> +
> >>>  /**
> >>>   * struct drm_bridge - central DRM bridge control structure
> >>>   */
> >>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>  	const struct drm_bridge_funcs *funcs;
> >>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>  	void *driver_private;
> >>> +	/** @ops: bitmask of operations supported by the bridge */
> >>> +	enum drm_bridge_ops ops;
> >>> +	/**
> >>> +	 * @type: Type of the connection at the bridge output
> >>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>> +	 * identifies the type of connected display.
> >>> +	 */
> >>> +	int type;
> >>> +	/** private: */
> >>> +	/**
> >>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>> +	 */
> >>> +	struct mutex hpd_mutex;
> >>> +	/**
> >>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>> +	 * drm_bridge_hpd_enable().
> >>> +	 */
> >>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>> +	/**
> >>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>> +	 * @hpd_cb.
> >>> +	 */
> >>> +	void *hpd_data;
> >>>  };
> >>>  
> >>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>  			      struct drm_atomic_state *state);
> >>>  
> >>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>> +			   void (*cb)(void *data,
> >>> +				      enum drm_connector_status status),
> >>> +			   void *data);
> >>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>> +			   enum drm_connector_status status);
> >>> +
> >>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>  					u32 connector_type);
> >>
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-11 13:18           ` Daniel Vetter
@ 2019-07-11 15:12             ` Andrzej Hajda
  2019-07-11 15:50               ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-11 15:12 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On 11.07.2019 15:18, Daniel Vetter wrote:
> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>> Hi Laurent,
>>>>
>>>>
>>>> I like the approach, current practice when almost every bridge should
>>>> optionally implement connector, or alternatively downstream bridge or
>>>> panel is very painful.
>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>> it's all used in the end. I probably should go and do that, at least to
>>> get a feeling for what your hpd_cb usually does.
>>>
>>>> More comments inlined.
>>>>
>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>> data:
>>>>>
>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>   retrieval operations
>>>>> - Bitmask of supported operations
>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>> operation's callback?
>>> Yeah also not a huge fan of these bitmasks. Smells like
>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>> add, generally good excuse to not have to think through the design between
>>> different parts of drivers - "just" add another flag.
>>>>> - Bridge output type
>>>>>
>>>>> Add and document these.
>>>>>
>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>> notification in a way that is as transparent as possible for the
>>>>> bridges.
>>>> Documentation of new opses does not explain how it should cooperate with
>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>> right? More comments about it later.
>>>>
>>>>
>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>> ---
>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>   */
>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>  {
>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>> +
>>>>>  	mutex_lock(&bridge_lock);
>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>  	mutex_unlock(&bridge_lock);
>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>  	mutex_lock(&bridge_lock);
>>>>>  	list_del_init(&bridge->list);
>>>>>  	mutex_unlock(&bridge_lock);
>>>>> +
>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>  }
>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>  
>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>  }
>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>  
>>>>> +/**
>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>> + * @bridge: bridge control structure
>>>>> + * @cb: hot-plug detection callback
>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>> + *
>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>> + *
>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>> + *
>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>> + * the bridge.
>>>>> + */
>>>> To simplify architecture maybe would be better to enable hpd just on
>>>> bridge attach:
>>>>
>>>> bridge->hpd_cb = cb;
>>>>
>>>> bridge->hpd_data = data;
>>>>
>>>> ret = drm_bridge_attach(...);
>>> Yeah I like this more. The other problem here is, what if you need more
>>> than 1 callback registers on the same bridge hdp signal?
>>>
>>>
>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>> without big sacrifices.
>>>>
>>>>
>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>> notifies about sink status change, how it translates to this cb?
>>>>
>>>>
>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>> +			   void (*cb)(void *data,
>>>>> +				      enum drm_connector_status status),
>>>>> +			   void *data)
>>>>> +{
>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>> +		return;
>>>>> +
>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>> +
>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>> +		goto unlock;
>>>>> +
>>>>> +	bridge->hpd_cb = cb;
>>>>> +	bridge->hpd_data = data;
>>>>> +
>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>> +
>>>>> +unlock:
>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>> +
>>>>> +/**
>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>> + * @bridge: bridge control structure
>>>>> + *
>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>> + * function returns the callback will not be called by the bridge when an
>>>>> + * output status change occurs.
>>>>> + *
>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>> + */
>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>> +{
>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>> +		return;
>>>>> +
>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>> +
>>>>> +	bridge->hpd_cb = NULL;
>>>>> +	bridge->hpd_data = NULL;
>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>> +
>>>>> +/**
>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>> + * @bridge: bridge control structure
>>>>> + * @status: output connection status
>>>>> + *
>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>> + *
>>>>> + * This function shall be called in a context that can sleep.
>>>>> + */
>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>> +			   enum drm_connector_status status)
>>>>> +{
>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>> +	if (bridge->hpd_cb)
>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>> So this isn't quite what I had in mind. Instead something like this:
>>>
>>> 	/* iterates over all bridges in the chain containing @bridge */
>>> 	for_each_bridge(tmp_bridge, bridge) {
>>> 		if (tmp_bridge == bridge)
>>> 			continue;
>>> 		if (bridge->hpd_notify);
>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>> 	}
>>>
>>> 	encoder = encoder_for_bridge(bridge);
>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>
>>> 	dev = bridge->dev
>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>
>>> No register callback needed, no locking needed, everyone gets exactly the
>>> hpd they want/need.
>>
>> As I understand you want to notify every member of the pipeline.
>>
>> I think it should be enough to notify only the source, and then source
>> should decide if/when the hpd should be propagated upstream.
>>
>> It looks more generic for me.
> I'm not parsing ... do you think my idea is more generic and useful, or
> the one from Laurent? Kinda confused here.


Regarding general idea:

1. Laurent's approach is to notify only consumer, I guess usually video
source.

2. Your is to notify all other bridges and encoder.


And I prefer 1st approach, why:

- the source can decide if/when and to who propagate the signal,

- is more generic, for example if bridge send signal to two
monitors/panels, it can delay hpd propagation till both sinks are present,

- it resembles hardware wires :)


And regarding implementation:

1. Laurent proposes to register callback drm_bridge_hpd_enable.

2. You propose to add ops hpd_notify in bridges and encoders.


Your proposition is more straightforward, but if we want to notify only
source we should locate it by parsing notification chain (what about
unchained bridges), or store pointer somewhere during attachment.

It still leaves us with this ugly dualism - source is encoder or bridge,
similarly to sink as bridge or panel, but fixing it can be done later.


Regards

Andrzej


> -Daniel
>
>>
>> Regards
>>
>> Andrzej
>>
>>
>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>> +
>>>>>  #ifdef CONFIG_OF
>>>>>  /**
>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>> --- a/include/drm/drm_bridge.h
>>>>> +++ b/include/drm/drm_bridge.h
>>>>> @@ -23,8 +23,9 @@
>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>  #define __DRM_BRIDGE_H__
>>>>>  
>>>>> -#include <linux/list.h>
>>>>>  #include <linux/ctype.h>
>>>>> +#include <linux/list.h>
>>>>> +#include <linux/mutex.h>
>>>>>  #include <drm/drm_mode_object.h>
>>>>>  #include <drm/drm_modes.h>
>>>>>  
>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>  	 */
>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>  				    struct drm_atomic_state *state);
>>>>> +
>>>>> +	/**
>>>>> +	 * @detect:
>>>>> +	 *
>>>>> +	 * Check if anything is attached to the bridge output.
>>>>> +	 *
>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>> +	 * considered as always having a component attached to its output.
>>>>> +	 * Bridges that implement this callback shall set the
>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>> +	 *
>>>>> +	 * RETURNS:
>>>>> +	 *
>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>> +	 */
>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>> +
>>>>> +	/**
>>>>> +	 * @get_modes:
>>>>> +	 *
>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>> +	 * with drm_mode_probed_add().
>>>>> +	 *
>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>> +	 *
>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>> +	 *
>>>>> +	 * RETURNS:
>>>>> +	 *
>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>> +	 */
>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>> +			 struct drm_connector *connector);
>>>>> +
>>>>> +	/**
>>>>> +	 * @get_edid:
>>>>> +	 *
>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>> +	 *
>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>> +	 * the @get_modes callback unimplemented.
>>>>> +	 *
>>>>> +	 * The caller of this operation shall first verify the output
>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>> +	 * output.
>>>>> +	 *
>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>> +	 *
>>>>> +	 * RETURNS:
>>>>> +	 *
>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>> +	 * the returned edid structure with kfree().
>>>>> +	 */
>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>> +				 struct drm_connector *connector);
>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>> presence of another one?
>>>>
>>>> I am not really convinced we need this op at all, cannot we just assign
>>>> some helper function to .get_modes cb, which will do the same?
>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>> case, and require that if it has an edid it must fill out connector->info
>>> and connector->edid correctly.
>>>
>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>> in the connector is up-to-date? With your current callback design that's
>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>> should guarantee that it'll first walk the connectors to update status and
>>> edid/mode list for the final drm_connector. And then instead of just
>>> passing the simple "status", it'll pass the connector, with everything
>>> correctly updated.
>>>
>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>> edid, which is not so awesome :-)
>>> -Daniel
>>>
>>>> Regards
>>>>
>>>> Andrzej
>>>>
>>>>
>>>>> +
>>>>> +	/**
>>>>> +	 * @lost_hotplug:
>>>>> +	 *
>>>>> +	 * Notify the bridge of display disconnection.
>>>>> +	 *
>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>> +	 * HDMI bridges.
>>>>> +	 */
>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>> +
>>>>> +	/**
>>>>> +	 * @hpd_enable:
>>>>> +	 *
>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>> +	 * @hpd_disable.
>>>>> +	 *
>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>> +	 */
>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>> +
>>>>> +	/**
>>>>> +	 * @hpd_disable:
>>>>> +	 *
>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>> +	 * connection status occurs.
>>>>> +	 *
>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>> +	 */
>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>  };
>>>>>  
>>>>>  /**
>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>  	bool dual_link;
>>>>>  };
>>>>>  
>>>>> +/**
>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>> + */
>>>>> +enum drm_bridge_ops {
>>>>> +	/**
>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>> +	 */
>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>> +	/**
>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>> +	 */
>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>> +	/**
>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>> +	 */
>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>> +	/**
>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>> +	 */
>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>> +};
>>>>> +
>>>>>  /**
>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>   */
>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>  	void *driver_private;
>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>> +	enum drm_bridge_ops ops;
>>>>> +	/**
>>>>> +	 * @type: Type of the connection at the bridge output
>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>> +	 * identifies the type of connected display.
>>>>> +	 */
>>>>> +	int type;
>>>>> +	/** private: */
>>>>> +	/**
>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>> +	 */
>>>>> +	struct mutex hpd_mutex;
>>>>> +	/**
>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>> +	 * drm_bridge_hpd_enable().
>>>>> +	 */
>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>> +	/**
>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>> +	 * @hpd_cb.
>>>>> +	 */
>>>>> +	void *hpd_data;
>>>>>  };
>>>>>  
>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>  			      struct drm_atomic_state *state);
>>>>>  
>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>> +			   void (*cb)(void *data,
>>>>> +				      enum drm_connector_status status),
>>>>> +			   void *data);
>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>> +			   enum drm_connector_status status);
>>>>> +
>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-11 15:12             ` Andrzej Hajda
@ 2019-07-11 15:50               ` Daniel Vetter
  2019-07-12  9:01                 ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-07-11 15:50 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> On 11.07.2019 15:18, Daniel Vetter wrote:
> > On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>> Hi Laurent,
> >>>>
> >>>>
> >>>> I like the approach, current practice when almost every bridge should
> >>>> optionally implement connector, or alternatively downstream bridge or
> >>>> panel is very painful.
> >>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>> it's all used in the end. I probably should go and do that, at least to
> >>> get a feeling for what your hpd_cb usually does.
> >>>
> >>>> More comments inlined.
> >>>>
> >>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>> data:
> >>>>>
> >>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>   retrieval operations
> >>>>> - Bitmask of supported operations
> >>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>> operation's callback?
> >>> Yeah also not a huge fan of these bitmasks. Smells like
> >>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>> add, generally good excuse to not have to think through the design between
> >>> different parts of drivers - "just" add another flag.
> >>>>> - Bridge output type
> >>>>>
> >>>>> Add and document these.
> >>>>>
> >>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>> notification in a way that is as transparent as possible for the
> >>>>> bridges.
> >>>> Documentation of new opses does not explain how it should cooperate with
> >>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>> right? More comments about it later.
> >>>>
> >>>>
> >>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>> ---
> >>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>
> >>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>   */
> >>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>  {
> >>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>> +
> >>>>>  	mutex_lock(&bridge_lock);
> >>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>  	mutex_unlock(&bridge_lock);
> >>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>  	mutex_lock(&bridge_lock);
> >>>>>  	list_del_init(&bridge->list);
> >>>>>  	mutex_unlock(&bridge_lock);
> >>>>> +
> >>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>  }
> >>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>  
> >>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>  }
> >>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>  
> >>>>> +/**
> >>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>> + * @bridge: bridge control structure
> >>>>> + * @cb: hot-plug detection callback
> >>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>> + *
> >>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>> + *
> >>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>> + *
> >>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>> + * the bridge.
> >>>>> + */
> >>>> To simplify architecture maybe would be better to enable hpd just on
> >>>> bridge attach:
> >>>>
> >>>> bridge->hpd_cb = cb;
> >>>>
> >>>> bridge->hpd_data = data;
> >>>>
> >>>> ret = drm_bridge_attach(...);
> >>> Yeah I like this more. The other problem here is, what if you need more
> >>> than 1 callback registers on the same bridge hdp signal?
> >>>
> >>>
> >>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>> without big sacrifices.
> >>>>
> >>>>
> >>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>> notifies about sink status change, how it translates to this cb?
> >>>>
> >>>>
> >>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>> +			   void (*cb)(void *data,
> >>>>> +				      enum drm_connector_status status),
> >>>>> +			   void *data)
> >>>>> +{
> >>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>> +		return;
> >>>>> +
> >>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>> +
> >>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>> +		goto unlock;
> >>>>> +
> >>>>> +	bridge->hpd_cb = cb;
> >>>>> +	bridge->hpd_data = data;
> >>>>> +
> >>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>> +
> >>>>> +unlock:
> >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>> +
> >>>>> +/**
> >>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>> + * @bridge: bridge control structure
> >>>>> + *
> >>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>> + * function returns the callback will not be called by the bridge when an
> >>>>> + * output status change occurs.
> >>>>> + *
> >>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>> + */
> >>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>> +{
> >>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>> +		return;
> >>>>> +
> >>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>> +
> >>>>> +	bridge->hpd_cb = NULL;
> >>>>> +	bridge->hpd_data = NULL;
> >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>> +
> >>>>> +/**
> >>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>> + * @bridge: bridge control structure
> >>>>> + * @status: output connection status
> >>>>> + *
> >>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>> + *
> >>>>> + * This function shall be called in a context that can sleep.
> >>>>> + */
> >>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>> +			   enum drm_connector_status status)
> >>>>> +{
> >>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>> +	if (bridge->hpd_cb)
> >>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>> So this isn't quite what I had in mind. Instead something like this:
> >>>
> >>> 	/* iterates over all bridges in the chain containing @bridge */
> >>> 	for_each_bridge(tmp_bridge, bridge) {
> >>> 		if (tmp_bridge == bridge)
> >>> 			continue;
> >>> 		if (bridge->hpd_notify);
> >>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>> 	}
> >>>
> >>> 	encoder = encoder_for_bridge(bridge);
> >>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>
> >>> 	dev = bridge->dev
> >>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>
> >>> No register callback needed, no locking needed, everyone gets exactly the
> >>> hpd they want/need.
> >>
> >> As I understand you want to notify every member of the pipeline.
> >>
> >> I think it should be enough to notify only the source, and then source
> >> should decide if/when the hpd should be propagated upstream.
> >>
> >> It looks more generic for me.
> > I'm not parsing ... do you think my idea is more generic and useful, or
> > the one from Laurent? Kinda confused here.
> 
> 
> Regarding general idea:
> 
> 1. Laurent's approach is to notify only consumer, I guess usually video
> source.
> 
> 2. Your is to notify all other bridges and encoder.
> 
> 
> And I prefer 1st approach, why:
> 
> - the source can decide if/when and to who propagate the signal,
> 
> - is more generic, for example if bridge send signal to two
> monitors/panels, it can delay hpd propagation till both sinks are present,

With Laurent's approach the bridge cannot send the hpd to more than one
consumer. There's only 1 callback. So you're example doesn't work.

> - it resembles hardware wires :)

This isn't for the hw wires afaiui. The hw hpd terminates in the source
bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
interested in that hpd singal. This includes:
- Other bridges, e.g. if they provide CEC support.
- Other bridges, maybe they need to re-run the HDCP state engine
- Overall driver, so it can update the modes/connector status and send the
  uevent to the driver.
- Overall display pipeline for this specific bridge, maybe you need to
  shut down/re-enable the pipe because $reasons.
 
That's at least my understanding from lots of chats with Laurent about
what he wants to do here.

> And regarding implementation:
> 
> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> 
> 2. You propose to add ops hpd_notify in bridges and encoders.
> 
> 
> Your proposition is more straightforward, but if we want to notify only
> source we should locate it by parsing notification chain (what about
> unchained bridges), or store pointer somewhere during attachment.
> 
> It still leaves us with this ugly dualism - source is encoder or bridge,
> similarly to sink as bridge or panel, but fixing it can be done later.

Uh I think we're not talking about the same thing really. My understanding
is that this callback is if someone (outside of this bridge) is interested
in a hpd signal _from_ this bridge. Which means you can only ever have 1
listener.

You seem to have some other idea here.
-Daniel

> 
> 
> Regards
> 
> Andrzej
> 
> 
> > -Daniel
> >
> >>
> >> Regards
> >>
> >> Andrzej
> >>
> >>
> >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>> +
> >>>>>  #ifdef CONFIG_OF
> >>>>>  /**
> >>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>> --- a/include/drm/drm_bridge.h
> >>>>> +++ b/include/drm/drm_bridge.h
> >>>>> @@ -23,8 +23,9 @@
> >>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>  #define __DRM_BRIDGE_H__
> >>>>>  
> >>>>> -#include <linux/list.h>
> >>>>>  #include <linux/ctype.h>
> >>>>> +#include <linux/list.h>
> >>>>> +#include <linux/mutex.h>
> >>>>>  #include <drm/drm_mode_object.h>
> >>>>>  #include <drm/drm_modes.h>
> >>>>>  
> >>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>  	 */
> >>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>  				    struct drm_atomic_state *state);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @detect:
> >>>>> +	 *
> >>>>> +	 * Check if anything is attached to the bridge output.
> >>>>> +	 *
> >>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>> +	 * considered as always having a component attached to its output.
> >>>>> +	 * Bridges that implement this callback shall set the
> >>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>> +	 *
> >>>>> +	 * RETURNS:
> >>>>> +	 *
> >>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>> +	 */
> >>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @get_modes:
> >>>>> +	 *
> >>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>> +	 * with drm_mode_probed_add().
> >>>>> +	 *
> >>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>> +	 *
> >>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>> +	 *
> >>>>> +	 * RETURNS:
> >>>>> +	 *
> >>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>> +	 */
> >>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>> +			 struct drm_connector *connector);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @get_edid:
> >>>>> +	 *
> >>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>> +	 *
> >>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>> +	 * the @get_modes callback unimplemented.
> >>>>> +	 *
> >>>>> +	 * The caller of this operation shall first verify the output
> >>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>> +	 * output.
> >>>>> +	 *
> >>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>> +	 *
> >>>>> +	 * RETURNS:
> >>>>> +	 *
> >>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>> +	 * the returned edid structure with kfree().
> >>>>> +	 */
> >>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>> +				 struct drm_connector *connector);
> >>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>> presence of another one?
> >>>>
> >>>> I am not really convinced we need this op at all, cannot we just assign
> >>>> some helper function to .get_modes cb, which will do the same?
> >>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>> case, and require that if it has an edid it must fill out connector->info
> >>> and connector->edid correctly.
> >>>
> >>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>> in the connector is up-to-date? With your current callback design that's
> >>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>> should guarantee that it'll first walk the connectors to update status and
> >>> edid/mode list for the final drm_connector. And then instead of just
> >>> passing the simple "status", it'll pass the connector, with everything
> >>> correctly updated.
> >>>
> >>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>> edid, which is not so awesome :-)
> >>> -Daniel
> >>>
> >>>> Regards
> >>>>
> >>>> Andrzej
> >>>>
> >>>>
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @lost_hotplug:
> >>>>> +	 *
> >>>>> +	 * Notify the bridge of display disconnection.
> >>>>> +	 *
> >>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>> +	 * HDMI bridges.
> >>>>> +	 */
> >>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @hpd_enable:
> >>>>> +	 *
> >>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>> +	 * @hpd_disable.
> >>>>> +	 *
> >>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>> +	 */
> >>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @hpd_disable:
> >>>>> +	 *
> >>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>> +	 * connection status occurs.
> >>>>> +	 *
> >>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>> +	 */
> >>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>  };
> >>>>>  
> >>>>>  /**
> >>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>  	bool dual_link;
> >>>>>  };
> >>>>>  
> >>>>> +/**
> >>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>> + */
> >>>>> +enum drm_bridge_ops {
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>> +};
> >>>>> +
> >>>>>  /**
> >>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>   */
> >>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>  	void *driver_private;
> >>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>> +	enum drm_bridge_ops ops;
> >>>>> +	/**
> >>>>> +	 * @type: Type of the connection at the bridge output
> >>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>> +	 * identifies the type of connected display.
> >>>>> +	 */
> >>>>> +	int type;
> >>>>> +	/** private: */
> >>>>> +	/**
> >>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>> +	 */
> >>>>> +	struct mutex hpd_mutex;
> >>>>> +	/**
> >>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>> +	 * drm_bridge_hpd_enable().
> >>>>> +	 */
> >>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>> +	/**
> >>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>> +	 * @hpd_cb.
> >>>>> +	 */
> >>>>> +	void *hpd_data;
> >>>>>  };
> >>>>>  
> >>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>  			      struct drm_atomic_state *state);
> >>>>>  
> >>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>> +			   void (*cb)(void *data,
> >>>>> +				      enum drm_connector_status status),
> >>>>> +			   void *data);
> >>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>> +			   enum drm_connector_status status);
> >>>>> +
> >>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>  					u32 connector_type);
> 
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-11 15:50               ` Daniel Vetter
@ 2019-07-12  9:01                 ` Andrzej Hajda
  2019-07-16  9:00                   ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-12  9:01 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On 11.07.2019 17:50, Daniel Vetter wrote:
> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>> Hi Laurent,
>>>>>>
>>>>>>
>>>>>> I like the approach, current practice when almost every bridge should
>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>> panel is very painful.
>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>> get a feeling for what your hpd_cb usually does.
>>>>>
>>>>>> More comments inlined.
>>>>>>
>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>> data:
>>>>>>>
>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>   retrieval operations
>>>>>>> - Bitmask of supported operations
>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>> operation's callback?
>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>> add, generally good excuse to not have to think through the design between
>>>>> different parts of drivers - "just" add another flag.
>>>>>>> - Bridge output type
>>>>>>>
>>>>>>> Add and document these.
>>>>>>>
>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>> bridges.
>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>> right? More comments about it later.
>>>>>>
>>>>>>
>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>> ---
>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>
>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>   */
>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>  {
>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>> +
>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>> +
>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>  }
>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>  
>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>  }
>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>  
>>>>>>> +/**
>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>> + * @bridge: bridge control structure
>>>>>>> + * @cb: hot-plug detection callback
>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>> + *
>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>> + *
>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>> + *
>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>> + * the bridge.
>>>>>>> + */
>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>> bridge attach:
>>>>>>
>>>>>> bridge->hpd_cb = cb;
>>>>>>
>>>>>> bridge->hpd_data = data;
>>>>>>
>>>>>> ret = drm_bridge_attach(...);
>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>
>>>>>
>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>> without big sacrifices.
>>>>>>
>>>>>>
>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>
>>>>>>
>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>> +			   void (*cb)(void *data,
>>>>>>> +				      enum drm_connector_status status),
>>>>>>> +			   void *data)
>>>>>>> +{
>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>> +		return;
>>>>>>> +
>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>> +
>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>> +		goto unlock;
>>>>>>> +
>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>> +	bridge->hpd_data = data;
>>>>>>> +
>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>> +
>>>>>>> +unlock:
>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>> + * @bridge: bridge control structure
>>>>>>> + *
>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>> + * output status change occurs.
>>>>>>> + *
>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>> + */
>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>> +{
>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>> +		return;
>>>>>>> +
>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>> +
>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>> + * @bridge: bridge control structure
>>>>>>> + * @status: output connection status
>>>>>>> + *
>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>> + *
>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>> + */
>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>> +			   enum drm_connector_status status)
>>>>>>> +{
>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>> +	if (bridge->hpd_cb)
>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>
>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>> 		if (tmp_bridge == bridge)
>>>>> 			continue;
>>>>> 		if (bridge->hpd_notify);
>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>> 	}
>>>>>
>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>
>>>>> 	dev = bridge->dev
>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>
>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>> hpd they want/need.
>>>> As I understand you want to notify every member of the pipeline.
>>>>
>>>> I think it should be enough to notify only the source, and then source
>>>> should decide if/when the hpd should be propagated upstream.
>>>>
>>>> It looks more generic for me.
>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>> the one from Laurent? Kinda confused here.
>>
>> Regarding general idea:
>>
>> 1. Laurent's approach is to notify only consumer, I guess usually video
>> source.
>>
>> 2. Your is to notify all other bridges and encoder.
>>
>>
>> And I prefer 1st approach, why:
>>
>> - the source can decide if/when and to who propagate the signal,
>>
>> - is more generic, for example if bridge send signal to two
>> monitors/panels, it can delay hpd propagation till both sinks are present,
> With Laurent's approach the bridge cannot send the hpd to more than one
> consumer. There's only 1 callback. So you're example doesn't work.


If there will be two consumers, there will be two bridge attachments,
thus there will be two notifications, it should work.


>
>> - it resembles hardware wires :)
> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> interested in that hpd singal. This includes:
> - Other bridges, e.g. if they provide CEC support.
> - Other bridges, maybe they need to re-run the HDCP state engine
> - Overall driver, so it can update the modes/connector status and send the
>   uevent to the driver.
> - Overall display pipeline for this specific bridge, maybe you need to
>   shut down/re-enable the pipe because $reasons.
>  
> That's at least my understanding from lots of chats with Laurent about
> what he wants to do here.


I do not know the full picture, but the solution where particular bridge
notifies everything unconditionally seems to me much less flexible.

If HPD signals is received by the consumer, if there are no obstacles it
can propagate it further, upstream bridge/encoder or to drm core - it
will mimic your scenario.

But there are also other scenarios where bridge does not want to
propagate signal, because for example:

- it wants to wait for other sinks to wake up,

- it propagates HPD signal via hardware wire,

- first it wants to verify if the sink is valid/compatible/authorized
device.


In general HPD is input signal for notify of state changes on particular
bus, in case of typical video bridge on its output video bus.

In case of bridges they have also input video buses, and they can send
HPD signal via this bus, but this is indeed different HPD signal, even
if for most cases they looks similar.


>
>> And regarding implementation:
>>
>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>
>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>
>>
>> Your proposition is more straightforward, but if we want to notify only
>> source we should locate it by parsing notification chain (what about
>> unchained bridges), or store pointer somewhere during attachment.
>>
>> It still leaves us with this ugly dualism - source is encoder or bridge,
>> similarly to sink as bridge or panel, but fixing it can be done later.
> Uh I think we're not talking about the same thing really. My understanding
> is that this callback is if someone (outside of this bridge) is interested
> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> listener.


Do we have real life examples?

I want to distinguish two situations:

- another device wants to know if input bus of the bridge has changed state,

- another device wants to know if output bus of the bridge has changed
state.


Regards

Andrzej


>
> You seem to have some other idea here.
> -Daniel
>
>>
>> Regards
>>
>> Andrzej
>>
>>
>>> -Daniel
>>>
>>>> Regards
>>>>
>>>> Andrzej
>>>>
>>>>
>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>> +
>>>>>>>  #ifdef CONFIG_OF
>>>>>>>  /**
>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>  
>>>>>>> -#include <linux/list.h>
>>>>>>>  #include <linux/ctype.h>
>>>>>>> +#include <linux/list.h>
>>>>>>> +#include <linux/mutex.h>
>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>  
>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>  	 */
>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>> +
>>>>>>> +	/**
>>>>>>> +	 * @detect:
>>>>>>> +	 *
>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>> +	 *
>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>> +	 *
>>>>>>> +	 * RETURNS:
>>>>>>> +	 *
>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>> +	 */
>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>> +
>>>>>>> +	/**
>>>>>>> +	 * @get_modes:
>>>>>>> +	 *
>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>> +	 *
>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>> +	 *
>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>> +	 *
>>>>>>> +	 * RETURNS:
>>>>>>> +	 *
>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>> +	 */
>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>> +			 struct drm_connector *connector);
>>>>>>> +
>>>>>>> +	/**
>>>>>>> +	 * @get_edid:
>>>>>>> +	 *
>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>> +	 *
>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>> +	 *
>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>> +	 * output.
>>>>>>> +	 *
>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>> +	 *
>>>>>>> +	 * RETURNS:
>>>>>>> +	 *
>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>> +	 */
>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>> +				 struct drm_connector *connector);
>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>> presence of another one?
>>>>>>
>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>> and connector->edid correctly.
>>>>>
>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>> in the connector is up-to-date? With your current callback design that's
>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>> correctly updated.
>>>>>
>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>> edid, which is not so awesome :-)
>>>>> -Daniel
>>>>>
>>>>>> Regards
>>>>>>
>>>>>> Andrzej
>>>>>>
>>>>>>
>>>>>>> +
>>>>>>> +	/**
>>>>>>> +	 * @lost_hotplug:
>>>>>>> +	 *
>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>> +	 *
>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>> +	 * HDMI bridges.
>>>>>>> +	 */
>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>> +
>>>>>>> +	/**
>>>>>>> +	 * @hpd_enable:
>>>>>>> +	 *
>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>> +	 * @hpd_disable.
>>>>>>> +	 *
>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>> +	 */
>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>> +
>>>>>>> +	/**
>>>>>>> +	 * @hpd_disable:
>>>>>>> +	 *
>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>> +	 * connection status occurs.
>>>>>>> +	 *
>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>> +	 */
>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>  };
>>>>>>>  
>>>>>>>  /**
>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>  	bool dual_link;
>>>>>>>  };
>>>>>>>  
>>>>>>> +/**
>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>> + */
>>>>>>> +enum drm_bridge_ops {
>>>>>>> +	/**
>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>> +	 */
>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>> +	/**
>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>> +	 */
>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>> +	/**
>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>> +	 */
>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>> +	/**
>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>> +	 */
>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>> +};
>>>>>>> +
>>>>>>>  /**
>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>   */
>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>  	void *driver_private;
>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>> +	/**
>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>> +	 * identifies the type of connected display.
>>>>>>> +	 */
>>>>>>> +	int type;
>>>>>>> +	/** private: */
>>>>>>> +	/**
>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>> +	 */
>>>>>>> +	struct mutex hpd_mutex;
>>>>>>> +	/**
>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>> +	 */
>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>> +	/**
>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>> +	 * @hpd_cb.
>>>>>>> +	 */
>>>>>>> +	void *hpd_data;
>>>>>>>  };
>>>>>>>  
>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>  
>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>> +			   void (*cb)(void *data,
>>>>>>> +				      enum drm_connector_status status),
>>>>>>> +			   void *data);
>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>> +			   enum drm_connector_status status);
>>>>>>> +
>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>  					u32 connector_type);
>>

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-12  9:01                 ` Andrzej Hajda
@ 2019-07-16  9:00                   ` Daniel Vetter
  2019-07-16 13:57                     ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-07-16  9:00 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> On 11.07.2019 17:50, Daniel Vetter wrote:
> > On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>> Hi Laurent,
> >>>>>>
> >>>>>>
> >>>>>> I like the approach, current practice when almost every bridge should
> >>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>> panel is very painful.
> >>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>> get a feeling for what your hpd_cb usually does.
> >>>>>
> >>>>>> More comments inlined.
> >>>>>>
> >>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>> data:
> >>>>>>>
> >>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>   retrieval operations
> >>>>>>> - Bitmask of supported operations
> >>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>> operation's callback?
> >>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>> add, generally good excuse to not have to think through the design between
> >>>>> different parts of drivers - "just" add another flag.
> >>>>>>> - Bridge output type
> >>>>>>>
> >>>>>>> Add and document these.
> >>>>>>>
> >>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>> bridges.
> >>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>> right? More comments about it later.
> >>>>>>
> >>>>>>
> >>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>> ---
> >>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>
> >>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>   */
> >>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>  {
> >>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>> +
> >>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>> +
> >>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>  }
> >>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>  
> >>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>  }
> >>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>  
> >>>>>>> +/**
> >>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>> + * @bridge: bridge control structure
> >>>>>>> + * @cb: hot-plug detection callback
> >>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>> + *
> >>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>> + *
> >>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>> + *
> >>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>> + * the bridge.
> >>>>>>> + */
> >>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>> bridge attach:
> >>>>>>
> >>>>>> bridge->hpd_cb = cb;
> >>>>>>
> >>>>>> bridge->hpd_data = data;
> >>>>>>
> >>>>>> ret = drm_bridge_attach(...);
> >>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>
> >>>>>
> >>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>> without big sacrifices.
> >>>>>>
> >>>>>>
> >>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>
> >>>>>>
> >>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>> +			   void (*cb)(void *data,
> >>>>>>> +				      enum drm_connector_status status),
> >>>>>>> +			   void *data)
> >>>>>>> +{
> >>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>> +		return;
> >>>>>>> +
> >>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>> +
> >>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>> +		goto unlock;
> >>>>>>> +
> >>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>> +	bridge->hpd_data = data;
> >>>>>>> +
> >>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>> +
> >>>>>>> +unlock:
> >>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>> +
> >>>>>>> +/**
> >>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>> + * @bridge: bridge control structure
> >>>>>>> + *
> >>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>> + * output status change occurs.
> >>>>>>> + *
> >>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>> + */
> >>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>> +{
> >>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>> +		return;
> >>>>>>> +
> >>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>> +
> >>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>> +
> >>>>>>> +/**
> >>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>> + * @bridge: bridge control structure
> >>>>>>> + * @status: output connection status
> >>>>>>> + *
> >>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>> + *
> >>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>> + */
> >>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>> +			   enum drm_connector_status status)
> >>>>>>> +{
> >>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>> +	if (bridge->hpd_cb)
> >>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>
> >>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>> 		if (tmp_bridge == bridge)
> >>>>> 			continue;
> >>>>> 		if (bridge->hpd_notify);
> >>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>> 	}
> >>>>>
> >>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>
> >>>>> 	dev = bridge->dev
> >>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>
> >>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>> hpd they want/need.
> >>>> As I understand you want to notify every member of the pipeline.
> >>>>
> >>>> I think it should be enough to notify only the source, and then source
> >>>> should decide if/when the hpd should be propagated upstream.
> >>>>
> >>>> It looks more generic for me.
> >>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>> the one from Laurent? Kinda confused here.
> >>
> >> Regarding general idea:
> >>
> >> 1. Laurent's approach is to notify only consumer, I guess usually video
> >> source.
> >>
> >> 2. Your is to notify all other bridges and encoder.
> >>
> >>
> >> And I prefer 1st approach, why:
> >>
> >> - the source can decide if/when and to who propagate the signal,
> >>
> >> - is more generic, for example if bridge send signal to two
> >> monitors/panels, it can delay hpd propagation till both sinks are present,
> > With Laurent's approach the bridge cannot send the hpd to more than one
> > consumer. There's only 1 callback. So you're example doesn't work.
> 
> 
> If there will be two consumers, there will be two bridge attachments,
> thus there will be two notifications, it should work.

2 consumers, 1 producer. There's only _one_ callback in the producer. The
callback is registered on the produce bridge, not on the consumer bridge
(or I'm totallly misreading what Laurent does here).

> >> - it resembles hardware wires :)
> > This isn't for the hw wires afaiui. The hw hpd terminates in the source
> > bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> > interested in that hpd singal. This includes:
> > - Other bridges, e.g. if they provide CEC support.
> > - Other bridges, maybe they need to re-run the HDCP state engine
> > - Overall driver, so it can update the modes/connector status and send the
> >   uevent to the driver.
> > - Overall display pipeline for this specific bridge, maybe you need to
> >   shut down/re-enable the pipe because $reasons.
> >  
> > That's at least my understanding from lots of chats with Laurent about
> > what he wants to do here.
> 
> 
> I do not know the full picture, but the solution where particular bridge
> notifies everything unconditionally seems to me much less flexible.
> 
> If HPD signals is received by the consumer, if there are no obstacles it
> can propagate it further, upstream bridge/encoder or to drm core - it
> will mimic your scenario.
> 
> But there are also other scenarios where bridge does not want to
> propagate signal, because for example:
> 
> - it wants to wait for other sinks to wake up,

The other sink can just do that in their hpd callback.

> - it propagates HPD signal via hardware wire,

Again, the other sink can just not listen to sw hpd in that case, and use
the wire/hw hpd interrupt.

> - first it wants to verify if the sink is valid/compatible/authorized
> device.

Now you lost me. Why would someone glue incompatible IP into a SoC or
board?

> In general HPD is input signal for notify of state changes on particular
> bus, in case of typical video bridge on its output video bus.
> 
> In case of bridges they have also input video buses, and they can send
> HPD signal via this bus, but this is indeed different HPD signal, even
> if for most cases they looks similar.

Ah, I think this is a problem we will eventually have. But it's not
something we're currently solving here at all I think.

> >> And regarding implementation:
> >>
> >> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>
> >> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>
> >>
> >> Your proposition is more straightforward, but if we want to notify only
> >> source we should locate it by parsing notification chain (what about
> >> unchained bridges), or store pointer somewhere during attachment.
> >>
> >> It still leaves us with this ugly dualism - source is encoder or bridge,
> >> similarly to sink as bridge or panel, but fixing it can be done later.
> > Uh I think we're not talking about the same thing really. My understanding
> > is that this callback is if someone (outside of this bridge) is interested
> > in a hpd signal _from_ this bridge. Which means you can only ever have 1
> > listener.
> 
> 
> Do we have real life examples?
> 
> I want to distinguish two situations:
> 
> - another device wants to know if input bus of the bridge has changed state,
> 
> - another device wants to know if output bus of the bridge has changed
> state.

Uh, that's what drm_bridge_state is for (if it ever happens). That's how
bridges can exchange state and information about each another. hpd is
about the physical world, i.e. "is there a cable plugged into the port
I'm driving?". We're not going to use fake hpd to update bridge state and
fun stuff like that, we have the atomic_check machinery for this.
-Daniel

>
> 
> Regards
> 
> Andrzej
> 
> 
> >
> > You seem to have some other idea here.
> > -Daniel
> >
> >>
> >> Regards
> >>
> >> Andrzej
> >>
> >>
> >>> -Daniel
> >>>
> >>>> Regards
> >>>>
> >>>> Andrzej
> >>>>
> >>>>
> >>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>> +
> >>>>>>>  #ifdef CONFIG_OF
> >>>>>>>  /**
> >>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>  
> >>>>>>> -#include <linux/list.h>
> >>>>>>>  #include <linux/ctype.h>
> >>>>>>> +#include <linux/list.h>
> >>>>>>> +#include <linux/mutex.h>
> >>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>  
> >>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>  	 */
> >>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @detect:
> >>>>>>> +	 *
> >>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>> +	 *
> >>>>>>> +	 * RETURNS:
> >>>>>>> +	 *
> >>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>> +	 */
> >>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @get_modes:
> >>>>>>> +	 *
> >>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>> +	 *
> >>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>> +	 *
> >>>>>>> +	 * RETURNS:
> >>>>>>> +	 *
> >>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>> +	 */
> >>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>> +			 struct drm_connector *connector);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @get_edid:
> >>>>>>> +	 *
> >>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>> +	 *
> >>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>> +	 *
> >>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>> +	 * output.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>> +	 *
> >>>>>>> +	 * RETURNS:
> >>>>>>> +	 *
> >>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>> +	 */
> >>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>> +				 struct drm_connector *connector);
> >>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>> presence of another one?
> >>>>>>
> >>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>> and connector->edid correctly.
> >>>>>
> >>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>> in the connector is up-to-date? With your current callback design that's
> >>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>> correctly updated.
> >>>>>
> >>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>> edid, which is not so awesome :-)
> >>>>> -Daniel
> >>>>>
> >>>>>> Regards
> >>>>>>
> >>>>>> Andrzej
> >>>>>>
> >>>>>>
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @lost_hotplug:
> >>>>>>> +	 *
> >>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>> +	 * HDMI bridges.
> >>>>>>> +	 */
> >>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_enable:
> >>>>>>> +	 *
> >>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>> +	 * @hpd_disable.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>> +	 */
> >>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_disable:
> >>>>>>> +	 *
> >>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>> +	 * connection status occurs.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>> +	 */
> >>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>  };
> >>>>>>>  
> >>>>>>>  /**
> >>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>  	bool dual_link;
> >>>>>>>  };
> >>>>>>>  
> >>>>>>> +/**
> >>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>> + */
> >>>>>>> +enum drm_bridge_ops {
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>> +};
> >>>>>>> +
> >>>>>>>  /**
> >>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>   */
> >>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>  	void *driver_private;
> >>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>> +	/**
> >>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>> +	 * identifies the type of connected display.
> >>>>>>> +	 */
> >>>>>>> +	int type;
> >>>>>>> +	/** private: */
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>> +	 */
> >>>>>>> +	struct mutex hpd_mutex;
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>> +	 */
> >>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>> +	 * @hpd_cb.
> >>>>>>> +	 */
> >>>>>>> +	void *hpd_data;
> >>>>>>>  };
> >>>>>>>  
> >>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>  
> >>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>> +			   void (*cb)(void *data,
> >>>>>>> +				      enum drm_connector_status status),
> >>>>>>> +			   void *data);
> >>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>> +			   enum drm_connector_status status);
> >>>>>>> +
> >>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>  					u32 connector_type);
> >>
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 10/60] drm/bridge: Add bridge driver for display connectors
  2019-07-07 18:18   ` [PATCH 10/60] drm/bridge: Add bridge driver for display connectors Laurent Pinchart
@ 2019-07-16  9:28     ` Sam Ravnborg
  2019-08-08 16:41       ` Laurent Pinchart
  2019-09-30 11:15     ` Tomi Valkeinen
  1 sibling, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-16  9:28 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Laurent.

Two small details, see below.

	Sam

On Sun, Jul 07, 2019 at 09:18:47PM +0300, Laurent Pinchart wrote:
> Display connectors are modelled in DT as a device node, but have so far
> been handled manually in several bridge drivers. This resulted in
> duplicate code in several bridge drivers, with slightly different (and
> thus confusing) logics.
> 
> In order to fix this, implement a bridge driver for display connectors.
> The driver centralises logic for the DVI, HDMI, VGAn composite and
> S-video connectors and exposes corresponding bridge operations.
> 
> This driver in itself doesn't solve the issue completely, changes in
> bridge and display controller drivers are needed to make use of the new
> connector driver.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/display-connector.c | 327 +++++++++++++++++++++
>  3 files changed, 339 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/display-connector.c
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index a78392e2dbb9..295a62f65ef9 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -37,6 +37,17 @@ config DRM_CDNS_DSI
>  	  Support Cadence DPI to DSI bridge. This is an internal
>  	  bridge and is meant to be directly embedded in a SoC.
>  
> +config DRM_DISPLAY_CONNECTOR
> +	tristate "Display connector support"
> +	depends on OF
> +	help
> +	  Driver for display connectors with support for DDC and hot-plug
> +	  detection. Most display controller handle display connectors
> +	  internally and don't need this driver, but the DRM subsystem is
> +	  moving towards separating connector handling from display controllers
> +	  on ARM-based platforms. Saying Y here when this driver is not needed
> +	  will not cause any issue.
> +
>  config DRM_LVDS_ENCODER
>  	tristate "Transparent parallel to LVDS encoder support"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 6ff7f2adbb0e..e5987b3aaf62 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> +obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
>  obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
> new file mode 100644
> index 000000000000..2e1e7ee89275
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/display-connector.c
> @@ -0,0 +1,327 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> + */
> +
...
> +
> +static void display_connector_hpd_enable(struct drm_bridge *bridge)
> +{
> +}
> +
> +static void display_connector_hpd_disable(struct drm_bridge *bridge)
> +{
> +}

It seems wrong that a new driver needs empty implementation of
hpd_enable() and hpd_disable().
I noticed the same in a later patch too.

Can we do without these empty functions?

> +
> +static const struct drm_bridge_funcs display_connector_bridge_funcs = {
> +	.attach = display_connector_attach,
> +	.detect = display_connector_detect,
> +	.get_edid = display_connector_get_edid,
> +	.hpd_enable = display_connector_hpd_enable,
> +	.hpd_disable = display_connector_hpd_disable,
> +};
> +
> +	struct display_connector *conn = arg;
> +	struct drm_bridge *bridge = &conn->bridge;
> +
> +	drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static const char *display_connector_type_name(struct display_connector *conn)
> +{
> +	switch (conn->bridge.type) {
> +	case DRM_MODE_CONNECTOR_Composite:
> +		return "Composite";
> +	case DRM_MODE_CONNECTOR_DVIA:
> +		return "DVI-A";
> +	case DRM_MODE_CONNECTOR_DVID:
> +		return "DVI-D";
> +	case DRM_MODE_CONNECTOR_DVII:
> +		return "DVI-I";
> +	case DRM_MODE_CONNECTOR_HDMIA:
> +		return "HDMI-A";
> +	case DRM_MODE_CONNECTOR_HDMIB:
> +		return "HDMI-B";
> +	case DRM_MODE_CONNECTOR_SVIDEO:
> +		return "S-Video";
> +	case DRM_MODE_CONNECTOR_VGA:
> +		return "VGA";
> +	default:
> +		return "unknown";
> +	}
> +}
We already have the relation DRM_MODE_CONNECTOR <=> name in drm_connector -
see drm_connector_enum_list.

Add a small function in drm_connector.c get the name, so we do not hardcode the
name twice?

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...]
  2019-07-07 18:18   ` [PATCH 12/60] drm/bridge: panel: Implement bridge connector operations Laurent Pinchart
@ 2019-07-16 11:08     ` Sam Ravnborg
  2019-08-08 16:07       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-07-16 11:08 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Laurent et all.

> +static int panel_bridge_get_modes(struct drm_bridge *bridge,
> +				  struct drm_connector *connector)
> +{
> +	struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
> +
> +	/*
> +	 * FIXME: drm_panel_get_modes() should take the connector as an
> +	 * argument.
> +	 */
> +	return drm_panel_get_modes(panel_bridge->panel);
> +}

I took a look at this - it seems simple:
- Update drm_panel.get_modes() to take controller as argument, and fix
  all callers. All callers already have connector available.
- Drop drm_panel_attach(), drm_panel_detach() and update all callers.
  In reality just drop all code around attach(), detach().
  drm_panel_attach(), drm_panel_detach() will be noops when the
  connector stored in drm_panel is no longer used.

The semantic difference is that we supply the connector when we call
drm_panel_get_modes() and not at panel creation time with an drm_panel_attach().

So it should be doable without any migration from one world to the other.

If someone can say "yes it should be that simple", then I will
give it a spin.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-16  9:00                   ` Daniel Vetter
@ 2019-07-16 13:57                     ` Andrzej Hajda
  2019-08-08 19:32                       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-16 13:57 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On 16.07.2019 11:00, Daniel Vetter wrote:
> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
>> On 11.07.2019 17:50, Daniel Vetter wrote:
>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>>>> Hi Laurent,
>>>>>>>>
>>>>>>>>
>>>>>>>> I like the approach, current practice when almost every bridge should
>>>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>>>> panel is very painful.
>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>>>> get a feeling for what your hpd_cb usually does.
>>>>>>>
>>>>>>>> More comments inlined.
>>>>>>>>
>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>>>> data:
>>>>>>>>>
>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>>>   retrieval operations
>>>>>>>>> - Bitmask of supported operations
>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>>>> operation's callback?
>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>>>> add, generally good excuse to not have to think through the design between
>>>>>>> different parts of drivers - "just" add another flag.
>>>>>>>>> - Bridge output type
>>>>>>>>>
>>>>>>>>> Add and document these.
>>>>>>>>>
>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>>>> bridges.
>>>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>>>> right? More comments about it later.
>>>>>>>>
>>>>>>>>
>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>>>> ---
>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>>>
>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>>>   */
>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>>>  {
>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>>>> +
>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>> +
>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>>>  }
>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>>>  
>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>  }
>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>>>  
>>>>>>>>> +/**
>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>> + * @cb: hot-plug detection callback
>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>>>> + *
>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>>>> + *
>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>> + *
>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>>>> + * the bridge.
>>>>>>>>> + */
>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>>>> bridge attach:
>>>>>>>>
>>>>>>>> bridge->hpd_cb = cb;
>>>>>>>>
>>>>>>>> bridge->hpd_data = data;
>>>>>>>>
>>>>>>>> ret = drm_bridge_attach(...);
>>>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>>>
>>>>>>>
>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>>>> without big sacrifices.
>>>>>>>>
>>>>>>>>
>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>>>
>>>>>>>>
>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>> +			   void *data)
>>>>>>>>> +{
>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>>>> +		return;
>>>>>>>>> +
>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>> +
>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>>>> +		goto unlock;
>>>>>>>>> +
>>>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>>>> +	bridge->hpd_data = data;
>>>>>>>>> +
>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>>>> +
>>>>>>>>> +unlock:
>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>> +}
>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>>>> +
>>>>>>>>> +/**
>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>> + *
>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>>>> + * output status change occurs.
>>>>>>>>> + *
>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>> + */
>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>>>> +{
>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>>>> +		return;
>>>>>>>>> +
>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>>>> +
>>>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>> +}
>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>>>> +
>>>>>>>>> +/**
>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>> + * @status: output connection status
>>>>>>>>> + *
>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>>>> + *
>>>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>>>> + */
>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>> +			   enum drm_connector_status status)
>>>>>>>>> +{
>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>> +	if (bridge->hpd_cb)
>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>>>
>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>>>> 		if (tmp_bridge == bridge)
>>>>>>> 			continue;
>>>>>>> 		if (bridge->hpd_notify);
>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>>>> 	}
>>>>>>>
>>>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>>>
>>>>>>> 	dev = bridge->dev
>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>>>
>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>>>> hpd they want/need.
>>>>>> As I understand you want to notify every member of the pipeline.
>>>>>>
>>>>>> I think it should be enough to notify only the source, and then source
>>>>>> should decide if/when the hpd should be propagated upstream.
>>>>>>
>>>>>> It looks more generic for me.
>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>>>> the one from Laurent? Kinda confused here.
>>>> Regarding general idea:
>>>>
>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
>>>> source.
>>>>
>>>> 2. Your is to notify all other bridges and encoder.
>>>>
>>>>
>>>> And I prefer 1st approach, why:
>>>>
>>>> - the source can decide if/when and to who propagate the signal,
>>>>
>>>> - is more generic, for example if bridge send signal to two
>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
>>> With Laurent's approach the bridge cannot send the hpd to more than one
>>> consumer. There's only 1 callback. So you're example doesn't work.
>>
>> If there will be two consumers, there will be two bridge attachments,
>> thus there will be two notifications, it should work.
> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> callback is registered on the produce bridge, not on the consumer bridge
> (or I'm totallly misreading what Laurent does here).


I have assumed that if devices exposes two hardware sink interfaces it
will expose two separate bridges - of course it will not work with
"bridge chaining" thing, but this is a different story.


>
>>>> - it resembles hardware wires :)
>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
>>> interested in that hpd singal. This includes:
>>> - Other bridges, e.g. if they provide CEC support.
>>> - Other bridges, maybe they need to re-run the HDCP state engine
>>> - Overall driver, so it can update the modes/connector status and send the
>>>   uevent to the driver.
>>> - Overall display pipeline for this specific bridge, maybe you need to
>>>   shut down/re-enable the pipe because $reasons.
>>>  
>>> That's at least my understanding from lots of chats with Laurent about
>>> what he wants to do here.
>>
>> I do not know the full picture, but the solution where particular bridge
>> notifies everything unconditionally seems to me much less flexible.
>>
>> If HPD signals is received by the consumer, if there are no obstacles it
>> can propagate it further, upstream bridge/encoder or to drm core - it
>> will mimic your scenario.
>>
>> But there are also other scenarios where bridge does not want to
>> propagate signal, because for example:
>>
>> - it wants to wait for other sinks to wake up,
> The other sink can just do that in their hpd callback.
>
>> - it propagates HPD signal via hardware wire,
> Again, the other sink can just not listen to sw hpd in that case, and use
> the wire/hw hpd interrupt.


If it should ignore HPD, why it should receive it at all - it is
unnecessary noise. And I am afraid with more complicated pipelines it
will be impossible for particular component (bridge/encoder/whatever) to
distinguish if HPD notification which came from non-directly connected
component should be ignored or not.


>
>> - first it wants to verify if the sink is valid/compatible/authorized
>> device.
> Now you lost me. Why would someone glue incompatible IP into a SoC or
> board?


Bridge can have external connectors, and the user can connect there
anything.


>
>> In general HPD is input signal for notify of state changes on particular
>> bus, in case of typical video bridge on its output video bus.
>>
>> In case of bridges they have also input video buses, and they can send
>> HPD signal via this bus, but this is indeed different HPD signal, even
>> if for most cases they looks similar.
> Ah, I think this is a problem we will eventually have. But it's not
> something we're currently solving here at all I think.


Currently sii8620 device in tm2 sends hpd signal upstream via hardware
line, so this is not something from far future. And I guess with HPD
broadcasting it could be racy/error prone, for example EDID reading can
fail due to bridge being not ready (ddc of sii8620 is connected to i2c
controller via hw wires also).


>
>>>> And regarding implementation:
>>>>
>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>>>
>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>>>
>>>>
>>>> Your proposition is more straightforward, but if we want to notify only
>>>> source we should locate it by parsing notification chain (what about
>>>> unchained bridges), or store pointer somewhere during attachment.
>>>>
>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
>>>> similarly to sink as bridge or panel, but fixing it can be done later.
>>> Uh I think we're not talking about the same thing really. My understanding
>>> is that this callback is if someone (outside of this bridge) is interested
>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
>>> listener.
>>
>> Do we have real life examples?
>>
>> I want to distinguish two situations:
>>
>> - another device wants to know if input bus of the bridge has changed state,
>>
>> - another device wants to know if output bus of the bridge has changed
>> state.
> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> bridges can exchange state and information about each another. hpd is
> about the physical world, i.e. "is there a cable plugged into the port
> I'm driving?". We're not going to use fake hpd to update bridge state and
> fun stuff like that, we have the atomic_check machinery for this.


My question was if we have real examples that upstream device requires
knowledge about state of output line of the bridge?

To be more precise, we have following display pipeline:

A-->B-->C

And C sends HPD to B (ie signal that state of line between B and C
changed). Does A really wants to know this information? or it should
just need to know if state of line A-->B changed?


Regards

Andrzej


> -Daniel
>
>>
>> Regards
>>
>> Andrzej
>>
>>
>>> You seem to have some other idea here.
>>> -Daniel
>>>
>>>> Regards
>>>>
>>>> Andrzej
>>>>
>>>>
>>>>> -Daniel
>>>>>
>>>>>> Regards
>>>>>>
>>>>>> Andrzej
>>>>>>
>>>>>>
>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>> +}
>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>>>> +
>>>>>>>>>  #ifdef CONFIG_OF
>>>>>>>>>  /**
>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>>>  
>>>>>>>>> -#include <linux/list.h>
>>>>>>>>>  #include <linux/ctype.h>
>>>>>>>>> +#include <linux/list.h>
>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>>>  
>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>>>  	 */
>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>>>> +
>>>>>>>>> +	/**
>>>>>>>>> +	 * @detect:
>>>>>>>>> +	 *
>>>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>>>> +	 *
>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>>>> +	 *
>>>>>>>>> +	 * RETURNS:
>>>>>>>>> +	 *
>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>>>> +	 */
>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>>>> +
>>>>>>>>> +	/**
>>>>>>>>> +	 * @get_modes:
>>>>>>>>> +	 *
>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>>>> +	 *
>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>>>> +	 *
>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>>>> +	 *
>>>>>>>>> +	 * RETURNS:
>>>>>>>>> +	 *
>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>>>> +	 */
>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>>>> +			 struct drm_connector *connector);
>>>>>>>>> +
>>>>>>>>> +	/**
>>>>>>>>> +	 * @get_edid:
>>>>>>>>> +	 *
>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>>>> +	 *
>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>>>> +	 *
>>>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>>>> +	 * output.
>>>>>>>>> +	 *
>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>>>> +	 *
>>>>>>>>> +	 * RETURNS:
>>>>>>>>> +	 *
>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>>>> +	 */
>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>>>> +				 struct drm_connector *connector);
>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>>>> presence of another one?
>>>>>>>>
>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>>>> and connector->edid correctly.
>>>>>>>
>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>>>> in the connector is up-to-date? With your current callback design that's
>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>>>> correctly updated.
>>>>>>>
>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>>>> edid, which is not so awesome :-)
>>>>>>> -Daniel
>>>>>>>
>>>>>>>> Regards
>>>>>>>>
>>>>>>>> Andrzej
>>>>>>>>
>>>>>>>>
>>>>>>>>> +
>>>>>>>>> +	/**
>>>>>>>>> +	 * @lost_hotplug:
>>>>>>>>> +	 *
>>>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>>>> +	 *
>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>>>> +	 * HDMI bridges.
>>>>>>>>> +	 */
>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>>>> +
>>>>>>>>> +	/**
>>>>>>>>> +	 * @hpd_enable:
>>>>>>>>> +	 *
>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>>>> +	 * @hpd_disable.
>>>>>>>>> +	 *
>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>> +	 */
>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>>>> +
>>>>>>>>> +	/**
>>>>>>>>> +	 * @hpd_disable:
>>>>>>>>> +	 *
>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>>>> +	 * connection status occurs.
>>>>>>>>> +	 *
>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>> +	 */
>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>>>  };
>>>>>>>>>  
>>>>>>>>>  /**
>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>>>  	bool dual_link;
>>>>>>>>>  };
>>>>>>>>>  
>>>>>>>>> +/**
>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>>>> + */
>>>>>>>>> +enum drm_bridge_ops {
>>>>>>>>> +	/**
>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>>>> +	 */
>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>>>> +	/**
>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>>>> +	 */
>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>>>> +	/**
>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>>>> +	 */
>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>>>> +	/**
>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>>>> +	 */
>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>>  /**
>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>>>   */
>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>>>  	void *driver_private;
>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>>>> +	/**
>>>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>>>> +	 * identifies the type of connected display.
>>>>>>>>> +	 */
>>>>>>>>> +	int type;
>>>>>>>>> +	/** private: */
>>>>>>>>> +	/**
>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>>>> +	 */
>>>>>>>>> +	struct mutex hpd_mutex;
>>>>>>>>> +	/**
>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>>>> +	 */
>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>>>> +	/**
>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>>>> +	 * @hpd_cb.
>>>>>>>>> +	 */
>>>>>>>>> +	void *hpd_data;
>>>>>>>>>  };
>>>>>>>>>  
>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>>>  
>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>> +			   void *data);
>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>> +			   enum drm_connector_status status);
>>>>>>>>> +
>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>>>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-07-07 18:18   ` [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation Laurent Pinchart
@ 2019-07-17  6:39     ` Andrzej Hajda
  2019-08-08 14:25       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-07-17  6:39 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel
  Cc: Maxime Ripard, Sebastian Reichel, Tomi Valkeinen, Sean Paul

On 07.07.2019 20:18, Laurent Pinchart wrote:
> Most bridge drivers create a DRM connector to model the connector at the
> output of the bridge. This model is historical and has worked pretty
> well so far, but causes several issues:
>
> - It prevents supporting more complex display pipelines where DRM
> connector operations are split over multiple components. For instance a
> pipeline with a bridge connected to the DDC signals to read EDID data,
> and another one connected to the HPD signal to detect connection and
> disconnection, will not be possible to support through this model.
>
> - It requires every bridge driver to implement similar connector
> handling code, resulting in code duplication.
>
> - It assumes that a bridge will either be wired to a connector or to
> another bridge, but doesn't support bridges that can be used in both
> positions very well (although there is some ad-hoc support for this in
> the analogix_dp bridge driver).
>
> In order to solve these issues, ownership of the connector should be
> moved to the display controller driver (where it can be implemented
> using helpers provided by the core).
>
> Extend the bridge API to allow disabling connector creation in bridge
> drivers as a first step towards the new model. The new create_connector
> argument to the bridge .attach() operation tells the bridge driver
> whether to create a connector. Set the argument to true unconditionally,
> and modify all existing bridge drivers to return an error when connector
> creation is not requested as they don't support this feature yet.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
>  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
>  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
>  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
>  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
>  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
>  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
>  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
>  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
>  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
>  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
>  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
>  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
>  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
>  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
>  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
>  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
>  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
>  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
>  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
>  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
>  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
>  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
>  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
>  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
>  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
>  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
>  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
>  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
>  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
>  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
>  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
>  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
>  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
>  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
>  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
>  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
>  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
>  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
>  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
>  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
>  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
>  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
>  include/drm/drm_bridge.h                                 | 4 ++--
>  53 files changed, 140 insertions(+), 67 deletions(-)
>
> diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> index 98aac743cc26..739f2358f1d5 100644
> --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
> +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
>  		return ret;
>  
>  	/* Link drm_bridge to encoder */
> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);


Few suggestions:

1. Maybe it would be more convenient to add flags argument instead of bool:

- code should be more readable: ret = drm_bridge_attach(encoder, bridge,
NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)

- it can be easily expanded later with other flags, there at least two
drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.

2. If the patch can be applied atomically it is OK as is, if not you can
use preprocessor vararg magic to support new and old syntax, sth like:

#define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
__drm_bridge_attach(encoder, bridge, prev, flags)

#define drm_bridge_attach(encoder, bridge, prev, optarg...)
_drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)

3. Maybe more convenient would be to just set the flags directly before
attachment:

    bridge->dont_create_connector = true;

    ret = drm_bridge_attach(encoder, bridge, NULL);

    This way it will be still expandable, and less changes.


Regards

Andrzej


>  	if (ret)
>  		drm_encoder_cleanup(encoder);
>  
> diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> index f73d8a92274e..606841d2c0b0 100644
> --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> @@ -123,7 +123,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
>  	}
>  
>  	if (bridge) {
> -		ret = drm_bridge_attach(&output->encoder, bridge, NULL);
> +		ret = drm_bridge_attach(&output->encoder, bridge, NULL, true);
>  		if (!ret)
>  			return 0;
>  
> diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> index f6d2681f6927..c67ba30edec4 100644
> --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> @@ -847,11 +847,15 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
>  	adv7511_mode_set(adv, mode, adj_mode);
>  }
>  
> -static int adv7511_bridge_attach(struct drm_bridge *bridge)
> +static int adv7511_bridge_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct adv7511 *adv = bridge_to_adv7511(bridge);
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Parent encoder object not found");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix-anx78xx.c
> index 3c7cc5af735c..f72755e59e12 100644
> --- a/drivers/gpu/drm/bridge/analogix-anx78xx.c
> +++ b/drivers/gpu/drm/bridge/analogix-anx78xx.c
> @@ -998,11 +998,15 @@ static const struct drm_connector_funcs anx78xx_connector_funcs = {
>  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int anx78xx_bridge_attach(struct drm_bridge *bridge)
> +static int anx78xx_bridge_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
>  	int err;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Parent encoder object not found");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> index 3f7f4880be09..f6a1bdcc09d6 100644
> --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> @@ -1179,13 +1179,17 @@ static const struct drm_connector_funcs analogix_dp_connector_funcs = {
>  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int analogix_dp_bridge_attach(struct drm_bridge *bridge)
> +static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
> +				     bool create_connector)
>  {
>  	struct analogix_dp_device *dp = bridge->driver_private;
>  	struct drm_encoder *encoder = dp->encoder;
>  	struct drm_connector *connector = NULL;
>  	int ret = 0;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Parent encoder object not found");
>  		return -ENODEV;
> @@ -1463,7 +1467,7 @@ static int analogix_dp_create_bridge(struct drm_device *drm_dev,
>  	bridge->driver_private = dp;
>  	bridge->funcs = &analogix_dp_bridge_funcs;
>  
> -	ret = drm_bridge_attach(dp->encoder, bridge, NULL);
> +	ret = drm_bridge_attach(dp->encoder, bridge, NULL, true);
>  	if (ret) {
>  		DRM_ERROR("failed to attach drm bridge\n");
>  		return -EINVAL;
> diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c
> index 6166dca6be81..45f50852cfbb 100644
> --- a/drivers/gpu/drm/bridge/cdns-dsi.c
> +++ b/drivers/gpu/drm/bridge/cdns-dsi.c
> @@ -645,7 +645,8 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
>  	return 0;
>  }
>  
> -static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
> +static int cdns_dsi_bridge_attach(struct drm_bridge *bridge,
> +				  bool create_connector)
>  {
>  	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
>  	struct cdns_dsi *dsi = input_to_dsi(input);
> @@ -657,7 +658,8 @@ static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
>  		return -ENOTSUPP;
>  	}
>  
> -	return drm_bridge_attach(bridge->encoder, output->bridge, bridge);
> +	return drm_bridge_attach(bridge->encoder, output->bridge, bridge,
> +				 create_connector);
>  }
>  
>  static enum drm_mode_status
> diff --git a/drivers/gpu/drm/bridge/lvds-encoder.c b/drivers/gpu/drm/bridge/lvds-encoder.c
> index 2ab2c234f26c..bafab97521af 100644
> --- a/drivers/gpu/drm/bridge/lvds-encoder.c
> +++ b/drivers/gpu/drm/bridge/lvds-encoder.c
> @@ -18,14 +18,14 @@ struct lvds_encoder {
>  	struct gpio_desc *powerdown_gpio;
>  };
>  
> -static int lvds_encoder_attach(struct drm_bridge *bridge)
> +static int lvds_encoder_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct lvds_encoder *lvds_encoder = container_of(bridge,
>  							 struct lvds_encoder,
>  							 bridge);
>  
>  	return drm_bridge_attach(bridge->encoder, lvds_encoder->panel_bridge,
> -				 bridge);
> +				 bridge, create_connector);
>  }
>  
>  static void lvds_encoder_enable(struct drm_bridge *bridge)
> diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
> index 79311f8354bd..4250e2235f50 100644
> --- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
> +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
> @@ -206,13 +206,17 @@ static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)
>  	return IRQ_HANDLED;
>  }
>  
> -static int ge_b850v3_lvds_attach(struct drm_bridge *bridge)
> +static int ge_b850v3_lvds_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector;
>  	struct i2c_client *stdp4028_i2c
>  			= ge_b850v3_lvds_ptr->stdp4028_i2c;
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Parent encoder object not found");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c
> index 98bc650b8c95..6bef439261da 100644
> --- a/drivers/gpu/drm/bridge/nxp-ptn3460.c
> +++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c
> @@ -238,11 +238,15 @@ static const struct drm_connector_funcs ptn3460_connector_funcs = {
>  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int ptn3460_bridge_attach(struct drm_bridge *bridge)
> +static int ptn3460_bridge_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge);
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Parent encoder object not found");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
> index b12ae3a4c5f1..98ad4abf2409 100644
> --- a/drivers/gpu/drm/bridge/panel.c
> +++ b/drivers/gpu/drm/bridge/panel.c
> @@ -52,12 +52,15 @@ static const struct drm_connector_funcs panel_bridge_connector_funcs = {
>  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int panel_bridge_attach(struct drm_bridge *bridge)
> +static int panel_bridge_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
>  	struct drm_connector *connector = &panel_bridge->connector;
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Missing encoder\n");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c
> index 2d88146e4836..b9243d51489b 100644
> --- a/drivers/gpu/drm/bridge/parade-ps8622.c
> +++ b/drivers/gpu/drm/bridge/parade-ps8622.c
> @@ -476,11 +476,14 @@ static const struct drm_connector_funcs ps8622_connector_funcs = {
>  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int ps8622_attach(struct drm_bridge *bridge)
> +static int ps8622_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Parent encoder object not found");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
> index dd7aa466b280..18904b1082b6 100644
> --- a/drivers/gpu/drm/bridge/sii902x.c
> +++ b/drivers/gpu/drm/bridge/sii902x.c
> @@ -396,12 +396,16 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
>  	mutex_unlock(&sii902x->mutex);
>  }
>  
> -static int sii902x_bridge_attach(struct drm_bridge *bridge)
> +static int sii902x_bridge_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct sii902x *sii902x = bridge_to_sii902x(bridge);
>  	struct drm_device *drm = bridge->dev;
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	drm_connector_helper_add(&sii902x->connector,
>  				 &sii902x_connector_helper_funcs);
>  
> diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
> index 0cc293a6ac24..ea6529df7d9c 100644
> --- a/drivers/gpu/drm/bridge/sil-sii8620.c
> +++ b/drivers/gpu/drm/bridge/sil-sii8620.c
> @@ -2203,7 +2203,7 @@ static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
>  	return container_of(bridge, struct sii8620, bridge);
>  }
>  
> -static int sii8620_attach(struct drm_bridge *bridge)
> +static int sii8620_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct sii8620 *ctx = bridge_to_sii8620(bridge);
>  
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
> index 7495b9bef865..86885eb6e28d 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -108,11 +108,15 @@ static const struct drm_connector_funcs simple_bridge_con_funcs = {
>  	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int simple_bridge_attach(struct drm_bridge *bridge)
> +static int simple_bridge_attach(struct drm_bridge *bridge,
> +				bool create_connector)
>  {
>  	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
>  	int ret;
>  
> +	if (!create_connector)
> +		return 0;
> +
>  	if (!bridge->encoder) {
>  		DRM_ERROR("Missing encoder\n");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> index c6490949d9db..930d67c618dd 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> @@ -2174,12 +2174,16 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
>  	.get_modes = dw_hdmi_connector_get_modes,
>  };
>  
> -static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
> +static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct dw_hdmi *hdmi = bridge->driver_private;
>  	struct drm_encoder *encoder = bridge->encoder;
>  	struct drm_connector *connector = &hdmi->connector;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	connector->interlace_allowed = 1;
>  	connector->polled = DRM_CONNECTOR_POLL_HPD;
>  
> @@ -2857,7 +2861,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
>  	if (IS_ERR(hdmi))
>  		return hdmi;
>  
> -	ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL);
> +	ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, true);
>  	if (ret) {
>  		dw_hdmi_remove(hdmi);
>  		DRM_ERROR("Failed to initialize bridge with drm\n");
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
> index 281c58bab1a1..05cf97ad524f 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
> @@ -906,7 +906,8 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
>  	return mode_status;
>  }
>  
> -static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
> +static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
> +				     bool create_connector)
>  {
>  	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
>  
> @@ -919,7 +920,8 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
>  	bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
>  
>  	/* Attach the panel-bridge to the dsi bridge */
> -	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
> +	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
> +				 create_connector);
>  }
>  
>  static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
> @@ -1064,7 +1066,7 @@ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
>  {
>  	int ret;
>  
> -	ret = drm_bridge_attach(encoder, &dsi->bridge, NULL);
> +	ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, true);
>  	if (ret) {
>  		DRM_ERROR("Failed to initialize bridge with drm\n");
>  		return ret;
> diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
> index 170f162ffa55..6016f3aae42f 100644
> --- a/drivers/gpu/drm/bridge/tc358764.c
> +++ b/drivers/gpu/drm/bridge/tc358764.c
> @@ -348,12 +348,15 @@ static void tc358764_enable(struct drm_bridge *bridge)
>  		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
>  }
>  
> -static int tc358764_attach(struct drm_bridge *bridge)
> +static int tc358764_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct tc358764 *ctx = bridge_to_tc358764(bridge);
>  	struct drm_device *drm = bridge->dev;
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
>  	ret = drm_connector_init(drm, &ctx->connector,
>  				 &tc358764_connector_funcs,
> diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
> index 13ade28a36a8..e2b2d2660adc 100644
> --- a/drivers/gpu/drm/bridge/tc358767.c
> +++ b/drivers/gpu/drm/bridge/tc358767.c
> @@ -1273,13 +1273,16 @@ static const struct drm_connector_funcs tc_connector_funcs = {
>  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int tc_bridge_attach(struct drm_bridge *bridge)
> +static int tc_bridge_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
>  	struct tc_data *tc = bridge_to_tc(bridge);
>  	struct drm_device *drm = bridge->dev;
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	/* Create DP/eDP connector */
>  	drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
>  	ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,
> diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c
> index 3d74129b2995..86f3b96f95db 100644
> --- a/drivers/gpu/drm/bridge/thc63lvd1024.c
> +++ b/drivers/gpu/drm/bridge/thc63lvd1024.c
> @@ -42,11 +42,12 @@ static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
>  	return container_of(bridge, struct thc63_dev, bridge);
>  }
>  
> -static int thc63_attach(struct drm_bridge *bridge)
> +static int thc63_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct thc63_dev *thc63 = to_thc63(bridge);
>  
> -	return drm_bridge_attach(bridge->encoder, thc63->next, bridge);
> +	return drm_bridge_attach(bridge->encoder, thc63->next, bridge,
> +				 create_connector);
>  }
>  
>  static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> index b77a52d05061..dbe265f7112d 100644
> --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> @@ -224,7 +224,7 @@ static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
>  				       pdata->supplies);
>  }
>  
> -static int ti_sn_bridge_attach(struct drm_bridge *bridge)
> +static int ti_sn_bridge_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	int ret, val;
>  	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> @@ -235,6 +235,9 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
>  						   .node = NULL,
>  						 };
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	ret = drm_connector_init(bridge->dev, &pdata->connector,
>  				 &ti_sn_bridge_connector_funcs,
>  				 DRM_MODE_CONNECTOR_eDP);
> diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
> index 4e76b2b27374..8d4690e436c3 100644
> --- a/drivers/gpu/drm/bridge/ti-tfp410.c
> +++ b/drivers/gpu/drm/bridge/ti-tfp410.c
> @@ -121,11 +121,14 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
>  	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
>  };
>  
> -static int tfp410_attach(struct drm_bridge *bridge)
> +static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	if (!bridge->encoder) {
>  		dev_err(dvi->dev, "Missing encoder\n");
>  		return -ENODEV;
> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> index cba537c99e43..519577f363e3 100644
> --- a/drivers/gpu/drm/drm_bridge.c
> +++ b/drivers/gpu/drm/drm_bridge.c
> @@ -95,6 +95,7 @@ EXPORT_SYMBOL(drm_bridge_remove);
>   * @encoder: DRM encoder
>   * @bridge: bridge to attach
>   * @previous: previous bridge in the chain (optional)
> + * @create_connector: true if the bridge should create a drm_connector
>   *
>   * Called by a kms driver to link the bridge to an encoder's chain. The previous
>   * argument specifies the previous bridge in the chain. If NULL, the bridge is
> @@ -112,7 +113,7 @@ EXPORT_SYMBOL(drm_bridge_remove);
>   * Zero on success, error code on failure
>   */
>  int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
> -		      struct drm_bridge *previous)
> +		      struct drm_bridge *previous, bool create_connector)
>  {
>  	int ret;
>  
> @@ -129,7 +130,7 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
>  	bridge->encoder = encoder;
>  
>  	if (bridge->funcs->attach) {
> -		ret = bridge->funcs->attach(bridge);
> +		ret = bridge->funcs->attach(bridge, create_connector);
>  		if (ret < 0) {
>  			bridge->dev = NULL;
>  			bridge->encoder = NULL;
> diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c
> index b11910f14c46..a367ef1e5081 100644
> --- a/drivers/gpu/drm/drm_simple_kms_helper.c
> +++ b/drivers/gpu/drm/drm_simple_kms_helper.c
> @@ -228,7 +228,7 @@ static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
>  int drm_simple_display_pipe_attach_bridge(struct drm_simple_display_pipe *pipe,
>  					  struct drm_bridge *bridge)
>  {
> -	return drm_bridge_attach(&pipe->encoder, bridge, NULL);
> +	return drm_bridge_attach(&pipe->encoder, bridge, NULL, true);
>  }
>  EXPORT_SYMBOL(drm_simple_display_pipe_attach_bridge);
>  
> diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
> index 3a0f0ba8c63a..02b98e6ca52d 100644
> --- a/drivers/gpu/drm/exynos/exynos_dp.c
> +++ b/drivers/gpu/drm/exynos/exynos_dp.c
> @@ -105,7 +105,8 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
>  
>  	/* Pre-empt DP connector creation if there's a bridge */
>  	if (dp->ptn_bridge) {
> -		ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge);
> +		ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge,
> +					true);
>  		if (ret) {
>  			DRM_DEV_ERROR(dp->dev,
>  				      "Failed to attach bridge to drm\n");
> diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
> index 5f6f523821a2..768acc79d10c 100644
> --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c
> +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
> @@ -1522,7 +1522,7 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
>  
>  	out_bridge  = of_drm_find_bridge(device->dev.of_node);
>  	if (out_bridge) {
> -		drm_bridge_attach(encoder, out_bridge, NULL);
> +		drm_bridge_attach(encoder, out_bridge, NULL, true);
>  		dsi->out_bridge = out_bridge;
>  		encoder->bridge = NULL;
>  	} else {
> @@ -1698,7 +1698,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master,
>  	if (dsi->in_bridge_node) {
>  		in_bridge = of_drm_find_bridge(dsi->in_bridge_node);
>  		if (in_bridge)
> -			drm_bridge_attach(encoder, in_bridge, NULL);
> +			drm_bridge_attach(encoder, in_bridge, NULL, true);
>  	}
>  
>  	return mipi_dsi_host_register(&dsi->dsi_host);
> diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
> index bc1565f1822a..808c98101d56 100644
> --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
> +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
> @@ -952,7 +952,7 @@ static int hdmi_create_connector(struct drm_encoder *encoder)
>  	drm_connector_attach_encoder(connector, encoder);
>  
>  	if (hdata->bridge) {
> -		ret = drm_bridge_attach(encoder, hdata->bridge, NULL);
> +		ret = drm_bridge_attach(encoder, hdata->bridge, NULL, true);
>  		if (ret)
>  			DRM_DEV_ERROR(hdata->dev, "Failed to attach bridge\n");
>  	}
> diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
> index c49e9e3740f8..8d22618ccf2e 100644
> --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
> +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
> @@ -159,5 +159,5 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
>  		return fsl_dcu_attach_panel(fsl_dev, panel);
>  	}
>  
> -	return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL);
> +	return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, true);
>  }
> diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
> index 3d6c45097f51..eac8ec1512ab 100644
> --- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
> +++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
> @@ -780,7 +780,7 @@ static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
>  	int ret;
>  
>  	/* associate the bridge to dsi encoder */
> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
>  	if (ret) {
>  		DRM_ERROR("failed to attach external bridge\n");
>  		return ret;
> diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
> index 3d368c43185f..6b2e648b6c4d 100644
> --- a/drivers/gpu/drm/i2c/tda998x_drv.c
> +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
> @@ -1366,10 +1366,14 @@ static int tda998x_connector_init(struct tda998x_priv *priv,
>  
>  /* DRM bridge functions */
>  
> -static int tda998x_bridge_attach(struct drm_bridge *bridge)
> +static int tda998x_bridge_attach(struct drm_bridge *bridge,
> +				 bool create_connector)
>  {
>  	struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge);
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	return tda998x_connector_init(priv, bridge->dev);
>  }
>  
> @@ -2033,7 +2037,7 @@ static int tda998x_encoder_init(struct device *dev, struct drm_device *drm)
>  	if (ret)
>  		goto err_encoder;
>  
> -	ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL);
> +	ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL, true);
>  	if (ret)
>  		goto err_bridge;
>  
> diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
> index 383733302280..845ead25ade7 100644
> --- a/drivers/gpu/drm/imx/imx-ldb.c
> +++ b/drivers/gpu/drm/imx/imx-ldb.c
> @@ -446,7 +446,7 @@ static int imx_ldb_register(struct drm_device *drm,
>  
>  	if (imx_ldb_ch->bridge) {
>  		ret = drm_bridge_attach(&imx_ldb_ch->encoder,
> -					imx_ldb_ch->bridge, NULL);
> +					imx_ldb_ch->bridge, NULL, true);
>  		if (ret) {
>  			DRM_ERROR("Failed to initialize bridge with drm\n");
>  			return ret;
> diff --git a/drivers/gpu/drm/imx/parallel-display.c b/drivers/gpu/drm/imx/parallel-display.c
> index 1a76de1e8e7b..cd746592d2a7 100644
> --- a/drivers/gpu/drm/imx/parallel-display.c
> +++ b/drivers/gpu/drm/imx/parallel-display.c
> @@ -182,7 +182,7 @@ static int imx_pd_register(struct drm_device *drm,
>  		drm_panel_attach(imxpd->panel, &imxpd->connector);
>  
>  	if (imxpd->bridge) {
> -		ret = drm_bridge_attach(encoder, imxpd->bridge, NULL);
> +		ret = drm_bridge_attach(encoder, imxpd->bridge, NULL, true);
>  		if (ret < 0) {
>  			dev_err(imxpd->dev, "failed to attach bridge: %d\n",
>  				ret);
> diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
> index 07f7090d08b3..f2deadc980f8 100644
> --- a/drivers/gpu/drm/mcde/mcde_dsi.c
> +++ b/drivers/gpu/drm/mcde/mcde_dsi.c
> @@ -817,12 +817,16 @@ mcde_dsi_connector_helper_funcs = {
>  	.get_modes = mcde_dsi_get_modes,
>  };
>  
> -static int mcde_dsi_bridge_attach(struct drm_bridge *bridge)
> +static int mcde_dsi_bridge_attach(struct drm_bridge *bridge,
> +				  bool create_connector)
>  {
>  	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
>  	struct drm_device *drm = bridge->dev;
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	drm_connector_helper_add(&d->connector,
>  				 &mcde_dsi_connector_helper_funcs);
>  
> diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c
> index bacd989cc9aa..1ff27bb17016 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dpi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c
> @@ -604,7 +604,7 @@ static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
>  	/* Currently DPI0 is fixed to be driven by OVL1 */
>  	dpi->encoder.possible_crtcs = BIT(1);
>  
> -	ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL);
> +	ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL, true);
>  	if (ret) {
>  		dev_err(dev, "Failed to attach bridge: %d\n", ret);
>  		goto err_cleanup;
> diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> index b91c4616644a..9c5bac48b44f 100644
> --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
> @@ -819,7 +819,7 @@ static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
>  
>  	/* If there's a bridge, attach to it and let it create the connector */
>  	if (dsi->bridge) {
> -		ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL);
> +		ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL, true);
>  		if (ret) {
>  			DRM_ERROR("Failed to attach bridge to drm\n");
>  			goto err_encoder_cleanup;
> diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> index 5d6a9f094df5..d3248a881cf0 100644
> --- a/drivers/gpu/drm/mediatek/mtk_hdmi.c
> +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> @@ -1290,11 +1290,15 @@ static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
>   * Bridge callbacks
>   */
>  
> -static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
> +static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge,
> +				  bool create_connector)
>  {
>  	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
>  	int ret;
>  
> +	if (!create_connector)
> +		return -EINVAL;
> +
>  	ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn,
>  				 &mtk_hdmi_connector_funcs,
>  				 DRM_MODE_CONNECTOR_HDMIA);
> @@ -1318,7 +1322,7 @@ static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
>  
>  	if (hdmi->next_bridge) {
>  		ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
> -					bridge);
> +					bridge, create_connector);
>  		if (ret) {
>  			dev_err(hdmi->dev,
>  				"Failed to attach external bridge: %d\n", ret);
> diff --git a/drivers/gpu/drm/msm/dsi/dsi_manager.c b/drivers/gpu/drm/msm/dsi/dsi_manager.c
> index 271aa7bbca92..ca733086041a 100644
> --- a/drivers/gpu/drm/msm/dsi/dsi_manager.c
> +++ b/drivers/gpu/drm/msm/dsi/dsi_manager.c
> @@ -664,7 +664,7 @@ struct drm_bridge *msm_dsi_manager_bridge_init(u8 id)
>  	bridge = &dsi_bridge->base;
>  	bridge->funcs = &dsi_mgr_bridge_funcs;
>  
> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
>  	if (ret)
>  		goto fail;
>  
> @@ -693,7 +693,7 @@ struct drm_connector *msm_dsi_manager_ext_bridge_init(u8 id)
>  	encoder = msm_dsi->encoder;
>  
>  	/* link the internal dsi bridge to the external bridge */
> -	drm_bridge_attach(encoder, ext_bridge, int_bridge);
> +	drm_bridge_attach(encoder, ext_bridge, int_bridge, true);
>  
>  	/*
>  	 * we need the drm_connector created by the external bridge
> diff --git a/drivers/gpu/drm/msm/edp/edp_bridge.c b/drivers/gpu/drm/msm/edp/edp_bridge.c
> index 2950bba4aca9..32a463c84cc1 100644
> --- a/drivers/gpu/drm/msm/edp/edp_bridge.c
> +++ b/drivers/gpu/drm/msm/edp/edp_bridge.c
> @@ -91,7 +91,7 @@ struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp)
>  	bridge = &edp_bridge->base;
>  	bridge->funcs = &edp_bridge_funcs;
>  
> -	ret = drm_bridge_attach(edp->encoder, bridge, NULL);
> +	ret = drm_bridge_attach(edp->encoder, bridge, NULL, true);
>  	if (ret)
>  		goto fail;
>  
> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
> index 03197b8959ba..d7738aafcff8 100644
> --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
> +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
> @@ -296,7 +296,7 @@ struct drm_bridge *msm_hdmi_bridge_init(struct hdmi *hdmi)
>  	bridge = &hdmi_bridge->base;
>  	bridge->funcs = &msm_hdmi_bridge_funcs;
>  
> -	ret = drm_bridge_attach(hdmi->encoder, bridge, NULL);
> +	ret = drm_bridge_attach(hdmi->encoder, bridge, NULL, true);
>  	if (ret)
>  		goto fail;
>  
> diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
> index 672e0f8ad11c..837d0cd20dd1 100644
> --- a/drivers/gpu/drm/omapdrm/omap_drv.c
> +++ b/drivers/gpu/drm/omapdrm/omap_drv.c
> @@ -301,7 +301,8 @@ static int omap_modeset_init(struct drm_device *dev)
>  
>  		if (pipe->output->bridge) {
>  			ret = drm_bridge_attach(pipe->encoder,
> -						pipe->output->bridge, NULL);
> +						pipe->output->bridge, NULL,
> +						true);
>  			if (ret < 0)
>  				return ret;
>  		}
> diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> index 0f00bdfe2366..74c2ae5ce687 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> @@ -120,7 +120,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
>  	 * Attach the bridge to the encoder. The bridge will create the
>  	 * connector.
>  	 */
> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
>  	if (ret) {
>  		drm_encoder_cleanup(encoder);
>  		return ret;
> diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> index 1c62578590f4..a8d8b05c4731 100644
> --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
> +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> @@ -605,7 +605,7 @@ static void rcar_lvds_mode_set(struct drm_bridge *bridge,
>  	rcar_lvds_get_lvds_mode(lvds);
>  }
>  
> -static int rcar_lvds_attach(struct drm_bridge *bridge)
> +static int rcar_lvds_attach(struct drm_bridge *bridge, bool create_connector)
>  {
>  	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
>  	struct drm_connector *connector = &lvds->connector;
> @@ -615,7 +615,10 @@ static int rcar_lvds_attach(struct drm_bridge *bridge)
>  	/* If we have a next bridge just attach it. */
>  	if (lvds->next_bridge)
>  		return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
> -					 bridge);
> +					 bridge, create_connector);
> +
> +	if (!create_connector)
> +		return -EINVAL;
>  
>  	/* Otherwise if we have a panel, create a connector. */
>  	if (!lvds->panel)
> diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
> index 830858a809e5..7ca412d294b2 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
> @@ -440,7 +440,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
>  			goto err_free_connector;
>  		}
>  	} else {
> -		ret = drm_bridge_attach(encoder, lvds->bridge, NULL);
> +		ret = drm_bridge_attach(encoder, lvds->bridge, NULL, true);
>  		if (ret) {
>  			DRM_DEV_ERROR(drm_dev->dev,
>  				      "failed to attach bridge: %d\n", ret);
> diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
> index ce4d82d293e4..8218bbd09a72 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
> @@ -143,7 +143,7 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
>  
>  	rgb->bridge = bridge;
>  
> -	ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
> +	ret = drm_bridge_attach(encoder, rgb->bridge, NULL, true);
>  	if (ret) {
>  		DRM_DEV_ERROR(drm_dev->dev,
>  			      "failed to attach bridge: %d\n", ret);
> diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c
> index 9e6d5d8b7030..f09209621568 100644
> --- a/drivers/gpu/drm/sti/sti_dvo.c
> +++ b/drivers/gpu/drm/sti/sti_dvo.c
> @@ -468,7 +468,7 @@ static int sti_dvo_bind(struct device *dev, struct device *master, void *data)
>  	bridge->of_node = dvo->dev.of_node;
>  	drm_bridge_add(bridge);
>  
> -	err = drm_bridge_attach(encoder, bridge, NULL);
> +	err = drm_bridge_attach(encoder, bridge, NULL, true);
>  	if (err) {
>  		DRM_ERROR("Failed to attach bridge\n");
>  		return err;
> diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c
> index 94e404f13234..87e0fb742dc8 100644
> --- a/drivers/gpu/drm/sti/sti_hda.c
> +++ b/drivers/gpu/drm/sti/sti_hda.c
> @@ -700,7 +700,7 @@ static int sti_hda_bind(struct device *dev, struct device *master, void *data)
>  
>  	bridge->driver_private = hda;
>  	bridge->funcs = &sti_hda_bridge_funcs;
> -	drm_bridge_attach(encoder, bridge, NULL);
> +	drm_bridge_attach(encoder, bridge, NULL, true);
>  
>  	connector->encoder = encoder;
>  
> diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
> index f03d617edc4c..8c0ffe6833f9 100644
> --- a/drivers/gpu/drm/sti/sti_hdmi.c
> +++ b/drivers/gpu/drm/sti/sti_hdmi.c
> @@ -1276,7 +1276,7 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
>  
>  	bridge->driver_private = hdmi;
>  	bridge->funcs = &sti_hdmi_bridge_funcs;
> -	drm_bridge_attach(encoder, bridge, NULL);
> +	drm_bridge_attach(encoder, bridge, NULL, true);
>  
>  	connector->encoder = encoder;
>  
> diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
> index 2fe6c4a8d915..10a9f848c5f6 100644
> --- a/drivers/gpu/drm/stm/ltdc.c
> +++ b/drivers/gpu/drm/stm/ltdc.c
> @@ -1053,7 +1053,7 @@ static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge)
>  	drm_encoder_init(ddev, encoder, &ltdc_encoder_funcs,
>  			 DRM_MODE_ENCODER_DPI, NULL);
>  
> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
>  	if (ret) {
>  		drm_encoder_cleanup(encoder);
>  		return -EINVAL;
> diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c
> index 3a3ba99fed22..3e5170fa1e67 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_lvds.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c
> @@ -155,7 +155,7 @@ int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
>  	}
>  
>  	if (bridge) {
> -		ret = drm_bridge_attach(encoder, bridge, NULL);
> +		ret = drm_bridge_attach(encoder, bridge, NULL, true);
>  		if (ret) {
>  			dev_err(drm->dev, "Couldn't attach our bridge\n");
>  			goto err_cleanup_connector;
> diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> index a901ec689b62..3f8629445fbb 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> @@ -252,7 +252,7 @@ int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon)
>  	}
>  
>  	if (rgb->bridge) {
> -		ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
> +		ret = drm_bridge_attach(encoder, rgb->bridge, NULL, true);
>  		if (ret) {
>  			dev_err(drm->dev, "Couldn't attach our bridge\n");
>  			goto err_cleanup_connector;
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c
> index e9969cd36610..ec693c11e455 100644
> --- a/drivers/gpu/drm/tilcdc/tilcdc_external.c
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c
> @@ -168,7 +168,7 @@ int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge)
>  
>  	priv->external_encoder->possible_crtcs = BIT(0);
>  
> -	ret = drm_bridge_attach(priv->external_encoder, bridge, NULL);
> +	ret = drm_bridge_attach(priv->external_encoder, bridge, NULL, true);
>  	if (ret) {
>  		dev_err(ddev->dev, "drm_bridge_attach() failed %d\n", ret);
>  		return ret;
> diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c
> index 34f90ca8f479..2d7c5cf0d468 100644
> --- a/drivers/gpu/drm/vc4/vc4_dpi.c
> +++ b/drivers/gpu/drm/vc4/vc4_dpi.c
> @@ -262,7 +262,7 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
>  	if (panel)
>  		bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DPI);
>  
> -	return drm_bridge_attach(dpi->encoder, bridge, NULL);
> +	return drm_bridge_attach(dpi->encoder, bridge, NULL, true);
>  }
>  
>  static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
> diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c
> index 2ea4e20b7b8a..3edd7ffc7383 100644
> --- a/drivers/gpu/drm/vc4/vc4_dsi.c
> +++ b/drivers/gpu/drm/vc4/vc4_dsi.c
> @@ -1607,7 +1607,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
>  			 DRM_MODE_ENCODER_DSI, NULL);
>  	drm_encoder_helper_add(dsi->encoder, &vc4_dsi_encoder_helper_funcs);
>  
> -	ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL);
> +	ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL, true);
>  	if (ret) {
>  		dev_err(dev, "bridge attach failed: %d\n", ret);
>  		return ret;
> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> index 7616f6562fe4..08dc15f93ded 100644
> --- a/include/drm/drm_bridge.h
> +++ b/include/drm/drm_bridge.h
> @@ -48,7 +48,7 @@ struct drm_bridge_funcs {
>  	 *
>  	 * Zero on success, error code on failure.
>  	 */
> -	int (*attach)(struct drm_bridge *bridge);
> +	int (*attach)(struct drm_bridge *bridge, bool create_connector);
>  
>  	/**
>  	 * @detach:
> @@ -404,7 +404,7 @@ void drm_bridge_add(struct drm_bridge *bridge);
>  void drm_bridge_remove(struct drm_bridge *bridge);
>  struct drm_bridge *of_drm_find_bridge(struct device_node *np);
>  int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
> -		      struct drm_bridge *previous);
> +		      struct drm_bridge *previous, bool create_connector);
>  
>  bool drm_bridge_mode_fixup(struct drm_bridge *bridge,
>  			   const struct drm_display_mode *mode,


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges
  2019-07-07 18:19   ` [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges Laurent Pinchart
@ 2019-07-18 17:01     ` Daniel Vetter
  2019-08-08 19:48       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-07-18 17:01 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Sun, Jul 07, 2019 at 09:19:02PM +0300, Laurent Pinchart wrote:
> Most bridge drivers create a DRM connector to model the connector at the
> output of the bridge. This model is historical and has worked pretty
> well so far, but causes several issues:
> 
> - It prevents supporting more complex display pipelines where DRM
> connector operations are split over multiple components. For instance a
> pipeline with a bridge connected to the DDC signals to read EDID data,
> and another one connected to the HPD signal to detect connection and
> disconnection, will not be possible to support through this model.
> 
> - It requires every bridge driver to implement similar connector
> handling code, resulting in code duplication.
> 
> - It assumes that a bridge will either be wired to a connector or to
> another bridge, but doesn't support bridges that can be used in both
> positions very well (although there is some ad-hoc support for this in
> the analogix_dp bridge driver).
> 
> In order to solve these issues, ownership of the connector needs to be
> moved to the display controller driver.
> 
> To avoid code duplication in display controller drivers, add a new
> helper to create and manage a DRM connector backed by a chain of
> bridges. All connector operations are delegating to the appropriate
> bridge in the chain.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/Makefile               |   3 +-
>  drivers/gpu/drm/drm_bridge_connector.c | 385 +++++++++++++++++++++++++
>  include/drm/drm_bridge_connector.h     |  18 ++
>  3 files changed, 405 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
>  create mode 100644 include/drm/drm_bridge_connector.h
> 
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 9f0d2ee35794..1b74653c9db9 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -37,7 +37,8 @@ drm_vram_helper-y := drm_gem_vram_helper.o \
>  		     drm_vram_mm_helper.o
>  obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
>  
> -drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper.o \
> +drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
> +		drm_dsc.o drm_probe_helper.o \
>  		drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
>  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
>  		drm_simple_kms_helper.o drm_modeset_helper.o \
> diff --git a/drivers/gpu/drm/drm_bridge_connector.c b/drivers/gpu/drm/drm_bridge_connector.c
> new file mode 100644
> index 000000000000..09f2d6bfb561
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_bridge_connector.c
> @@ -0,0 +1,385 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +#include <drm/drm_atomic_state_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_bridge_connector.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_probe_helper.h>
> +
> +/**
> + * DOC: overview
> + *
> + * The DRM bridge connector helper object provides a DRM connector
> + * implementation that wraps a chain of &struct drm_bridge. The connector
> + * operations are fully implemented based on the operations of the bridges in
> + * the chain, and don't require any intervention from the display controller
> + * driver at runtime.
> + *
> + * To use the helper, display controller drivers create a bridge connector with
> + * a call to drm_bridge_connector_init(). This associates the newly created
> + * connector with the chain of bridges passed to the function and registers it
> + * with the DRM device. At that point the connector becomes fully usable, no
> + * further operation is needed.
> + *
> + * The DRM bridge connector operations are implemented based on the operations
> + * provided by the bridges in the chain. Each connector operation is delegated
> + * to the bridge closest to the connector (at the end of the chain) that
> + * provides the relevant functionality.
> + *
> + * To make use of this helper, all bridges in the chain shall report bridge
> + * operation flags (&drm_bridge->ops) and bridge output type
> + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> + */
> +
> +/**
> + * struct drm_bridge_connector - A connector backed by a chain of bridges
> + */
> +struct drm_bridge_connector {
> +	/**
> +	 * @base: The base DRM connector
> +	 */
> +	struct drm_connector base;
> +	/**
> +	 * @bridge:
> +	 *
> +	 * The first bridge in the chain (connected to the output of the CRTC).
> +	 */
> +	struct drm_bridge *bridge;
> +	/**
> +	 * @bridge_edid:
> +	 *
> +	 * The last bridge in the chain (closest to the connector) that provides
> +	 * EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
> +	 */
> +	struct drm_bridge *bridge_edid;
> +	/**
> +	 * @bridge_hpd:
> +	 *
> +	 * The last bridge in the chain (closest to the connector) that provides
> +	 * hot-plug detection notification, if any (see &DRM_BRIDGE_OP_HPD).
> +	 */
> +	struct drm_bridge *bridge_hpd;
> +	/**
> +	 * @bridge_detect:
> +	 *
> +	 * The last bridge in the chain (closest to the connector) that provides
> +	 * connector detection, if any (see &DRM_BRIDGE_OP_DETECT).
> +	 */
> +	struct drm_bridge *bridge_detect;
> +	/**
> +	 * @bridge_detect:
> +	 *
> +	 * The last bridge in the chain (closest to the connector) that provides
> +	 * connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
> +	 */
> +	struct drm_bridge *bridge_modes;
> +	/**
> +	 * @hdmi_mode: Valid for HDMI connectors only.
> +	 */
> +	bool hdmi_mode;

This should probably be in drm_display_info somewhere, not here?


Wrt the overall design ... why do we need a new struct? If we assume (at
least for now) that we only allow one encoder for such a bridge chain
(currently still true), then you can always go from the connector to it's
only possibel encoder. And from there to the bridge chain.

Furthermore all the special bridge pointers here can just be found at
runtime by walking the bridge links. And none of these paths are hot
enough to make this a problem.

With that your drm_bridge_connector here would become just a pile of
functions as default implementations for connectors. Making it more
modular and more helper-y and easier to transition gradually.

> +};
> +
> +#define to_drm_bridge_connector(x) \
> +	container_of(x, struct drm_bridge_connector, base)
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge Connector Hot-Plug Handling
> + */
> +
> +static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
> +					    enum drm_connector_status status)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	struct drm_bridge *bridge;
> +
> +	if (status != connector_status_disconnected)
> +		return;
> +
> +	/*
> +	 * Notify all bridges in the pipeline of disconnection. This is required
> +	 * to let the HDMI encoders reset their internal state related to
> +	 * connection status, such as the CEC address.
> +	 */
> +	for (bridge = bridge_connector->bridge; bridge; bridge = bridge->next) {
> +		if (bridge->funcs->lost_hotplug)
> +			bridge->funcs->lost_hotplug(bridge);

So looking at this you pretty much implement my idea for hdp handling
already, except you're calling it ->lost_hotplug and not ->notify_hpd.
Plus you require some callback registration. Essentially my design (that I
explained in my reply to your bridge patch) would just make
drm_bridge_connector_hpd_cb() the one and only hpd_cb, and punt all hpd
handling to bridge drivers like you do here.

Ofc that leaves us with "who's calling drm_kms_helper_hotplug_event()",
and that's what the new hdp_notify on the encoder and the global
drm_mode_config_helper_funcs would be for.

> +	}
> +}
> +
> +static void drm_bridge_connector_hpd_cb(void *cb_data,
> +					enum drm_connector_status status)
> +{
> +	struct drm_bridge_connector *drm_bridge_connector = cb_data;
> +	struct drm_connector *connector = &drm_bridge_connector->base;
> +	struct drm_device *dev = connector->dev;
> +	enum drm_connector_status old_status;
> +
> +	mutex_lock(&dev->mode_config.mutex);
> +	old_status = connector->status;
> +	connector->status = status;
> +	mutex_unlock(&dev->mode_config.mutex);
> +
> +	if (old_status == status)
> +		return;
> +
> +	drm_bridge_connector_hpd_notify(connector, status);
> +
> +	drm_kms_helper_hotplug_event(dev);
> +}
> +
> +/**
> + * drm_bridge_connector_enable_hpd - Enable hot-plug detection for the connector
> + * @connector: The DRM bridge connector
> + *
> + * This function enables hot-plug detection for the given bridge connector.
> + * This is typically used by display drivers in their resume handler.
> + */
> +void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> +
> +	if (hpd)
> +		drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
> +				      bridge_connector);
> +}
> +EXPORT_SYMBOL_GPL(drm_bridge_connector_enable_hpd);
> +
> +/**
> + * drm_bridge_connector_disable_hpd - Disable hot-plug detection for the
> + * connector
> + * @connector: The DRM bridge connector
> + *
> + * This function disables hot-plug detection for the given bridge connector.
> + * This is typically used by display drivers in their suspend handler.
> + */
> +void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> +
> +	if (hpd)
> +		drm_bridge_hpd_disable(hpd);
> +}
> +EXPORT_SYMBOL_GPL(drm_bridge_connector_disable_hpd);
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge Connector Functions
> + */
> +
> +static enum drm_connector_status
> +drm_bridge_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	struct drm_bridge *detect = bridge_connector->bridge_detect;
> +	enum drm_connector_status status;
> +
> +	if (detect) {
> +		status = detect->funcs->detect(detect);
> +
> +		drm_bridge_connector_hpd_notify(connector, status);
> +	} else {
> +		switch (connector->connector_type) {
> +		case DRM_MODE_CONNECTOR_DPI:
> +		case DRM_MODE_CONNECTOR_LVDS:
> +		case DRM_MODE_CONNECTOR_DSI:
> +			status = connector_status_connected;
> +			break;
> +		default:
> +			status = connector_status_unknown;
> +			break;
> +		}
> +	}
> +
> +	return status;
> +}
> +
> +static void drm_bridge_connector_destroy(struct drm_connector *connector)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +
> +	if (bridge_connector->bridge_hpd) {
> +		struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> +
> +		drm_bridge_hpd_disable(hpd);
> +	}
> +
> +	drm_connector_unregister(connector);
> +	drm_connector_cleanup(connector);
> +
> +	kfree(bridge_connector);
> +}
> +
> +static const struct drm_connector_funcs drm_bridge_connector_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.detect = drm_bridge_connector_detect,

For that smooht helper library feeling I think we should export _detect
and get_modes at least.

Cheers, Daniel

> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_bridge_connector_destroy,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge Connector Helper Functions
> + */
> +
> +#define MAX_EDID  512
> +
> +static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> +					       struct drm_bridge *bridge)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	enum drm_connector_status status;
> +	struct edid *edid;
> +	int n;
> +
> +	status = drm_bridge_connector_detect(connector, false);
> +	if (status != connector_status_connected)
> +		goto no_edid;
> +
> +	edid = bridge->funcs->get_edid(bridge, connector);
> +	if (!edid || !drm_edid_is_valid(edid)) {
> +		kfree(edid);
> +		goto no_edid;
> +	}
> +
> +	drm_connector_update_edid_property(connector, edid);
> +	n = drm_add_edid_modes(connector, edid);
> +
> +	bridge_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
> +
> +	kfree(edid);
> +	return n;
> +
> +no_edid:
> +	drm_connector_update_edid_property(connector, NULL);
> +	return 0;
> +}
> +
> +static int drm_bridge_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_bridge_connector *bridge_connector =
> +		to_drm_bridge_connector(connector);
> +	struct drm_bridge *bridge;
> +
> +	/*
> +	 * If display exposes EDID, then we parse that in the normal way to
> +	 * build table of supported modes.
> +	 */
> +	bridge = bridge_connector->bridge_edid;
> +	if (bridge)
> +		return drm_bridge_connector_get_modes_edid(connector, bridge);
> +
> +	/*
> +	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
> +	 * resolution panel or an analog TV output), query it.
> +	 */
> +	bridge = bridge_connector->bridge_modes;
> +	if (bridge)
> +		return bridge->funcs->get_modes(bridge, connector);
> +
> +	/*
> +	 * We can't retrieve modes, which can happen for instance for a DVI or
> +	 * VGA output with the DDC bus unconnected. The KMS core will add the
> +	 * default modes.
> +	 */
> +	return 0;
> +}
> +
> +static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
> +	.get_modes = drm_bridge_connector_get_modes,
> +	/* No need for .mode_valid(), the bridges are checked by the core. */
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge Connector Initialisation
> + */
> +
> +/**
> + * drm_bridge_connector_init - Initialise a connector for a chain of bridges
> + * @drm: the DRM device
> + * @bridge: the bridge closest to the CRTC output
> + *
> + * Allocate, initialise and register a &drm_bridge_connector with the @drm
> + * device. The connector is associated with a chain of bridges that starts at
> + * the CRTC output with @bridge. All bridges in the chain shall report bridge
> + * operation flags (&drm_bridge->ops) and bridge output type
> + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> + *
> + * Returns a pointer to the new connector on success, or a negative error
> + * pointer otherwise.
> + */
> +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> +						struct drm_bridge *bridge)
> +{
> +	struct drm_bridge_connector *bridge_connector;
> +	struct drm_connector *connector;
> +	int connector_type;
> +
> +	bridge_connector = kzalloc(sizeof(*bridge_connector), GFP_KERNEL);
> +	if (!bridge_connector)
> +		return ERR_PTR(-ENOMEM);
> +
> +	bridge_connector->bridge = bridge;
> +
> +	/*
> +	 * Initialise connector status handling. First locate the furthest
> +	 * bridges in the pipeline that support HPD and output detection. Then
> +	 * initialise the connector polling mode, using HPD if available and
> +	 * falling back to polling if supported. If neither HPD nor output
> +	 * detection are available, we don't support hotplug detection at all.
> +	 */
> +	connector_type = DRM_MODE_CONNECTOR_Unknown;
> +	for ( ; bridge; bridge = bridge->next) {
> +		if (bridge->ops & DRM_BRIDGE_OP_EDID)
> +			bridge_connector->bridge_edid = bridge;
> +		if (bridge->ops & DRM_BRIDGE_OP_HPD)
> +			bridge_connector->bridge_hpd = bridge;
> +		if (bridge->ops & DRM_BRIDGE_OP_DETECT)
> +			bridge_connector->bridge_detect = bridge;
> +		if (bridge->ops & DRM_BRIDGE_OP_MODES)
> +			bridge_connector->bridge_modes = bridge;
> +
> +		if (!bridge->next)
> +			connector_type = bridge->type;
> +	}
> +
> +	if (connector_type == DRM_MODE_CONNECTOR_Unknown) {
> +		kfree(bridge_connector);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	/*
> +	 * TODO: Handle interlace_allowed, doublescan_allowed, stereo_allowed
> +	 * and ycbcr_420_allowed.
> +	 */
> +	connector = &bridge_connector->base;
> +	drm_connector_init(drm, connector, &drm_bridge_connector_funcs,
> +			   connector_type);
> +	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
> +
> +	if (bridge_connector->bridge_hpd)
> +		connector->polled = DRM_CONNECTOR_POLL_HPD;
> +	else if (bridge_connector->bridge_detect)
> +		connector->polled = DRM_CONNECTOR_POLL_CONNECT
> +				  | DRM_CONNECTOR_POLL_DISCONNECT;
> +
> +	return connector;
> +}
> +EXPORT_SYMBOL_GPL(drm_bridge_connector_init);
> diff --git a/include/drm/drm_bridge_connector.h b/include/drm/drm_bridge_connector.h
> new file mode 100644
> index 000000000000..ec33b44954b8
> --- /dev/null
> +++ b/include/drm/drm_bridge_connector.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> + */
> +
> +#ifndef __DRM_BRIDGE_CONNECTOR_H__
> +#define __DRM_BRIDGE_CONNECTOR_H__
> +
> +struct drm_bridge;
> +struct drm_connector;
> +struct drm_device;
> +
> +void drm_bridge_connector_enable_hpd(struct drm_connector *connector);
> +void drm_bridge_connector_disable_hpd(struct drm_connector *connector);
> +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> +						struct drm_bridge *bridge);
> +
> +#endif /* __DRM_BRIDGE_CONNECTOR_H__ */
> -- 
> Regards,
> 
> Laurent Pinchart
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display
  2019-07-07 18:18   ` [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display Laurent Pinchart
@ 2019-07-24 16:22     ` Rob Herring
  0 siblings, 0 replies; 166+ messages in thread
From: Rob Herring @ 2019-07-24 16:22 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Mark Rutland, devicetree, Maxime Ripard, Sebastian Reichel,
	dri-devel, Tomi Valkeinen, Sean Paul

On Sun,  7 Jul 2019 21:18:53 +0300, Laurent Pinchart wrote:
> LG Display is an LCD display manufacturer. Originally formed as a joint
> venture by LG Electronics and Philips Electronics, it was formerly known
> as LG.Philips LCD, hence the DT vendor prefix lgphilips (which is
> already in active use in the kernel).
> 
> More information is available at
> https://en.wikipedia.org/wiki/LG_Display.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
>  1 file changed, 2 insertions(+)
> 

Reviewed-by: Rob Herring <robh@kernel.org>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO
  2019-07-07 18:18   ` [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO Laurent Pinchart
  2019-07-09 14:32     ` Andrzej Hajda
@ 2019-07-26 13:19     ` Stefan Agner
  1 sibling, 0 replies; 166+ messages in thread
From: Stefan Agner @ 2019-07-26 13:19 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

On 2019-07-07 20:18, Laurent Pinchart wrote:
> If an enable GPIO is declared in the firmware, assert it when enabling
> the bridge and deassert it when disabling it.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

This also comes in handy for the ADV7123, which has a power save pin.

Looks good to me.

Reviewed-by: Stefan Agner <stefan@agner.ch>

--
Stefan

> ---
>  drivers/gpu/drm/bridge/simple-bridge.c | 22 ++++++++++++++++++----
>  1 file changed, 18 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c
> b/drivers/gpu/drm/bridge/simple-bridge.c
> index bff240cf283d..a7edf3c39627 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -6,6 +6,7 @@
>   * Maxime Ripard <maxime.ripard@free-electrons.com>
>   */
>  
> +#include <linux/gpio/consumer.h>
>  #include <linux/module.h>
>  #include <linux/of_device.h>
>  #include <linux/of_graph.h>
> @@ -29,6 +30,7 @@ struct simple_bridge {
>  
>  	struct i2c_adapter	*ddc;
>  	struct regulator	*vdd;
> +	struct gpio_desc	*enable;
>  };
>  
>  static inline struct simple_bridge *
> @@ -135,19 +137,23 @@ static int simple_bridge_attach(struct drm_bridge *bridge)
>  static void simple_bridge_enable(struct drm_bridge *bridge)
>  {
>  	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
> -	int ret = 0;
> +	int ret;
>  
> -	if (sbridge->vdd)
> +	if (sbridge->vdd) {
>  		ret = regulator_enable(sbridge->vdd);
> +		if (ret)
> +			DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
> +	}
>  
> -	if (ret)
> -		DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
> +	gpiod_set_value_cansleep(sbridge->enable, 1);
>  }
>  
>  static void simple_bridge_disable(struct drm_bridge *bridge)
>  {
>  	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
>  
> +	gpiod_set_value_cansleep(sbridge->enable, 0);
> +
>  	if (sbridge->vdd)
>  		regulator_disable(sbridge->vdd);
>  }
> @@ -200,6 +206,14 @@ static int simple_bridge_probe(struct
> platform_device *pdev)
>  		dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
>  	}
>  
> +	sbridge->enable = devm_gpiod_get_optional(&pdev->dev, "enable",
> +						  GPIOD_OUT_LOW);
> +	if (IS_ERR(sbridge->enable)) {
> +		if (PTR_ERR(sbridge->enable) != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "Unable to retrieve enable GPIO\n");
> +		return PTR_ERR(sbridge->enable);
> +	}
> +
>  	sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
>  	if (IS_ERR(sbridge->ddc)) {
>  		if (PTR_ERR(sbridge->ddc) == -ENODEV) {
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges
  2019-07-07 18:18   ` [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges Laurent Pinchart
  2019-07-09 14:08     ` Andrzej Hajda
@ 2019-07-26 13:24     ` Stefan Agner
  1 sibling, 0 replies; 166+ messages in thread
From: Stefan Agner @ 2019-07-26 13:24 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

On 2019-07-07 20:18, Laurent Pinchart wrote:
> Create a new simple_bridge_info structure that stores information about
> the bridge model, and store the bridge timings in there, along with the
> connector type. Use that new structure for of_device_id data. This
> enables support for non-VGA bridges.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/bridge/simple-bridge.c | 41 ++++++++++++++++++--------
>  1 file changed, 29 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c
> b/drivers/gpu/drm/bridge/simple-bridge.c
> index da5479bd5878..bff240cf283d 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -16,10 +16,17 @@
>  #include <drm/drm_print.h>
>  #include <drm/drm_probe_helper.h>
>  
> +struct simple_bridge_info {
> +	const struct drm_bridge_timings *timings;
> +	unsigned int type;

How about connector_type? That is how this field is named in other
places.

Otherwise looks good to me.

Reviewed-by: Stefan Agner <stefan@agner.ch>

--
Stefan

> +};
> +
>  struct simple_bridge {
>  	struct drm_bridge	bridge;
>  	struct drm_connector	connector;
>  
> +	const struct simple_bridge_info *info;
> +
>  	struct i2c_adapter	*ddc;
>  	struct regulator	*vdd;
>  };
> @@ -113,7 +120,7 @@ static int simple_bridge_attach(struct drm_bridge *bridge)
>  				 &simple_bridge_con_helper_funcs);
>  	ret = drm_connector_init(bridge->dev, &sbridge->connector,
>  				 &simple_bridge_con_funcs,
> -				 DRM_MODE_CONNECTOR_VGA);
> +				 sbridge->info->type);
>  	if (ret) {
>  		DRM_ERROR("Failed to initialize connector\n");
>  		return ret;
> @@ -182,6 +189,8 @@ static int simple_bridge_probe(struct platform_device *pdev)
>  		return -ENOMEM;
>  	platform_set_drvdata(pdev, sbridge);
>  
> +	sbridge->info = of_device_get_match_data(&pdev->dev);
> +
>  	sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
>  	if (IS_ERR(sbridge->vdd)) {
>  		int ret = PTR_ERR(sbridge->vdd);
> @@ -204,7 +213,7 @@ static int simple_bridge_probe(struct platform_device *pdev)
>  
>  	sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
>  	sbridge->bridge.of_node = pdev->dev.of_node;
> -	sbridge->bridge.timings = of_device_get_match_data(&pdev->dev);
> +	sbridge->bridge.timings = sbridge->info->timings;
>  
>  	drm_bridge_add(&sbridge->bridge);
>  
> @@ -264,19 +273,27 @@ static const struct drm_bridge_timings
> ti_ths8135_bridge_timings = {
>  static const struct of_device_id simple_bridge_match[] = {
>  	{
>  		.compatible = "dumb-vga-dac",
> -		.data = NULL,
> -	},
> -	{
> +		.data = &(const struct simple_bridge_info) {
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
> +	}, {
>  		.compatible = "adi,adv7123",
> -		.data = &default_bridge_timings,
> -	},
> -	{
> +		.data = &(const struct simple_bridge_info) {
> +			.timings = &default_bridge_timings,
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
> +	}, {
>  		.compatible = "ti,ths8135",
> -		.data = &ti_ths8135_bridge_timings,
> -	},
> -	{
> +		.data = &(const struct simple_bridge_info) {
> +			.timings = &ti_ths8135_bridge_timings,
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
> +	}, {
>  		.compatible = "ti,ths8134",
> -		.data = &ti_ths8134_bridge_timings,
> +		.data = &(const struct simple_bridge_info) {
> +			.timings = &ti_ths8134_bridge_timings,
> +			.type = DRM_MODE_CONNECTOR_VGA,
> +		},
>  	},
>  	{},
>  };
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-07-17  6:39     ` Andrzej Hajda
@ 2019-08-08 14:25       ` Laurent Pinchart
  2019-08-08 17:36         ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 14:25 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Wed, Jul 17, 2019 at 08:39:47AM +0200, Andrzej Hajda wrote:
> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > Most bridge drivers create a DRM connector to model the connector at the
> > output of the bridge. This model is historical and has worked pretty
> > well so far, but causes several issues:
> >
> > - It prevents supporting more complex display pipelines where DRM
> > connector operations are split over multiple components. For instance a
> > pipeline with a bridge connected to the DDC signals to read EDID data,
> > and another one connected to the HPD signal to detect connection and
> > disconnection, will not be possible to support through this model.
> >
> > - It requires every bridge driver to implement similar connector
> > handling code, resulting in code duplication.
> >
> > - It assumes that a bridge will either be wired to a connector or to
> > another bridge, but doesn't support bridges that can be used in both
> > positions very well (although there is some ad-hoc support for this in
> > the analogix_dp bridge driver).
> >
> > In order to solve these issues, ownership of the connector should be
> > moved to the display controller driver (where it can be implemented
> > using helpers provided by the core).
> >
> > Extend the bridge API to allow disabling connector creation in bridge
> > drivers as a first step towards the new model. The new create_connector
> > argument to the bridge .attach() operation tells the bridge driver
> > whether to create a connector. Set the argument to true unconditionally,
> > and modify all existing bridge drivers to return an error when connector
> > creation is not requested as they don't support this feature yet.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
> >  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
> >  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
> >  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
> >  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
> >  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
> >  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
> >  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
> >  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
> >  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
> >  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
> >  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
> >  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
> >  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
> >  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
> >  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
> >  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
> >  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
> >  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
> >  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
> >  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
> >  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
> >  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
> >  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
> >  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
> >  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
> >  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
> >  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
> >  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
> >  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
> >  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
> >  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
> >  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
> >  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
> >  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
> >  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
> >  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
> >  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
> >  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
> >  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
> >  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
> >  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
> >  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
> >  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
> >  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
> >  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
> >  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
> >  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
> >  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
> >  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
> >  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
> >  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
> >  include/drm/drm_bridge.h                                 | 4 ++--
> >  53 files changed, 140 insertions(+), 67 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > index 98aac743cc26..739f2358f1d5 100644
> > --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
> >  		return ret;
> >  
> >  	/* Link drm_bridge to encoder */
> > -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> 
> Few suggestions:
> 
> 1. Maybe it would be more convenient to add flags argument instead of bool:
> 
> - code should be more readable: ret = drm_bridge_attach(encoder, bridge,
> NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)
> 
> - it can be easily expanded later with other flags, there at least two
> drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.

Please note that I think this flag should disappear once drivers get
converted to the new model. This will however take some time. I'm not
opposed to turning the book into a flag though. I was hoping to receive
more review comments on this particular patch, but as that's not the
case, I can already proceed with the change.

What would the DRM_BRIDGE_FLAG_NO_CHAINING flag be used for ?

> 2. If the patch can be applied atomically it is OK as is, if not you can
> use preprocessor vararg magic to support new and old syntax, sth like:
> 
> #define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
> __drm_bridge_attach(encoder, bridge, prev, flags)
> 
> #define drm_bridge_attach(encoder, bridge, prev, optarg...)
> _drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)

Good point. I'll try to do this atomically, but if it fails I'll follow
your suggestion.

> 3. Maybe more convenient would be to just set the flags directly before
> attachment:
> 
>     bridge->dont_create_connector = true;
> 
>     ret = drm_bridge_attach(encoder, bridge, NULL);
> 
>     This way it will be still expandable, and less changes.

Bridges that are chained would need to set the dont_create_connector
flag of the next bridge. It would be a bit ugly, but would make this
patch smaller. On the other hand we would need to keep the if
(!create_connector) check in the .attach() handlers, and it would be
easier to miss it in bridge drivers (current or new) than with an
explicit argument to the .attach() operation. I would thus have a
preference for the new argument to .attach(). Especially if it can help
you with DRM_BRIDGE_FLAG_NO_CHAINING :-)

> >  	if (ret)
> >  		drm_encoder_cleanup(encoder);
> >  
> > diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> > index f73d8a92274e..606841d2c0b0 100644
> > --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> > +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
> > @@ -123,7 +123,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
> >  	}
> >  
> >  	if (bridge) {
> > -		ret = drm_bridge_attach(&output->encoder, bridge, NULL);
> > +		ret = drm_bridge_attach(&output->encoder, bridge, NULL, true);
> >  		if (!ret)
> >  			return 0;
> >  
> > diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> > index f6d2681f6927..c67ba30edec4 100644
> > --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> > +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> > @@ -847,11 +847,15 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
> >  	adv7511_mode_set(adv, mode, adj_mode);
> >  }
> >  
> > -static int adv7511_bridge_attach(struct drm_bridge *bridge)
> > +static int adv7511_bridge_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct adv7511 *adv = bridge_to_adv7511(bridge);
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Parent encoder object not found");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix-anx78xx.c
> > index 3c7cc5af735c..f72755e59e12 100644
> > --- a/drivers/gpu/drm/bridge/analogix-anx78xx.c
> > +++ b/drivers/gpu/drm/bridge/analogix-anx78xx.c
> > @@ -998,11 +998,15 @@ static const struct drm_connector_funcs anx78xx_connector_funcs = {
> >  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int anx78xx_bridge_attach(struct drm_bridge *bridge)
> > +static int anx78xx_bridge_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
> >  	int err;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Parent encoder object not found");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> > index 3f7f4880be09..f6a1bdcc09d6 100644
> > --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> > +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> > @@ -1179,13 +1179,17 @@ static const struct drm_connector_funcs analogix_dp_connector_funcs = {
> >  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int analogix_dp_bridge_attach(struct drm_bridge *bridge)
> > +static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
> > +				     bool create_connector)
> >  {
> >  	struct analogix_dp_device *dp = bridge->driver_private;
> >  	struct drm_encoder *encoder = dp->encoder;
> >  	struct drm_connector *connector = NULL;
> >  	int ret = 0;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Parent encoder object not found");
> >  		return -ENODEV;
> > @@ -1463,7 +1467,7 @@ static int analogix_dp_create_bridge(struct drm_device *drm_dev,
> >  	bridge->driver_private = dp;
> >  	bridge->funcs = &analogix_dp_bridge_funcs;
> >  
> > -	ret = drm_bridge_attach(dp->encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(dp->encoder, bridge, NULL, true);
> >  	if (ret) {
> >  		DRM_ERROR("failed to attach drm bridge\n");
> >  		return -EINVAL;
> > diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c
> > index 6166dca6be81..45f50852cfbb 100644
> > --- a/drivers/gpu/drm/bridge/cdns-dsi.c
> > +++ b/drivers/gpu/drm/bridge/cdns-dsi.c
> > @@ -645,7 +645,8 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
> >  	return 0;
> >  }
> >  
> > -static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
> > +static int cdns_dsi_bridge_attach(struct drm_bridge *bridge,
> > +				  bool create_connector)
> >  {
> >  	struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
> >  	struct cdns_dsi *dsi = input_to_dsi(input);
> > @@ -657,7 +658,8 @@ static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
> >  		return -ENOTSUPP;
> >  	}
> >  
> > -	return drm_bridge_attach(bridge->encoder, output->bridge, bridge);
> > +	return drm_bridge_attach(bridge->encoder, output->bridge, bridge,
> > +				 create_connector);
> >  }
> >  
> >  static enum drm_mode_status
> > diff --git a/drivers/gpu/drm/bridge/lvds-encoder.c b/drivers/gpu/drm/bridge/lvds-encoder.c
> > index 2ab2c234f26c..bafab97521af 100644
> > --- a/drivers/gpu/drm/bridge/lvds-encoder.c
> > +++ b/drivers/gpu/drm/bridge/lvds-encoder.c
> > @@ -18,14 +18,14 @@ struct lvds_encoder {
> >  	struct gpio_desc *powerdown_gpio;
> >  };
> >  
> > -static int lvds_encoder_attach(struct drm_bridge *bridge)
> > +static int lvds_encoder_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct lvds_encoder *lvds_encoder = container_of(bridge,
> >  							 struct lvds_encoder,
> >  							 bridge);
> >  
> >  	return drm_bridge_attach(bridge->encoder, lvds_encoder->panel_bridge,
> > -				 bridge);
> > +				 bridge, create_connector);
> >  }
> >  
> >  static void lvds_encoder_enable(struct drm_bridge *bridge)
> > diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
> > index 79311f8354bd..4250e2235f50 100644
> > --- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
> > +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
> > @@ -206,13 +206,17 @@ static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)
> >  	return IRQ_HANDLED;
> >  }
> >  
> > -static int ge_b850v3_lvds_attach(struct drm_bridge *bridge)
> > +static int ge_b850v3_lvds_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector;
> >  	struct i2c_client *stdp4028_i2c
> >  			= ge_b850v3_lvds_ptr->stdp4028_i2c;
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Parent encoder object not found");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c
> > index 98bc650b8c95..6bef439261da 100644
> > --- a/drivers/gpu/drm/bridge/nxp-ptn3460.c
> > +++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c
> > @@ -238,11 +238,15 @@ static const struct drm_connector_funcs ptn3460_connector_funcs = {
> >  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int ptn3460_bridge_attach(struct drm_bridge *bridge)
> > +static int ptn3460_bridge_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge);
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Parent encoder object not found");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
> > index b12ae3a4c5f1..98ad4abf2409 100644
> > --- a/drivers/gpu/drm/bridge/panel.c
> > +++ b/drivers/gpu/drm/bridge/panel.c
> > @@ -52,12 +52,15 @@ static const struct drm_connector_funcs panel_bridge_connector_funcs = {
> >  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int panel_bridge_attach(struct drm_bridge *bridge)
> > +static int panel_bridge_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
> >  	struct drm_connector *connector = &panel_bridge->connector;
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Missing encoder\n");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c
> > index 2d88146e4836..b9243d51489b 100644
> > --- a/drivers/gpu/drm/bridge/parade-ps8622.c
> > +++ b/drivers/gpu/drm/bridge/parade-ps8622.c
> > @@ -476,11 +476,14 @@ static const struct drm_connector_funcs ps8622_connector_funcs = {
> >  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int ps8622_attach(struct drm_bridge *bridge)
> > +static int ps8622_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Parent encoder object not found");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
> > index dd7aa466b280..18904b1082b6 100644
> > --- a/drivers/gpu/drm/bridge/sii902x.c
> > +++ b/drivers/gpu/drm/bridge/sii902x.c
> > @@ -396,12 +396,16 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
> >  	mutex_unlock(&sii902x->mutex);
> >  }
> >  
> > -static int sii902x_bridge_attach(struct drm_bridge *bridge)
> > +static int sii902x_bridge_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct sii902x *sii902x = bridge_to_sii902x(bridge);
> >  	struct drm_device *drm = bridge->dev;
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	drm_connector_helper_add(&sii902x->connector,
> >  				 &sii902x_connector_helper_funcs);
> >  
> > diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
> > index 0cc293a6ac24..ea6529df7d9c 100644
> > --- a/drivers/gpu/drm/bridge/sil-sii8620.c
> > +++ b/drivers/gpu/drm/bridge/sil-sii8620.c
> > @@ -2203,7 +2203,7 @@ static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
> >  	return container_of(bridge, struct sii8620, bridge);
> >  }
> >  
> > -static int sii8620_attach(struct drm_bridge *bridge)
> > +static int sii8620_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct sii8620 *ctx = bridge_to_sii8620(bridge);
> >  
> > diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
> > index 7495b9bef865..86885eb6e28d 100644
> > --- a/drivers/gpu/drm/bridge/simple-bridge.c
> > +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> > @@ -108,11 +108,15 @@ static const struct drm_connector_funcs simple_bridge_con_funcs = {
> >  	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int simple_bridge_attach(struct drm_bridge *bridge)
> > +static int simple_bridge_attach(struct drm_bridge *bridge,
> > +				bool create_connector)
> >  {
> >  	struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return 0;
> > +
> >  	if (!bridge->encoder) {
> >  		DRM_ERROR("Missing encoder\n");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> > index c6490949d9db..930d67c618dd 100644
> > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> > @@ -2174,12 +2174,16 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
> >  	.get_modes = dw_hdmi_connector_get_modes,
> >  };
> >  
> > -static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
> > +static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct dw_hdmi *hdmi = bridge->driver_private;
> >  	struct drm_encoder *encoder = bridge->encoder;
> >  	struct drm_connector *connector = &hdmi->connector;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	connector->interlace_allowed = 1;
> >  	connector->polled = DRM_CONNECTOR_POLL_HPD;
> >  
> > @@ -2857,7 +2861,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
> >  	if (IS_ERR(hdmi))
> >  		return hdmi;
> >  
> > -	ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, true);
> >  	if (ret) {
> >  		dw_hdmi_remove(hdmi);
> >  		DRM_ERROR("Failed to initialize bridge with drm\n");
> > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
> > index 281c58bab1a1..05cf97ad524f 100644
> > --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
> > +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
> > @@ -906,7 +906,8 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
> >  	return mode_status;
> >  }
> >  
> > -static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
> > +static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
> > +				     bool create_connector)
> >  {
> >  	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
> >  
> > @@ -919,7 +920,8 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
> >  	bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
> >  
> >  	/* Attach the panel-bridge to the dsi bridge */
> > -	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
> > +	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
> > +				 create_connector);
> >  }
> >  
> >  static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
> > @@ -1064,7 +1066,7 @@ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
> >  {
> >  	int ret;
> >  
> > -	ret = drm_bridge_attach(encoder, &dsi->bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, true);
> >  	if (ret) {
> >  		DRM_ERROR("Failed to initialize bridge with drm\n");
> >  		return ret;
> > diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
> > index 170f162ffa55..6016f3aae42f 100644
> > --- a/drivers/gpu/drm/bridge/tc358764.c
> > +++ b/drivers/gpu/drm/bridge/tc358764.c
> > @@ -348,12 +348,15 @@ static void tc358764_enable(struct drm_bridge *bridge)
> >  		dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
> >  }
> >  
> > -static int tc358764_attach(struct drm_bridge *bridge)
> > +static int tc358764_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct tc358764 *ctx = bridge_to_tc358764(bridge);
> >  	struct drm_device *drm = bridge->dev;
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
> >  	ret = drm_connector_init(drm, &ctx->connector,
> >  				 &tc358764_connector_funcs,
> > diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
> > index 13ade28a36a8..e2b2d2660adc 100644
> > --- a/drivers/gpu/drm/bridge/tc358767.c
> > +++ b/drivers/gpu/drm/bridge/tc358767.c
> > @@ -1273,13 +1273,16 @@ static const struct drm_connector_funcs tc_connector_funcs = {
> >  	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int tc_bridge_attach(struct drm_bridge *bridge)
> > +static int tc_bridge_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
> >  	struct tc_data *tc = bridge_to_tc(bridge);
> >  	struct drm_device *drm = bridge->dev;
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	/* Create DP/eDP connector */
> >  	drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
> >  	ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,
> > diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c
> > index 3d74129b2995..86f3b96f95db 100644
> > --- a/drivers/gpu/drm/bridge/thc63lvd1024.c
> > +++ b/drivers/gpu/drm/bridge/thc63lvd1024.c
> > @@ -42,11 +42,12 @@ static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
> >  	return container_of(bridge, struct thc63_dev, bridge);
> >  }
> >  
> > -static int thc63_attach(struct drm_bridge *bridge)
> > +static int thc63_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct thc63_dev *thc63 = to_thc63(bridge);
> >  
> > -	return drm_bridge_attach(bridge->encoder, thc63->next, bridge);
> > +	return drm_bridge_attach(bridge->encoder, thc63->next, bridge,
> > +				 create_connector);
> >  }
> >  
> >  static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
> > diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> > index b77a52d05061..dbe265f7112d 100644
> > --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> > +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> > @@ -224,7 +224,7 @@ static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
> >  				       pdata->supplies);
> >  }
> >  
> > -static int ti_sn_bridge_attach(struct drm_bridge *bridge)
> > +static int ti_sn_bridge_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	int ret, val;
> >  	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> > @@ -235,6 +235,9 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
> >  						   .node = NULL,
> >  						 };
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	ret = drm_connector_init(bridge->dev, &pdata->connector,
> >  				 &ti_sn_bridge_connector_funcs,
> >  				 DRM_MODE_CONNECTOR_eDP);
> > diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
> > index 4e76b2b27374..8d4690e436c3 100644
> > --- a/drivers/gpu/drm/bridge/ti-tfp410.c
> > +++ b/drivers/gpu/drm/bridge/ti-tfp410.c
> > @@ -121,11 +121,14 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
> >  	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
> >  };
> >  
> > -static int tfp410_attach(struct drm_bridge *bridge)
> > +static int tfp410_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	if (!bridge->encoder) {
> >  		dev_err(dvi->dev, "Missing encoder\n");
> >  		return -ENODEV;
> > diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > index cba537c99e43..519577f363e3 100644
> > --- a/drivers/gpu/drm/drm_bridge.c
> > +++ b/drivers/gpu/drm/drm_bridge.c
> > @@ -95,6 +95,7 @@ EXPORT_SYMBOL(drm_bridge_remove);
> >   * @encoder: DRM encoder
> >   * @bridge: bridge to attach
> >   * @previous: previous bridge in the chain (optional)
> > + * @create_connector: true if the bridge should create a drm_connector
> >   *
> >   * Called by a kms driver to link the bridge to an encoder's chain. The previous
> >   * argument specifies the previous bridge in the chain. If NULL, the bridge is
> > @@ -112,7 +113,7 @@ EXPORT_SYMBOL(drm_bridge_remove);
> >   * Zero on success, error code on failure
> >   */
> >  int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
> > -		      struct drm_bridge *previous)
> > +		      struct drm_bridge *previous, bool create_connector)
> >  {
> >  	int ret;
> >  
> > @@ -129,7 +130,7 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
> >  	bridge->encoder = encoder;
> >  
> >  	if (bridge->funcs->attach) {
> > -		ret = bridge->funcs->attach(bridge);
> > +		ret = bridge->funcs->attach(bridge, create_connector);
> >  		if (ret < 0) {
> >  			bridge->dev = NULL;
> >  			bridge->encoder = NULL;
> > diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c
> > index b11910f14c46..a367ef1e5081 100644
> > --- a/drivers/gpu/drm/drm_simple_kms_helper.c
> > +++ b/drivers/gpu/drm/drm_simple_kms_helper.c
> > @@ -228,7 +228,7 @@ static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
> >  int drm_simple_display_pipe_attach_bridge(struct drm_simple_display_pipe *pipe,
> >  					  struct drm_bridge *bridge)
> >  {
> > -	return drm_bridge_attach(&pipe->encoder, bridge, NULL);
> > +	return drm_bridge_attach(&pipe->encoder, bridge, NULL, true);
> >  }
> >  EXPORT_SYMBOL(drm_simple_display_pipe_attach_bridge);
> >  
> > diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
> > index 3a0f0ba8c63a..02b98e6ca52d 100644
> > --- a/drivers/gpu/drm/exynos/exynos_dp.c
> > +++ b/drivers/gpu/drm/exynos/exynos_dp.c
> > @@ -105,7 +105,8 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
> >  
> >  	/* Pre-empt DP connector creation if there's a bridge */
> >  	if (dp->ptn_bridge) {
> > -		ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge);
> > +		ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge,
> > +					true);
> >  		if (ret) {
> >  			DRM_DEV_ERROR(dp->dev,
> >  				      "Failed to attach bridge to drm\n");
> > diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
> > index 5f6f523821a2..768acc79d10c 100644
> > --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c
> > +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
> > @@ -1522,7 +1522,7 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
> >  
> >  	out_bridge  = of_drm_find_bridge(device->dev.of_node);
> >  	if (out_bridge) {
> > -		drm_bridge_attach(encoder, out_bridge, NULL);
> > +		drm_bridge_attach(encoder, out_bridge, NULL, true);
> >  		dsi->out_bridge = out_bridge;
> >  		encoder->bridge = NULL;
> >  	} else {
> > @@ -1698,7 +1698,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master,
> >  	if (dsi->in_bridge_node) {
> >  		in_bridge = of_drm_find_bridge(dsi->in_bridge_node);
> >  		if (in_bridge)
> > -			drm_bridge_attach(encoder, in_bridge, NULL);
> > +			drm_bridge_attach(encoder, in_bridge, NULL, true);
> >  	}
> >  
> >  	return mipi_dsi_host_register(&dsi->dsi_host);
> > diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
> > index bc1565f1822a..808c98101d56 100644
> > --- a/drivers/gpu/drm/exynos/exynos_hdmi.c
> > +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
> > @@ -952,7 +952,7 @@ static int hdmi_create_connector(struct drm_encoder *encoder)
> >  	drm_connector_attach_encoder(connector, encoder);
> >  
> >  	if (hdata->bridge) {
> > -		ret = drm_bridge_attach(encoder, hdata->bridge, NULL);
> > +		ret = drm_bridge_attach(encoder, hdata->bridge, NULL, true);
> >  		if (ret)
> >  			DRM_DEV_ERROR(hdata->dev, "Failed to attach bridge\n");
> >  	}
> > diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
> > index c49e9e3740f8..8d22618ccf2e 100644
> > --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
> > +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
> > @@ -159,5 +159,5 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
> >  		return fsl_dcu_attach_panel(fsl_dev, panel);
> >  	}
> >  
> > -	return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL);
> > +	return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, true);
> >  }
> > diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
> > index 3d6c45097f51..eac8ec1512ab 100644
> > --- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
> > +++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
> > @@ -780,7 +780,7 @@ static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
> >  	int ret;
> >  
> >  	/* associate the bridge to dsi encoder */
> > -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >  	if (ret) {
> >  		DRM_ERROR("failed to attach external bridge\n");
> >  		return ret;
> > diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
> > index 3d368c43185f..6b2e648b6c4d 100644
> > --- a/drivers/gpu/drm/i2c/tda998x_drv.c
> > +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
> > @@ -1366,10 +1366,14 @@ static int tda998x_connector_init(struct tda998x_priv *priv,
> >  
> >  /* DRM bridge functions */
> >  
> > -static int tda998x_bridge_attach(struct drm_bridge *bridge)
> > +static int tda998x_bridge_attach(struct drm_bridge *bridge,
> > +				 bool create_connector)
> >  {
> >  	struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge);
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	return tda998x_connector_init(priv, bridge->dev);
> >  }
> >  
> > @@ -2033,7 +2037,7 @@ static int tda998x_encoder_init(struct device *dev, struct drm_device *drm)
> >  	if (ret)
> >  		goto err_encoder;
> >  
> > -	ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL);
> > +	ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL, true);
> >  	if (ret)
> >  		goto err_bridge;
> >  
> > diff --git a/drivers/gpu/drm/imx/imx-ldb.c b/drivers/gpu/drm/imx/imx-ldb.c
> > index 383733302280..845ead25ade7 100644
> > --- a/drivers/gpu/drm/imx/imx-ldb.c
> > +++ b/drivers/gpu/drm/imx/imx-ldb.c
> > @@ -446,7 +446,7 @@ static int imx_ldb_register(struct drm_device *drm,
> >  
> >  	if (imx_ldb_ch->bridge) {
> >  		ret = drm_bridge_attach(&imx_ldb_ch->encoder,
> > -					imx_ldb_ch->bridge, NULL);
> > +					imx_ldb_ch->bridge, NULL, true);
> >  		if (ret) {
> >  			DRM_ERROR("Failed to initialize bridge with drm\n");
> >  			return ret;
> > diff --git a/drivers/gpu/drm/imx/parallel-display.c b/drivers/gpu/drm/imx/parallel-display.c
> > index 1a76de1e8e7b..cd746592d2a7 100644
> > --- a/drivers/gpu/drm/imx/parallel-display.c
> > +++ b/drivers/gpu/drm/imx/parallel-display.c
> > @@ -182,7 +182,7 @@ static int imx_pd_register(struct drm_device *drm,
> >  		drm_panel_attach(imxpd->panel, &imxpd->connector);
> >  
> >  	if (imxpd->bridge) {
> > -		ret = drm_bridge_attach(encoder, imxpd->bridge, NULL);
> > +		ret = drm_bridge_attach(encoder, imxpd->bridge, NULL, true);
> >  		if (ret < 0) {
> >  			dev_err(imxpd->dev, "failed to attach bridge: %d\n",
> >  				ret);
> > diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c
> > index 07f7090d08b3..f2deadc980f8 100644
> > --- a/drivers/gpu/drm/mcde/mcde_dsi.c
> > +++ b/drivers/gpu/drm/mcde/mcde_dsi.c
> > @@ -817,12 +817,16 @@ mcde_dsi_connector_helper_funcs = {
> >  	.get_modes = mcde_dsi_get_modes,
> >  };
> >  
> > -static int mcde_dsi_bridge_attach(struct drm_bridge *bridge)
> > +static int mcde_dsi_bridge_attach(struct drm_bridge *bridge,
> > +				  bool create_connector)
> >  {
> >  	struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
> >  	struct drm_device *drm = bridge->dev;
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	drm_connector_helper_add(&d->connector,
> >  				 &mcde_dsi_connector_helper_funcs);
> >  
> > diff --git a/drivers/gpu/drm/mediatek/mtk_dpi.c b/drivers/gpu/drm/mediatek/mtk_dpi.c
> > index bacd989cc9aa..1ff27bb17016 100644
> > --- a/drivers/gpu/drm/mediatek/mtk_dpi.c
> > +++ b/drivers/gpu/drm/mediatek/mtk_dpi.c
> > @@ -604,7 +604,7 @@ static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
> >  	/* Currently DPI0 is fixed to be driven by OVL1 */
> >  	dpi->encoder.possible_crtcs = BIT(1);
> >  
> > -	ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL);
> > +	ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL, true);
> >  	if (ret) {
> >  		dev_err(dev, "Failed to attach bridge: %d\n", ret);
> >  		goto err_cleanup;
> > diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
> > index b91c4616644a..9c5bac48b44f 100644
> > --- a/drivers/gpu/drm/mediatek/mtk_dsi.c
> > +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
> > @@ -819,7 +819,7 @@ static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
> >  
> >  	/* If there's a bridge, attach to it and let it create the connector */
> >  	if (dsi->bridge) {
> > -		ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL);
> > +		ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL, true);
> >  		if (ret) {
> >  			DRM_ERROR("Failed to attach bridge to drm\n");
> >  			goto err_encoder_cleanup;
> > diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi.c b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> > index 5d6a9f094df5..d3248a881cf0 100644
> > --- a/drivers/gpu/drm/mediatek/mtk_hdmi.c
> > +++ b/drivers/gpu/drm/mediatek/mtk_hdmi.c
> > @@ -1290,11 +1290,15 @@ static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
> >   * Bridge callbacks
> >   */
> >  
> > -static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
> > +static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge,
> > +				  bool create_connector)
> >  {
> >  	struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
> >  	int ret;
> >  
> > +	if (!create_connector)
> > +		return -EINVAL;
> > +
> >  	ret = drm_connector_init(bridge->encoder->dev, &hdmi->conn,
> >  				 &mtk_hdmi_connector_funcs,
> >  				 DRM_MODE_CONNECTOR_HDMIA);
> > @@ -1318,7 +1322,7 @@ static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
> >  
> >  	if (hdmi->next_bridge) {
> >  		ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
> > -					bridge);
> > +					bridge, create_connector);
> >  		if (ret) {
> >  			dev_err(hdmi->dev,
> >  				"Failed to attach external bridge: %d\n", ret);
> > diff --git a/drivers/gpu/drm/msm/dsi/dsi_manager.c b/drivers/gpu/drm/msm/dsi/dsi_manager.c
> > index 271aa7bbca92..ca733086041a 100644
> > --- a/drivers/gpu/drm/msm/dsi/dsi_manager.c
> > +++ b/drivers/gpu/drm/msm/dsi/dsi_manager.c
> > @@ -664,7 +664,7 @@ struct drm_bridge *msm_dsi_manager_bridge_init(u8 id)
> >  	bridge = &dsi_bridge->base;
> >  	bridge->funcs = &dsi_mgr_bridge_funcs;
> >  
> > -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >  	if (ret)
> >  		goto fail;
> >  
> > @@ -693,7 +693,7 @@ struct drm_connector *msm_dsi_manager_ext_bridge_init(u8 id)
> >  	encoder = msm_dsi->encoder;
> >  
> >  	/* link the internal dsi bridge to the external bridge */
> > -	drm_bridge_attach(encoder, ext_bridge, int_bridge);
> > +	drm_bridge_attach(encoder, ext_bridge, int_bridge, true);
> >  
> >  	/*
> >  	 * we need the drm_connector created by the external bridge
> > diff --git a/drivers/gpu/drm/msm/edp/edp_bridge.c b/drivers/gpu/drm/msm/edp/edp_bridge.c
> > index 2950bba4aca9..32a463c84cc1 100644
> > --- a/drivers/gpu/drm/msm/edp/edp_bridge.c
> > +++ b/drivers/gpu/drm/msm/edp/edp_bridge.c
> > @@ -91,7 +91,7 @@ struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp)
> >  	bridge = &edp_bridge->base;
> >  	bridge->funcs = &edp_bridge_funcs;
> >  
> > -	ret = drm_bridge_attach(edp->encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(edp->encoder, bridge, NULL, true);
> >  	if (ret)
> >  		goto fail;
> >  
> > diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
> > index 03197b8959ba..d7738aafcff8 100644
> > --- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
> > +++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
> > @@ -296,7 +296,7 @@ struct drm_bridge *msm_hdmi_bridge_init(struct hdmi *hdmi)
> >  	bridge = &hdmi_bridge->base;
> >  	bridge->funcs = &msm_hdmi_bridge_funcs;
> >  
> > -	ret = drm_bridge_attach(hdmi->encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(hdmi->encoder, bridge, NULL, true);
> >  	if (ret)
> >  		goto fail;
> >  
> > diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c
> > index 672e0f8ad11c..837d0cd20dd1 100644
> > --- a/drivers/gpu/drm/omapdrm/omap_drv.c
> > +++ b/drivers/gpu/drm/omapdrm/omap_drv.c
> > @@ -301,7 +301,8 @@ static int omap_modeset_init(struct drm_device *dev)
> >  
> >  		if (pipe->output->bridge) {
> >  			ret = drm_bridge_attach(pipe->encoder,
> > -						pipe->output->bridge, NULL);
> > +						pipe->output->bridge, NULL,
> > +						true);
> >  			if (ret < 0)
> >  				return ret;
> >  		}
> > diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> > index 0f00bdfe2366..74c2ae5ce687 100644
> > --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> > +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
> > @@ -120,7 +120,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
> >  	 * Attach the bridge to the encoder. The bridge will create the
> >  	 * connector.
> >  	 */
> > -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >  	if (ret) {
> >  		drm_encoder_cleanup(encoder);
> >  		return ret;
> > diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> > index 1c62578590f4..a8d8b05c4731 100644
> > --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
> > +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> > @@ -605,7 +605,7 @@ static void rcar_lvds_mode_set(struct drm_bridge *bridge,
> >  	rcar_lvds_get_lvds_mode(lvds);
> >  }
> >  
> > -static int rcar_lvds_attach(struct drm_bridge *bridge)
> > +static int rcar_lvds_attach(struct drm_bridge *bridge, bool create_connector)
> >  {
> >  	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
> >  	struct drm_connector *connector = &lvds->connector;
> > @@ -615,7 +615,10 @@ static int rcar_lvds_attach(struct drm_bridge *bridge)
> >  	/* If we have a next bridge just attach it. */
> >  	if (lvds->next_bridge)
> >  		return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
> > -					 bridge);
> > +					 bridge, create_connector);
> > +
> > +	if (!create_connector)
> > +		return -EINVAL;
> >  
> >  	/* Otherwise if we have a panel, create a connector. */
> >  	if (!lvds->panel)
> > diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
> > index 830858a809e5..7ca412d294b2 100644
> > --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
> > +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
> > @@ -440,7 +440,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
> >  			goto err_free_connector;
> >  		}
> >  	} else {
> > -		ret = drm_bridge_attach(encoder, lvds->bridge, NULL);
> > +		ret = drm_bridge_attach(encoder, lvds->bridge, NULL, true);
> >  		if (ret) {
> >  			DRM_DEV_ERROR(drm_dev->dev,
> >  				      "failed to attach bridge: %d\n", ret);
> > diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c
> > index ce4d82d293e4..8218bbd09a72 100644
> > --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c
> > +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c
> > @@ -143,7 +143,7 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
> >  
> >  	rgb->bridge = bridge;
> >  
> > -	ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, rgb->bridge, NULL, true);
> >  	if (ret) {
> >  		DRM_DEV_ERROR(drm_dev->dev,
> >  			      "failed to attach bridge: %d\n", ret);
> > diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c
> > index 9e6d5d8b7030..f09209621568 100644
> > --- a/drivers/gpu/drm/sti/sti_dvo.c
> > +++ b/drivers/gpu/drm/sti/sti_dvo.c
> > @@ -468,7 +468,7 @@ static int sti_dvo_bind(struct device *dev, struct device *master, void *data)
> >  	bridge->of_node = dvo->dev.of_node;
> >  	drm_bridge_add(bridge);
> >  
> > -	err = drm_bridge_attach(encoder, bridge, NULL);
> > +	err = drm_bridge_attach(encoder, bridge, NULL, true);
> >  	if (err) {
> >  		DRM_ERROR("Failed to attach bridge\n");
> >  		return err;
> > diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c
> > index 94e404f13234..87e0fb742dc8 100644
> > --- a/drivers/gpu/drm/sti/sti_hda.c
> > +++ b/drivers/gpu/drm/sti/sti_hda.c
> > @@ -700,7 +700,7 @@ static int sti_hda_bind(struct device *dev, struct device *master, void *data)
> >  
> >  	bridge->driver_private = hda;
> >  	bridge->funcs = &sti_hda_bridge_funcs;
> > -	drm_bridge_attach(encoder, bridge, NULL);
> > +	drm_bridge_attach(encoder, bridge, NULL, true);
> >  
> >  	connector->encoder = encoder;
> >  
> > diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
> > index f03d617edc4c..8c0ffe6833f9 100644
> > --- a/drivers/gpu/drm/sti/sti_hdmi.c
> > +++ b/drivers/gpu/drm/sti/sti_hdmi.c
> > @@ -1276,7 +1276,7 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
> >  
> >  	bridge->driver_private = hdmi;
> >  	bridge->funcs = &sti_hdmi_bridge_funcs;
> > -	drm_bridge_attach(encoder, bridge, NULL);
> > +	drm_bridge_attach(encoder, bridge, NULL, true);
> >  
> >  	connector->encoder = encoder;
> >  
> > diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c
> > index 2fe6c4a8d915..10a9f848c5f6 100644
> > --- a/drivers/gpu/drm/stm/ltdc.c
> > +++ b/drivers/gpu/drm/stm/ltdc.c
> > @@ -1053,7 +1053,7 @@ static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge)
> >  	drm_encoder_init(ddev, encoder, &ltdc_encoder_funcs,
> >  			 DRM_MODE_ENCODER_DPI, NULL);
> >  
> > -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >  	if (ret) {
> >  		drm_encoder_cleanup(encoder);
> >  		return -EINVAL;
> > diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c
> > index 3a3ba99fed22..3e5170fa1e67 100644
> > --- a/drivers/gpu/drm/sun4i/sun4i_lvds.c
> > +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c
> > @@ -155,7 +155,7 @@ int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon)
> >  	}
> >  
> >  	if (bridge) {
> > -		ret = drm_bridge_attach(encoder, bridge, NULL);
> > +		ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >  		if (ret) {
> >  			dev_err(drm->dev, "Couldn't attach our bridge\n");
> >  			goto err_cleanup_connector;
> > diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> > index a901ec689b62..3f8629445fbb 100644
> > --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
> > +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> > @@ -252,7 +252,7 @@ int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon)
> >  	}
> >  
> >  	if (rgb->bridge) {
> > -		ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
> > +		ret = drm_bridge_attach(encoder, rgb->bridge, NULL, true);
> >  		if (ret) {
> >  			dev_err(drm->dev, "Couldn't attach our bridge\n");
> >  			goto err_cleanup_connector;
> > diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c
> > index e9969cd36610..ec693c11e455 100644
> > --- a/drivers/gpu/drm/tilcdc/tilcdc_external.c
> > +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c
> > @@ -168,7 +168,7 @@ int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge)
> >  
> >  	priv->external_encoder->possible_crtcs = BIT(0);
> >  
> > -	ret = drm_bridge_attach(priv->external_encoder, bridge, NULL);
> > +	ret = drm_bridge_attach(priv->external_encoder, bridge, NULL, true);
> >  	if (ret) {
> >  		dev_err(ddev->dev, "drm_bridge_attach() failed %d\n", ret);
> >  		return ret;
> > diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c
> > index 34f90ca8f479..2d7c5cf0d468 100644
> > --- a/drivers/gpu/drm/vc4/vc4_dpi.c
> > +++ b/drivers/gpu/drm/vc4/vc4_dpi.c
> > @@ -262,7 +262,7 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
> >  	if (panel)
> >  		bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DPI);
> >  
> > -	return drm_bridge_attach(dpi->encoder, bridge, NULL);
> > +	return drm_bridge_attach(dpi->encoder, bridge, NULL, true);
> >  }
> >  
> >  static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
> > diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c
> > index 2ea4e20b7b8a..3edd7ffc7383 100644
> > --- a/drivers/gpu/drm/vc4/vc4_dsi.c
> > +++ b/drivers/gpu/drm/vc4/vc4_dsi.c
> > @@ -1607,7 +1607,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
> >  			 DRM_MODE_ENCODER_DSI, NULL);
> >  	drm_encoder_helper_add(dsi->encoder, &vc4_dsi_encoder_helper_funcs);
> >  
> > -	ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL);
> > +	ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL, true);
> >  	if (ret) {
> >  		dev_err(dev, "bridge attach failed: %d\n", ret);
> >  		return ret;
> > diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > index 7616f6562fe4..08dc15f93ded 100644
> > --- a/include/drm/drm_bridge.h
> > +++ b/include/drm/drm_bridge.h
> > @@ -48,7 +48,7 @@ struct drm_bridge_funcs {
> >  	 *
> >  	 * Zero on success, error code on failure.
> >  	 */
> > -	int (*attach)(struct drm_bridge *bridge);
> > +	int (*attach)(struct drm_bridge *bridge, bool create_connector);
> >  
> >  	/**
> >  	 * @detach:
> > @@ -404,7 +404,7 @@ void drm_bridge_add(struct drm_bridge *bridge);
> >  void drm_bridge_remove(struct drm_bridge *bridge);
> >  struct drm_bridge *of_drm_find_bridge(struct device_node *np);
> >  int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
> > -		      struct drm_bridge *previous);
> > +		      struct drm_bridge *previous, bool create_connector);
> >  
> >  bool drm_bridge_mode_fixup(struct drm_bridge *bridge,
> >  			   const struct drm_display_mode *mode,

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel
  2019-07-11 11:59   ` Sebastian Reichel
@ 2019-08-08 14:26     ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 14:26 UTC (permalink / raw)
  To: Sebastian Reichel; +Cc: Maxime Ripard, dri-devel, Tomi Valkeinen, Sean Paul

Hi Sebastian,

On Thu, Jul 11, 2019 at 01:59:23PM +0200, Sebastian Reichel wrote:
> On Thu, Jul 11, 2019 at 09:37:26AM +0200, Daniel Vetter wrote:
> > > [1] The only notable exception is the omapdrm-specific DSI panel driver
> > > that implements a large number of custom operations. This should be
> > > addressed separately.
> > 
> > DSI tends to be fairly custom in all drivers, I think that's totally fine.
> > Maybe not forever, but we have a lot worse crimes in our codebase than
> > that :-)
> 
> I have a WIP branch, which moves omapdrm DSI to mipi_dsi_driver and
> drm_panel:
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-n900.git/log/?h=omapdrm-5.2-with-dsi-untested-work-branch
> 
> The name is a bit misleading, since it is tested now. HEAD~2, which
> moves the last custom operation (panel update for DSI command mode)
> from the panel driver to the DSI core unfortunatley does not yet work.
> I'm still investigating the reason. Anyways - this is being worked
> on :)

Words fail me to express my gratitude for your work on this topic :-)

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel
  2019-07-11  7:37 ` [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Daniel Vetter
  2019-07-11 11:59   ` Sebastian Reichel
@ 2019-08-08 14:31   ` Laurent Pinchart
  1 sibling, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 14:31 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Thu, Jul 11, 2019 at 09:37:26AM +0200, Daniel Vetter wrote:
> On Sun, Jul 07, 2019 at 09:07:52PM +0300, Laurent Pinchart wrote:
> > Hello,
> > 
> > This patch series (nearly, see [1]) completes the rework of the omapdrm
> > driver to move to drm_bridge and drm_panel.
> > 
> > What a journey. This work was started more than a year ago, and this
> > last piece is perhaps the one that will generate the most bikeshedding
> > as it touches the DRM core. I'm braced for the impact, but please be
> > gentle :-)
> > 
> > Let's start with some context to understand the problem. omapdrm
> > contains custom drivers for external encoders, panels and connectors
> > (collectively referred to as display drivers). It combines them to
> > create output pipelines abstracted by a drm_encoder and a drm_connector,
> > with the ability to delegate the encoder and connector operations to the
> > component in the pipeline that implements them. For instance, for an
> > HDMI output pipeline, the hot plug detection and the EDID read can be
> > implemented by two different components, when they are handled by two
> > different devices at the hardware level.
> > 
> > DRM/KMS uses drm_bridge and drm_panel to abstract external encoders and
> > panels. The model is however simpler than what omapdrm provides, as
> > bridges were designed to be simple add-ons at the output of a
> > drm_encoder. The ability to chain bridges exists, but a bridge driver
> > hardcodes in its design its position in the pipeline : bridges that
> > expect to terminate the pipeline create a drm_connector, while bridges
> > that expect to be an intermediate component in the pipeline do not
> > create a connector. In addition to not supporting bridges that can be
> > either internal or a termination point in the pipeline depending on the
> > hardware design, implementing the drm_connector inside a bridge driver
> > makes it impossible to support hardware where bridge operations are
> > handled by different hardware components, as explained above.
> > 
> > The omapdrm driver has received support for drm_bridge and drm_panel,
> > but these issues prevented completely moving away from the omapdrm
> > custom display drivers. This patch series thus first reworks the
> > drm_bridge infrastructure to support the omapdrm use cases, and then
> > transitions the omapdrm driver.
> > 
> > The series starts by 01/60 that adds a new flag to the drm_display_info
> > structure to identify HDMI sinks. This is a feature needed by the OMAP4
> > and OMAP5 HDMI encoders, and I believe it can be useful to other HDMI
> > encoders as well. 02/60 is then a small drive-by cleanup.
> > 
> > The first sizeable change follows with the rename of the dumb-vga-dac
> > driver to simple-bridge (03/60 and 04/60) and support for non-VGA
> > bridges (05/60). This doesn't change the spirit of the driver that still
> > focusses on transparent bridges, but prepares it to support an analog
> > video amplifier. Patches 06/60 then add support for an enable GPIO, and
> > 07/60 support for the OPA362 video amplifier itself.
> > 
> > The next two patches address the drm_bridge issues explained above.
> > Patch 08/60 makes it possible to attach to a bridge without having the
> > bridge create a connector. The connector is expected to be created by
> > the display controller driver. Patch 09/60 adds connector-related
> > operations to drm_bridge to make this possible.
> > 
> > The approach taken here is slightly intrusive as path 08/60 adds a
> > parameter to tbe bridge .attach() operation, and thus touches all bridge
> > drivers, even if the changes are very simple (as a consequence I haven't
> > CC'ed all the individual bridge maintainers as the CC list was too
> > large). Other options may be possible, what matters most to me is the
> > feature, not so much its implementation. Please note that I envision the
> > parameter to be removed down the road once all bridge drivers will be
> > converted to the new model (but this will likely take time, and both
> > models can co-exist for as long as necessary).
> > 
> > The next six patches make use of these new features: patches 10/60 and
> > 11/60 add new bridge drivers for display connectors and for the TI
> > TPD12S015 HDMI level shifter respectively, patch 12/60 supports the new
> > API in the panel bridge driver, and patches 13/60 to 15/60 do the same
> > in the ti-tfp410 driver.
> > 
> > The nine patches that follow add support for six new panels, with the
> > related DT bindings (16/60 to 18/60) and the drm_panel drivers (19/60 to
> > 24/60). The code originates from the corresponding omapdrm-specific
> > panel drivers (which explains why only three DT patches are needed as
> > most of the bindings are already present).
> > 
> > Patch 25/60 is possibly the most remarkable one in the series, with the
> > drm_bridge operations extension, as it provides a helper for display
> > controller drivers to construct a drm_connector entirerly backed by a
> > chain of bridges. This offsets the complexity of the additional bridge
> > operations by handling it all in a single place. An example usage for
> > omapdrm can be found in patch 43/60. Don't let its diffstat mislead you,
> > usage of the helper would remove lots of code if it wasn't for the fact
> > that the legacy implementation still has to be kept for the DSI panel
> > (see [1]). Down the road this helper and the new operation paradigm
> > should remove code from both display controller and bridge drivers.
> > 
> > The rest of the series is omapdrm-focussed, slowly preparing the driver
> > for the switch to drm_bridge drivers using the new helper (43/60), the
> > removal of the omapdrm-specific display drivers (44/60 and 50/60), and
> > lots of simplification and code removal in the other patches.
> 
> git branch pls, thanks.

Sorry about that.

	git://linuxtv.org/pinchartl/media.git omapdrm/bridge/devel

(regularly updated with new versions)

> > [1] The only notable exception is the omapdrm-specific DSI panel driver
> > that implements a large number of custom operations. This should be
> > addressed separately.
> 
> DSI tends to be fairly custom in all drivers, I think that's totally fine.
> Maybe not forever, but we have a lot worse crimes in our codebase than
> that :-)
> 
> > Laurent Pinchart (60):
> >   drm/edid: Add flag to drm_display_info to identify HDMI sinks
> >   video: hdmi: Change return type of hdmi_avi_infoframe_init() to void
> >   drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge
> >   drm/bridge: dumb-vga-dac: Rename driver to simple-bridge
> >   drm/bridge: simple-bridge: Add support for non-VGA bridges
> >   drm/bridge: simple-bridge: Add support for enable GPIO
> >   drm/bridge: simple-bridge: Add support for the TI OP362
> >   drm/bridge: Extend bridge API to disable connector creation
> >   drm/bridge: Add connector-related bridge operations and data
> >   drm/bridge: Add bridge driver for display connectors
> >   drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter
> >   drm/bridge: panel: Implement bridge connector operations
> >   drm/bridge: tfp410: Don't include drmP.h
> >   drm/bridge: tfp410: Replace manual connector handling with bridge
> >   drm/bridge: tfp410: Allow operation without drm_connector
> >   dt-bindings: Add vendor prefix for LG Display
> >   dt-bindings: Add legacy 'toppoly' vendor prefix
> >   dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel
> >   drm/panel: Add driver for the LG Philips LB035Q02 panel
> >   drm/panel: Add driver for the NEC NL8048HL11 panel
> >   drm/panel: Add driver for the Sharp LS037V7DW01 panel
> >   drm/panel: Add driver for the Sony ACX565AKM panel
> >   drm/panel: Add driver for the Toppology TD028TTEC1 panel
> >   drm/panel: Add driver for the Toppology TD043MTEA1 panel
> >   drm: Add helper to create a connector for a chain of bridges
> >   drm/omap: Detach from panels at remove time
> >   drm/omap: Simplify HDMI mode and infoframe configuration
> >   drm/omap: Factor out display type to connector type conversion
> >   drm/omap: Use the drm_panel_bridge API
> >   drm/omap: dss: Fix output next device lookup in DT
> >   drm/omap: Add infrastructure to support drm_bridge local to DSS
> >     outputs
> >   drm/omap: dss: Make omap_dss_device_ops optional
> >   drm/omap: hdmi: Allocate EDID in the .read_edid() operation
> >   drm/omap: hdmi4: Rework EDID read to isolate data read
> >   drm/omap: hdmi5: Rework EDID read to isolate data read
> >   drm/omap: hdmi4: Register a drm_bridge for EDID read
> >   drm/omap: hdmi5: Register a drm_bridge for EDID read
> >   drm/omap: hdmi4: Move mode set, enable and disable operations to
> >     bridge
> >   drm/omap: hdmi5: Move mode set, enable and disable operations to
> >     bridge
> >   drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation
> >   drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations
> >   drm/omap: venc: Register a drm_bridge
> >   drm/omap: Create connector for bridges
> >   drm/omap: Switch the HDMI and VENC outputs to drm_bridge
> >   drm/omap: Remove HPD, detect and EDID omapdss operations
> >   drm/omap: hdmi: Remove omap_dss_device operations
> >   drm/omap: venc: Remove omap_dss_device operations
> >   drm/omap: hdmi4: Simplify EDID read
> >   drm/omap: hdmi5: Simplify EDID read
> >   drm/omap: displays: Remove unused panel drivers
> >   drm/omap: dpi: Sort includes alphabetically
> >   drm/omap: dpi: Reorder functions in sections
> >   drm/omap: dpi: Simplify clock setting API
> >   drm/omap: dpi: Register a drm_bridge
> >   drm/omap: sdi: Sort includes alphabetically
> >   drm/omap: sdi: Register a drm_bridge
> >   drm/omap: Simplify connector implementation
> >   drm/omap: dss: Remove unused omap_dss_device operations
> >   drm/omap: dss: Inline the omapdss_display_get() function
> >   drm/omap: dss: Remove unused omapdss_of_find_connected_device()
> >     function
> > 
> >  .../bindings/display/panel/nec,nl8048hl11.txt |  38 +
> >  .../devicetree/bindings/vendor-prefixes.yaml  |   4 +
> >  arch/arm/configs/davinci_all_defconfig        |   2 +-
> >  arch/arm/configs/integrator_defconfig         |   2 +-
> >  arch/arm/configs/multi_v7_defconfig           |   2 +-
> >  arch/arm/configs/shmobile_defconfig           |   2 +-
> >  arch/arm/configs/sunxi_defconfig              |   2 +-
> >  arch/arm/configs/versatile_defconfig          |   2 +-
> >  drivers/gpu/drm/Makefile                      |   3 +-
> >  drivers/gpu/drm/arc/arcpgu_hdmi.c             |   2 +-
> >  .../gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c  |   2 +-
> >  drivers/gpu/drm/bridge/Kconfig                |  29 +-
> >  drivers/gpu/drm/bridge/Makefile               |   4 +-
> >  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c  |   6 +-
> >  drivers/gpu/drm/bridge/analogix-anx78xx.c     |   6 +-
> >  .../drm/bridge/analogix/analogix_dp_core.c    |   8 +-
> >  drivers/gpu/drm/bridge/cdns-dsi.c             |   6 +-
> >  drivers/gpu/drm/bridge/display-connector.c    | 327 ++++++++
> >  drivers/gpu/drm/bridge/dumb-vga-dac.c         | 296 -------
> >  drivers/gpu/drm/bridge/lvds-encoder.c         |   4 +-
> >  .../bridge/megachips-stdpxxxx-ge-b850v3-fw.c  |   6 +-
> >  drivers/gpu/drm/bridge/nxp-ptn3460.c          |   6 +-
> >  drivers/gpu/drm/bridge/panel.c                |  21 +-
> >  drivers/gpu/drm/bridge/parade-ps8622.c        |   5 +-
> >  drivers/gpu/drm/bridge/sii902x.c              |   6 +-
> >  drivers/gpu/drm/bridge/sil-sii8620.c          |   2 +-
> >  drivers/gpu/drm/bridge/simple-bridge.c        | 337 ++++++++
> >  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c     |   8 +-
> >  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c |   8 +-
> >  drivers/gpu/drm/bridge/tc358764.c             |   5 +-
> >  drivers/gpu/drm/bridge/tc358767.c             |   5 +-
> >  drivers/gpu/drm/bridge/thc63lvd1024.c         |   5 +-
> >  drivers/gpu/drm/bridge/ti-sn65dsi86.c         |   5 +-
> >  drivers/gpu/drm/bridge/ti-tfp410.c            | 202 ++---
> >  drivers/gpu/drm/bridge/ti-tpd12s015.c         | 204 +++++
> >  drivers/gpu/drm/drm_bridge.c                  |  97 ++-
> >  drivers/gpu/drm/drm_bridge_connector.c        | 385 +++++++++
> >  drivers/gpu/drm/drm_edid.c                    |   8 +-
> >  drivers/gpu/drm/drm_simple_kms_helper.c       |   2 +-
> >  drivers/gpu/drm/exynos/exynos_dp.c            |   3 +-
> >  drivers/gpu/drm/exynos/exynos_drm_dsi.c       |   4 +-
> >  drivers/gpu/drm/exynos/exynos_hdmi.c          |   2 +-
> >  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c     |   2 +-
> >  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c  |   2 +-
> >  drivers/gpu/drm/i2c/tda998x_drv.c             |   8 +-
> >  drivers/gpu/drm/imx/imx-ldb.c                 |   2 +-
> >  drivers/gpu/drm/imx/parallel-display.c        |   2 +-
> >  drivers/gpu/drm/mcde/mcde_dsi.c               |   6 +-
> >  drivers/gpu/drm/mediatek/mtk_dpi.c            |   2 +-
> >  drivers/gpu/drm/mediatek/mtk_dsi.c            |   2 +-
> >  drivers/gpu/drm/mediatek/mtk_hdmi.c           |   8 +-
> >  drivers/gpu/drm/msm/dsi/dsi_manager.c         |   4 +-
> >  drivers/gpu/drm/msm/edp/edp_bridge.c          |   2 +-
> >  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c        |   2 +-
> >  drivers/gpu/drm/omapdrm/displays/Kconfig      |  60 --
> >  drivers/gpu/drm/omapdrm/displays/Makefile     |  10 -
> >  .../omapdrm/displays/connector-analog-tv.c    | 100 ---
> >  .../gpu/drm/omapdrm/displays/connector-hdmi.c | 186 -----
> >  .../gpu/drm/omapdrm/displays/encoder-opa362.c | 140 ----
> >  .../drm/omapdrm/displays/encoder-tpd12s015.c  | 220 -----
> >  .../gpu/drm/omapdrm/displays/panel-dsi-cm.c   |   2 +-
> >  .../displays/panel-lgphilips-lb035q02.c       | 254 ------
> >  .../omapdrm/displays/panel-nec-nl8048hl11.c   | 271 -------
> >  .../displays/panel-sharp-ls037v7dw01.c        | 265 ------
> >  .../omapdrm/displays/panel-sony-acx565akm.c   | 766 ------------------
> >  .../omapdrm/displays/panel-tpo-td028ttec1.c   | 401 ---------
> >  .../omapdrm/displays/panel-tpo-td043mtea1.c   | 513 ------------
> >  drivers/gpu/drm/omapdrm/dss/Makefile          |   2 +-
> >  drivers/gpu/drm/omapdrm/dss/base.c            |  72 +-
> >  drivers/gpu/drm/omapdrm/dss/display.c         |   9 -
> >  drivers/gpu/drm/omapdrm/dss/dpi.c             | 336 ++++----
> >  drivers/gpu/drm/omapdrm/dss/dsi.c             |   4 +-
> >  drivers/gpu/drm/omapdrm/dss/dss-of.c          |  28 -
> >  drivers/gpu/drm/omapdrm/dss/dss.c             |   3 +-
> >  drivers/gpu/drm/omapdrm/dss/hdmi.h            |   4 +-
> >  drivers/gpu/drm/omapdrm/dss/hdmi4.c           | 321 ++++----
> >  drivers/gpu/drm/omapdrm/dss/hdmi4_core.c      |  59 +-
> >  drivers/gpu/drm/omapdrm/dss/hdmi4_core.h      |   4 +-
> >  drivers/gpu/drm/omapdrm/dss/hdmi5.c           | 303 +++----
> >  drivers/gpu/drm/omapdrm/dss/hdmi5_core.c      |  48 +-
> >  drivers/gpu/drm/omapdrm/dss/hdmi5_core.h      |   5 +-
> >  .../gpu/drm/omapdrm/dss/omapdss-boot-init.c   |  12 -
> >  drivers/gpu/drm/omapdrm/dss/omapdss.h         |  47 +-
> >  drivers/gpu/drm/omapdrm/dss/output.c          |  55 +-
> >  drivers/gpu/drm/omapdrm/dss/sdi.c             | 187 +++--
> >  drivers/gpu/drm/omapdrm/dss/venc.c            | 269 +++---
> >  drivers/gpu/drm/omapdrm/omap_connector.c      | 246 +-----
> >  drivers/gpu/drm/omapdrm/omap_connector.h      |   3 -
> >  drivers/gpu/drm/omapdrm/omap_drv.c            |  98 ++-
> >  drivers/gpu/drm/omapdrm/omap_encoder.c        |  83 +-
> >  drivers/gpu/drm/panel/Kconfig                 |  44 +
> >  drivers/gpu/drm/panel/Makefile                |   6 +
> >  drivers/gpu/drm/panel/panel-lg-lb035q02.c     | 235 ++++++
> >  drivers/gpu/drm/panel/panel-nec-nl8048hl11.c  | 249 ++++++
> >  .../gpu/drm/panel/panel-sharp-ls037v7dw01.c   | 231 ++++++
> >  drivers/gpu/drm/panel/panel-sony-acx565akm.c  | 691 ++++++++++++++++
> >  drivers/gpu/drm/panel/panel-tpo-td028ttec1.c  | 382 +++++++++
> >  drivers/gpu/drm/panel/panel-tpo-td043mtea1.c  | 510 ++++++++++++
> >  drivers/gpu/drm/rcar-du/rcar_du_encoder.c     |   2 +-
> >  drivers/gpu/drm/rcar-du/rcar_lvds.c           |   7 +-
> >  drivers/gpu/drm/rockchip/rockchip_lvds.c      |   2 +-
> >  drivers/gpu/drm/rockchip/rockchip_rgb.c       |   2 +-
> >  drivers/gpu/drm/sti/sti_dvo.c                 |   2 +-
> >  drivers/gpu/drm/sti/sti_hda.c                 |   2 +-
> >  drivers/gpu/drm/sti/sti_hdmi.c                |   2 +-
> >  drivers/gpu/drm/stm/ltdc.c                    |   2 +-
> >  drivers/gpu/drm/sun4i/sun4i_lvds.c            |   2 +-
> >  drivers/gpu/drm/sun4i/sun4i_rgb.c             |   2 +-
> >  drivers/gpu/drm/tilcdc/tilcdc_external.c      |   2 +-
> >  drivers/gpu/drm/vc4/vc4_dpi.c                 |   2 +-
> >  drivers/gpu/drm/vc4/vc4_dsi.c                 |   2 +-
> >  drivers/video/hdmi.c                          |   9 +-
> >  include/drm/drm_bridge.h                      | 174 +++-
> >  include/drm/drm_bridge_connector.h            |  18 +
> >  include/drm/drm_connector.h                   |   5 +
> >  include/linux/hdmi.h                          |   2 +-
> >  116 files changed, 5188 insertions(+), 4900 deletions(-)
> >  create mode 100644 Documentation/devicetree/bindings/display/panel/nec,nl8048hl11.txt
> >  create mode 100644 drivers/gpu/drm/bridge/display-connector.c
> >  delete mode 100644 drivers/gpu/drm/bridge/dumb-vga-dac.c
> >  create mode 100644 drivers/gpu/drm/bridge/simple-bridge.c
> >  create mode 100644 drivers/gpu/drm/bridge/ti-tpd12s015.c
> >  create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-analog-tv.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/connector-hdmi.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-opa362.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/encoder-tpd12s015.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-lgphilips-lb035q02.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-nec-nl8048hl11.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sharp-ls037v7dw01.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-sony-acx565akm.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td028ttec1.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/displays/panel-tpo-td043mtea1.c
> >  delete mode 100644 drivers/gpu/drm/omapdrm/dss/dss-of.c
> >  create mode 100644 drivers/gpu/drm/panel/panel-lg-lb035q02.c
> >  create mode 100644 drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
> >  create mode 100644 drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
> >  create mode 100644 drivers/gpu/drm/panel/panel-sony-acx565akm.c
> >  create mode 100644 drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
> >  create mode 100644 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> >  create mode 100644 include/drm/drm_bridge_connector.h

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel
  2019-07-08 19:10     ` Sam Ravnborg
  2019-07-08 19:26       ` Sam Ravnborg
@ 2019-08-08 15:17       ` Laurent Pinchart
  1 sibling, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 15:17 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Sam,

On Mon, Jul 08, 2019 at 09:10:08PM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> Thanks for keeping me busy :-)

My pleasure ;-)

> On Sun, Jul 07, 2019 at 09:18:57PM +0300, Laurent Pinchart wrote:
> > This panel is used on the Zoom2/3/3630 SDP boards.
> 
> This information may be good to have in the Kconfig help entry too.
> 
> Maybe tell in the changelog where this code originates from.

Good point, I'll do so.

> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-nec-nl8048hl11.c
> > @@ -0,0 +1,249 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * NEC NL8048HL11 Panel Driver
> > + *
> > + * Copyright (C) 2019 Texas Instruments Incorporated
> > + *
> > + * Based on the omapdrm-specific panel-nec-nl8048hl11 driver
> > + *
> > + * Copyright (C) 2010 Texas Instruments Incorporated
> > + * Author: Erik Gilling <konkers@android.com>
> > + */
>
> No added copyright from you?
> (Apply to all panel drivers)

The copyright goes to TI for this specific work.

> > +#include <linux/delay.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/module.h>
> > +#include <linux/pm.h>
> > +#include <linux/spi/spi.h>
> > +
> > +#include <drm/drm_connector.h>
> > +#include <drm/drm_modes.h>
> > +#include <drm/drm_panel.h>
> 
> Good to see panel drivers that are NOT using drmP.h :-)
> 
> > +
> > +struct nl8048_device {
> > +	struct drm_panel panel;
> > +
> > +	struct spi_device *spi;
> > +	struct gpio_desc *reset_gpio;
> > +};
>
> Naming bikeshedding. This is a nl8048_panel, not a device.

It's a device too, but I'll change this.

> > +
> > +static const struct drm_display_mode nl8048_mode = {
> > +	/*  NEC PIX Clock Ratings MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz */
> > +	.clock	= 23800,
> > +	.hdisplay = 800,
> > +	.hsync_start = 800 + 6,
> > +	.hsync_end = 800 + 6 + 1,
> > +	.htotal = 800 + 6 + 1 + 4,
> > +	.vdisplay = 480,
> > +	.vsync_start = 480 + 3,
> > +	.vsync_end = 480 + 3 + 1,
> > +	.vtotal = 480 + 3 + 1 + 4,
> > +	.vrefresh = 60,
> > +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> > +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> > +};
>
> Same comment as for previous patch on height/width.

Fixed.

> > +
> > +static int nl8048_get_modes(struct drm_panel *panel)
> > +{
> > +	struct drm_connector *connector = panel->connector;
> > +	struct drm_display_mode *mode;
> > +
> > +	mode = drm_mode_duplicate(panel->drm, &nl8048_mode);
> > +	if (!mode)
> > +		return -ENOMEM;
> > +
> > +	drm_mode_set_name(mode);
> > +	drm_mode_probed_add(connector, mode);
> > +
> > +	connector->display_info.width_mm = 89;
> > +	connector->display_info.height_mm = 53;
> > +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> > +					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
> > +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> > +
> > +	return 1;
> > +}
> 
> This should be moved to drm_panle_helper.c (which we do not have yet.
> But the function is used in some many drivers it makes sense.
> On my TODO list.
> I have yet to find a good way to specify the bus_flags though.

It's a bit annoying to have different set of flags for the various
structures that describe modes and display information. Cleaning all
that up would be great, but will be a significant effort.

> > +#ifdef CONFIG_PM_SLEEP
> 
> Use __maybe_unused, and loose the #ifdef

OK.

> And why does this panel need suspend/resume?
> The panel is supposed to be turned off in disable()
> 
> To simple solution would be to move the write to "2" to
> the enable and disable functions.
> And then this driver is alinged with the rest.
> 
> > +static int nl8048_suspend(struct device *dev)
> > +{
> > +	struct nl8048_device *lcd = dev_get_drvdata(dev);
> > +
> > +	nl8048_write(lcd, 2, 0x01);
> > +	msleep(40);
> 
> This sleep puzzle me. What is the puspose?
> And the write is to a display that is already reset??

This has been copied from the omapdrm-specific driver, and I have no
hardware to test this, so I didn't dare changing the code. I would
prefer reworking the suspend/resume on top of this patch, ideally
developed by someone who can test the changes.

> > +
> > +	return 0;
> > +}
> > +
> > +static int nl8048_resume(struct device *dev)
> > +{
> > +	struct nl8048_device *lcd = dev_get_drvdata(dev);
> > +
> > +	/* Reinitialize the panel. */
> > +	spi_setup(lcd->spi);
> > +	nl8048_write(lcd, 2, 0x00);
> > +	nl8048_init(lcd);
> > +
> > +	return 0;
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(nl8048_pm_ops, nl8048_suspend, nl8048_resume);
> > +#endif
> > +
> > +static int nl8048_probe(struct spi_device *spi)
> > +{
> > +	struct nl8048_device *lcd;
> > +	int ret;
> > +
> > +	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> > +	if (lcd == NULL)
> > +		return -ENOMEM;
> > +
> > +	spi_set_drvdata(spi, lcd);
> > +	lcd->spi = spi;
> > +
> > +	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->reset_gpio)) {
> > +		dev_err(&spi->dev, "failed to parse reset gpio\n");
> > +		return PTR_ERR(lcd->reset_gpio);
> > +	}
> > +
> > +	spi->mode = SPI_MODE_0;
> > +	spi->bits_per_word = 32;
> > +
> > +	ret = spi_setup(spi);
> > +	if (ret < 0) {
> > +		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = nl8048_init(lcd);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	drm_panel_init(&lcd->panel);
> > +	lcd->panel.dev = &lcd->spi->dev;
> > +	lcd->panel.funcs = &nl8048_funcs;
> > +
> > +	return drm_panel_add(&lcd->panel);
> > +}
> > +
> > +static int nl8048_remove(struct spi_device *spi)
> > +{
> > +	struct nl8048_device *lcd = spi_get_drvdata(spi);
> > +
> > +	drm_panel_remove(&lcd->panel);
> > +	nl8048_disable(&lcd->panel);
> 
> Use drm_panel_disable() - same comment as other panel driver.

Sure.

> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id nl8048_of_match[] = {
> > +	{ .compatible = "nec,nl8048hl11", },
> > +	{},
> 
> { /* sentinel */ },?

Done.

> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, nl8048_of_match);
> > +
> > +static struct spi_driver nl8048_driver = {
> > +	.probe		= nl8048_probe,
> > +	.remove		= nl8048_remove,
> > +	.driver		= {
> > +		.name	= "panel-nec-nl8048hl11",
> > +#ifdef CONFIG_PM_SLEEP
> > +		.pm	= &nl8048_pm_ops,
> > +#endif
> > +		.of_match_table = nl8048_of_match,
> > +	},
> > +};
> > +
> > +module_spi_driver(nl8048_driver);
> > +
> > +MODULE_ALIAS("spi:nec,nl8048hl11");
> > +MODULE_AUTHOR("Erik Gilling <konkers@android.com>");
> > +MODULE_DESCRIPTION("NEC-NL8048HL11 Driver");
> > +MODULE_LICENSE("GPL");
> "GPL v2"?
> 
> The suspend/resume thing needs to be sorted out before I can add my r-b
> on this.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel
  2019-07-08 19:44     ` Sam Ravnborg
  2019-07-08 19:47       ` Sam Ravnborg
@ 2019-08-08 15:31       ` Laurent Pinchart
  1 sibling, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 15:31 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Sam,

On Mon, Jul 08, 2019 at 09:44:52PM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> Third panel driver in line for review.
> Review comments that are duplicates from the first two will have only a
> brief remark - if any.

I'll address them the same way. Please consider all unanswered comments
below as addressed.

> On Sun, Jul 07, 2019 at 09:18:58PM +0300, Laurent Pinchart wrote:
> > This panel is used on the SDP3430.
>
> Add a little more context and put it in Kconfig help.
> Maybe this is the TI board, and maybe it is something else.
> 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/panel/Kconfig                 |   7 +
> >  drivers/gpu/drm/panel/Makefile                |   1 +
> >  .../gpu/drm/panel/panel-sharp-ls037v7dw01.c   | 231 ++++++++++++++++++
> >  3 files changed, 239 insertions(+)
> >  create mode 100644 drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
> > 
> > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > index da613c04b835..04fd152efe4c 100644
> > --- a/drivers/gpu/drm/panel/Kconfig
> > +++ b/drivers/gpu/drm/panel/Kconfig
> > @@ -271,6 +271,13 @@ config DRM_PANEL_SHARP_LS043T1LE01
> >  	  Say Y here if you want to enable support for Sharp LS043T1LE01 qHD
> >  	  (540x960) DSI panel as found on the Qualcomm APQ8074 Dragonboard
> >  
> > +config DRM_PANEL_SHARP_LS037V7DW01
> > +	tristate "Sharp LS037V7DW01 VGA LCD panel"
> > +	depends on GPIOLIB && OF && REGULATOR
> > +	help
> > +	  Say Y here if you want to enable support for Sharp LS037V7DW01 VGA
> > +	  (480x640) LCD panel.
> > +
> 
> Alphabetical order, so it comes before DRM_PANEL_SHARP_LS043T1LE01
> 
> >  config DRM_PANEL_SITRONIX_ST7701
> >  	tristate "Sitronix ST7701 panel driver"
> >  	depends on OF
> > diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
> > index e81ed1535024..12dcd76eb87c 100644
> > --- a/drivers/gpu/drm/panel/Makefile
> > +++ b/drivers/gpu/drm/panel/Makefile
> > @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E63M0) += panel-samsung-s6e63m0.o
> >  obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o
> >  obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o
> >  obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
> > +obj-$(CONFIG_DRM_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
> >  obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o
> 
> And here it is right.
> 
> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-sharp-ls037v7dw01.c
> > @@ -0,0 +1,231 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Sharp LS037V7DW01 LCD Panel Driver
> > + *
> > + * Copyright (C) 2019 Texas Instruments Incorporated
> > + *
> > + * Based on the omapdrm-specific panel-sharp-ls037v7dw01 driver
> > + *
> > + * Copyright (C) 2013 Texas Instruments Incorporated
> > + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
> 
> Add your copyright?

As with the previous patches, the copyright goes to TI.

> > +struct ls037v7dw01_device {
> > +	struct drm_panel panel;
> > +	struct platform_device *pdev;
> > +
> > +	struct regulator *vcc;
> 
> The property is named envdd - should they use the same name?

I'll rename that to vdd.

> > +	struct gpio_desc *resb_gpio;	/* low = reset active min 20 us */
> > +	struct gpio_desc *ini_gpio;	/* high = power on */
> > +	struct gpio_desc *mo_gpio;	/* low = 480x640, high = 240x320 */
> > +	struct gpio_desc *lr_gpio;	/* high = conventional horizontal scanning */
> > +	struct gpio_desc *ud_gpio;	/* high = conventional vertical scanning */
> > +};
> 
> device versus panel, but bikeshedding, so feel free to ignore.

I'll rename it.

> > +
> > +static int ls037v7dw01_disable(struct drm_panel *panel)
> > +{
> > +	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
> > +
> > +	gpiod_set_value_cansleep(lcd->ini_gpio, 0);
> > +	gpiod_set_value_cansleep(lcd->resb_gpio, 0);
> > +
> > +	/* Wait at least 5 vsyncs after disabling the LCD. */
> > +	msleep(100);
> > +
> > +	return 0;
> > +}
> > +
> > +static int ls037v7dw01_unprepare(struct drm_panel *panel)
> > +{
> > +	struct ls037v7dw01_device *lcd = to_ls037v7dw01_device(panel);
> > +
> > +	if (lcd->vcc)
> > +		regulator_disable(lcd->vcc);
> 
> Why is the if (lcd-vcc) needed?
> If I read the probe code correct then we either get a regulator or we
> error out.
> 
> Same goes for all other checks of lcd->vcc
> 
> > +static const struct drm_display_mode ls037v7dw01_mode = {
> > +	.clock = 19200,
> > +	.hdisplay = 480,
> > +	.hsync_start = 480 + 1,
> > +	.hsync_end = 480 + 1 + 2,
> > +	.htotal = 480 + 1 + 2 + 28,
> > +	.vdisplay = 640,
> > +	.vsync_start = 640 + 1,
> > +	.vsync_end = 640 + 1 + 1,
> > +	.vtotal = 640 + 1 + 1 + 1,
> > +	.vrefresh = 58,
> > +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> > +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> > +};
> > +
> > +static int ls037v7dw01_get_modes(struct drm_panel *panel)
> > +{
> > +	struct drm_connector *connector = panel->connector;
> > +	struct drm_display_mode *mode;
> > +
> > +	mode = drm_mode_duplicate(panel->drm, &ls037v7dw01_mode);
> > +	if (!mode)
> > +		return -ENOMEM;
> > +
> > +	drm_mode_set_name(mode);
> > +	drm_mode_probed_add(connector, mode);
> > +
> > +	connector->display_info.width_mm = 56;
> > +	connector->display_info.height_mm = 75;
> > +	/*
> > +	 * FIXME: According to the datasheet pixel data is sampled on the
> > +	 * rising edge of the clock, but the code running on the SDP3430
> > +	 * indicates sampling on the negative edge. This should be tested on a
> > +	 * real device.
> > +	 */
> > +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> > +					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
> > +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
> > +
> > +	return 1;
> > +}
> > +
> > +static const struct drm_panel_funcs ls037v7dw01_funcs = {
> > +	.disable = ls037v7dw01_disable,
> > +	.unprepare = ls037v7dw01_unprepare,
> > +	.prepare = ls037v7dw01_prepare,
> > +	.enable = ls037v7dw01_enable,
> > +	.get_modes = ls037v7dw01_get_modes,
> > +};
> > +
> > +static int ls037v7dw01_probe(struct platform_device *pdev)
> > +{
> > +	struct ls037v7dw01_device *lcd;
> > +
> > +	lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL);
> > +	if (lcd == NULL)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, lcd);
> > +	lcd->pdev = pdev;
> > +
> > +	lcd->vcc = devm_regulator_get(&pdev->dev, "envdd");
> > +	if (IS_ERR(lcd->vcc)) {
> > +		dev_err(&pdev->dev, "failed to get regulator\n");
> > +		return PTR_ERR(lcd->vcc);
> > +	}
> > +
> > +	lcd->ini_gpio = devm_gpiod_get_index(&pdev->dev, "enable", 0,
> > +					    GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->ini_gpio)) {
> > +		dev_err(&pdev->dev, "failed to get enable gpio\n");
> > +		return PTR_ERR(lcd->ini_gpio);
> > +	}
> 
> I fail to see why the _index() variant is used here.
> But then I did not check the binding, so it may originate from that.
> Same goes for ireset gpio
> 
> > +
> > +	lcd->resb_gpio = devm_gpiod_get_index(&pdev->dev, "reset", 0,
> > +					     GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->resb_gpio)) {
> > +		dev_err(&pdev->dev, "failed to get reset gpio\n");
> > +		return PTR_ERR(lcd->resb_gpio);
> > +	}
> > +
> > +	lcd->mo_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 0,
> > +					   GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->mo_gpio)) {
> > +		dev_err(&pdev->dev, "failed to get mode[0] gpio\n");
> > +		return PTR_ERR(lcd->mo_gpio);
> > +	}
> > +
> > +	lcd->lr_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 1,
> > +					   GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->lr_gpio)) {
> > +		dev_err(&pdev->dev, "failed to get mode[1] gpio\n");
> > +		return PTR_ERR(lcd->lr_gpio);
> > +	}
> > +
> > +	lcd->ud_gpio = devm_gpiod_get_index(&pdev->dev, "mode", 2,
> > +					   GPIOD_OUT_LOW);
> > +	if (IS_ERR(lcd->ud_gpio)) {
> > +		dev_err(&pdev->dev, "failed to get mode[2] gpio\n");
> > +		return PTR_ERR(lcd->ud_gpio);
> > +	}
> 
> Do we set mo, lr ,ud gpio when we call devm_gpiod_get, or are they
> unused?

They are set by GPIOD_OUT_LOW.

> > +
> > +	drm_panel_init(&lcd->panel);
> > +	lcd->panel.dev = &pdev->dev;
> > +	lcd->panel.funcs = &ls037v7dw01_funcs;
> > +
> > +	return drm_panel_add(&lcd->panel);
> > +}
> > +
> > +static int ls037v7dw01_remove(struct platform_device *pdev)
> > +{
> > +	struct ls037v7dw01_device *lcd = platform_get_drvdata(pdev);
> > +
> > +	drm_panel_remove(&lcd->panel);
> > +	ls037v7dw01_disable(&lcd->panel);
> > +	ls037v7dw01_unprepare(&lcd->panel);
> 
> Use drm_panel_disable(), drm_panel_unprepare()
> 
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id ls037v7dw01_of_match[] = {
> > +	{ .compatible = "sharp,ls037v7dw01", },
> > +	{},
> 
> { /* sentinel */ },
> 
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, ls037v7dw01_of_match);
> > +
> > +static struct platform_driver ls037v7dw01_driver = {
> > +	.probe		= ls037v7dw01_probe,
> > +	.remove		= __exit_p(ls037v7dw01_remove),
> > +	.driver		= {
> > +		.name = "panel-sharp-ls037v7dw01",
> > +		.of_match_table = ls037v7dw01_of_match,
> > +	},
> > +};
> > +
> > +module_platform_driver(ls037v7dw01_driver);
> > +
> > +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
> > +MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
> > +MODULE_LICENSE("GPL");
> 
> "GPL v2"?

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel
  2019-07-10  7:48     ` Sam Ravnborg
@ 2019-08-08 15:43       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 15:43 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Sam,

On Wed, Jul 10, 2019 at 09:48:55AM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> This driver looks very good.
> 
> On Sun, Jul 07, 2019 at 09:19:00PM +0300, Laurent Pinchart wrote:
> > This panel is used on the OpenMoko Neo FreeRunner and Neo 1973.
> 
> Add info in Kconfig help entry?
> 
> > +config DRM_PANEL_TPO_TD028TTEC1
> > +	tristate "TPO TD028TTEC1 panel driver"
> 
> Maybe spell out TPO like "TPO (Topology) TD028..."
>
> > +	depends on OF && SPI
> > +	depends on BACKLIGHT_CLASS_DEVICE
> > +	help
> > +	  Say Y here if you want to enable support for TPO TD028TTEC1 480x640
> > +	  2.8" panel.
> > +
> >  config DRM_PANEL_TPO_TPG110
> >  	tristate "TPO TPG 800x400 panel"
> >  	depends on OF && SPI && GPIOLIB
> >  obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
> > diff --git a/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
> > new file mode 100644
> > index 000000000000..05af9ea6339c
> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-tpo-td028ttec1.c
> > +
> > +static int jbt_ret_write_0(struct td028ttec1_device *lcd, u8 reg, int *err)
> > +{
> > +	struct spi_device *spi = lcd->spi;
> > +	u16 tx_buf = JBT_COMMAND | reg;
> > +	int ret;
> > +
> > +	if (err && *err)
> > +		return *err;
> > +
> > +	ret = spi_write(spi, (u8 *)&tx_buf, sizeof(tx_buf));
> > +	if (ret < 0) {
> > +		dev_err(&spi->dev, "%s: SPI write failed: %d\n", __func__, ret);
> > +		if (err)
> > +			*err = ret;
> > +	}
> > +
> > +	return ret;
> > +}
> 
> I like the way *err is used here.
> So if one call fails, the remaining calls are ignored.

I think it's a construct that could be used in more drivers, when
dealing with large sequences of writes.

> The way the code is written above it will only work on a little endian
> box, as the values are stored in an u16 that is later seen as an array of
> bytes.
> This is also true for the remaing similar functions and may be OK.
> We do not see any real demands for big endian anyway.

This could be fixed if desired, but I would do so on top of this patch
to minimise the changes compared to the omapdrm-specific panel driver.

> > +static int td028ttec1_enable(struct drm_panel *panel)
> > +{
> > +	struct td028ttec1_device *lcd = to_td028ttec1_device(panel);
> > +	unsigned int i;
> > +	int ret = 0;
> > +
> > +	/* Three times command zero */
> > +	for (i = 0; i < 3; ++i) {
> > +		jbt_ret_write_0(lcd, 0x00, &ret);
> > +		usleep_range(1000, 2000);
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> 
> This if (ret) is really not needed.
> It somehow short-circuit the principle used in the rest of the function
> here. All jbt_reg_write() will be nop if ret != 0.

I'll drop it.

> > +
> > +	/* deep standby out */
> > +	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x17, &ret);
> > +
> > +	/* RGB I/F on, RAM write off, QVGA through, SIGCON enable */
> > +	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE, 0x80, &ret);
> > +
> > +	/* Quad mode off */
> > +	jbt_reg_write_1(lcd, JBT_REG_QUAD_RATE, 0x00, &ret);
> > +
> > +	/* AVDD on, XVDD on */
> > +	jbt_reg_write_1(lcd, JBT_REG_POWER_ON_OFF, 0x16, &ret);
> > +
> > +	/* Output control */
> > +	jbt_reg_write_2(lcd, JBT_REG_OUTPUT_CONTROL, 0xfff9, &ret);
> > +
> > +	/* Sleep mode off */
> > +	jbt_ret_write_0(lcd, JBT_REG_SLEEP_OUT, &ret);
> > +
> > +	/* at this point we have like 50% grey */
> > +
> > +	/* initialize register set */
> > +	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE1, 0x01, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_DISPLAY_MODE2, 0x00, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_RGB_FORMAT, 0x60, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_DRIVE_SYSTEM, 0x10, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_OP, 0x56, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_MODE, 0x33, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_BOOSTER_FREQ, 0x11, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_OPAMP_SYSCLK, 0x02, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_VSC_VOLTAGE, 0x2b, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_VCOM_VOLTAGE, 0x40, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_EXT_DISPL, 0x03, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_DCCLK_DCEV, 0x04, &ret);
> > +	/*
> > +	 * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement
> > +	 * to avoid red / blue flicker
> > +	 */
> > +	jbt_reg_write_1(lcd, JBT_REG_ASW_SLEW, 0x04, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_DUMMY_DISPLAY, 0x00, &ret);
> > +
> > +	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_A, 0x11, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_B, 0x11, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_SLEEP_OUT_FR_C, 0x11, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0, &ret);
> > +
> > +	jbt_reg_write_2(lcd, JBT_REG_GAMMA1_FINE_1, 0x5533, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_FINE_2, 0x00, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_INCLINATION, 0x00, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00, &ret);
> > +
> > +	jbt_reg_write_2(lcd, JBT_REG_HCLOCK_VGA, 0x1f0, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_BLANK_CONTROL, 0x02, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_BLANK_TH_TV, 0x0804, &ret);
> > +
> > +	jbt_reg_write_1(lcd, JBT_REG_CKV_ON_OFF, 0x01, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_CKV_1_2, 0x0000, &ret);
> > +
> > +	jbt_reg_write_2(lcd, JBT_REG_OEV_TIMING, 0x0d0e, &ret);
> > +	jbt_reg_write_2(lcd, JBT_REG_ASW_TIMING_1, 0x11a4, &ret);
> > +	jbt_reg_write_1(lcd, JBT_REG_ASW_TIMING_2, 0x0e, &ret);
> > +
> > +	jbt_ret_write_0(lcd, JBT_REG_DISPLAY_ON, &ret);
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	backlight_enable(lcd->backlight);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct drm_display_mode td028ttec1_mode = {
> > +	.clock = 22153,
> > +	.hdisplay = 480,
> > +	.hsync_start = 480 + 24,
> > +	.hsync_end = 480 + 24 + 8,
> > +	.htotal = 480 + 24 + 8 + 8,
> > +	.vdisplay = 640,
> > +	.vsync_start = 640 + 4,
> > +	.vsync_end = 640 + 4 + 2,
> > +	.vtotal = 640 + 4 + 2 + 2,
> > +	.vrefresh = 66,
> > +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> > +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> > +};
> 
> Add width_mm + height_mm.
> 
> > +static int td028ttec1_remove(struct spi_device *spi)
> > +{
> > +	struct td028ttec1_device *lcd = spi_get_drvdata(spi);
> > +
> > +	drm_panel_remove(&lcd->panel);
> > +	td028ttec1_disable(&lcd->panel);
> 
> Use drm_panel_disable();
> 
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id td028ttec1_of_match[] = {
> > +	{ .compatible = "tpo,td028ttec1", },
> > +	/* DT backward compatibility. */
> > +	{ .compatible = "toppoly,td028ttec1", },
> > +	{},
> 
> { /* sentinel */ },
> 
> With the above nits fixed/considered:
> Reviewed-by: Sam Ravnborg <sam@ravnborg.org>
> 
> 	Sam

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel
  2019-07-10 13:09     ` Sam Ravnborg
@ 2019-08-08 15:54       ` Laurent Pinchart
  2019-08-09 13:33         ` Sam Ravnborg
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 15:54 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Sam,

On Wed, Jul 10, 2019 at 03:09:17PM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> I had assumed this driver would look like the other Topology driver, but
> they differ a lot. So it makes sense to have different drivers.
> 
> This driver implements suspend/resume.
> But the correct way would be to implment prepare/unprepare.

As for the NEC panel, I'm not opposed to this change, but I have no
hardware to test it, so I would prefer if it was later done on top.

> The power_on(), power_off() functions would then be embedded in
> the prepare(), unprepare() functions as there would be only one user.
> 
> See additional comments in the following.
> 
> On Sun, Jul 07, 2019 at 09:19:01PM +0300, Laurent Pinchart wrote:
> > This panel is used on the OMAP3 Pandora.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/panel/Kconfig                |   7 +
> >  drivers/gpu/drm/panel/Makefile               |   1 +
> >  drivers/gpu/drm/panel/panel-tpo-td043mtea1.c | 510 +++++++++++++++++++
> >  3 files changed, 518 insertions(+)
> >  create mode 100644 drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> > 
> > diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
> > index b7099d211061..8f3660c73044 100644
> > --- a/drivers/gpu/drm/panel/Kconfig
> > +++ b/drivers/gpu/drm/panel/Kconfig
> > @@ -312,6 +312,13 @@ config DRM_PANEL_TPO_TD028TTEC1
> >  	  Say Y here if you want to enable support for TPO TD028TTEC1 480x640
> >  	  2.8" panel.
> >  
> > +config DRM_PANEL_TPO_TD043MTEA1
> > +	tristate "TPO TD043MTEA1 panel driver"
>
> Spell out TPO?
>
> > +	depends on GPIOLIB && OF && REGULATOR && SPI
> > +	help
> > +	  Say Y here if you want to enable support for TPO TD043MTEA1 800x480
> > +	  4.3" panel.
>
> Maybe tell it is used on OMAP3 Pandora
> 
> > +
> >  config DRM_PANEL_TPO_TPG110
> >  	tristate "TPO TPG 800x400 panel"
> >  	depends on OF && SPI && GPIOLIB
> > diff --git a/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> > new file mode 100644
> > index 000000000000..6b17e47582b8
> > --- /dev/null
> > +++ b/drivers/gpu/drm/panel/panel-tpo-td043mtea1.c
> > @@ -0,0 +1,510 @@
> > +// SPDX-License-Identifier: GPL-2.0+
>
> Just noticed, this is a different license than the others.
> But I guess this comes from the original file.

Correct.


> > +/*
> > + * Toppology TD043MTEA1 Panel Driver
> 
> So I actually asked Google this time - the correct spelling is
> "Toppoly".
> See: http://www.innolux.com/Pages/EN/AboutUs/Company_Overview_EN.html

Fixed (and in the previous patch too).

> > +struct td043mtea1_device {
> > +	struct drm_panel panel;
> > +
> > +	struct spi_device *spi;
> > +	struct regulator *vcc_reg;
> > +	struct gpio_desc *reset_gpio;
> > +
> > +	unsigned int mode;
> > +	u16 gamma[12];
> > +	bool vmirror;
> > +	bool powered_on;
> 
> This flag will not be needed when prepare(), unprepare() are used.
> 
> > +	bool spi_suspended;
> > +	bool power_on_resume;
> > +};
> > +
> > +
> 
> > +static void td043mtea1_write_gamma(struct td043mtea1_device *lcd)
> > +{
> > +	const u16 *gamma = lcd->gamma;
> > +	unsigned int i;
> > +	u8 val;
> > +
> > +	/* gamma bits [9:8] */
> > +	for (val = i = 0; i < 4; i++)
> > +		val |= (gamma[i] & 0x300) >> ((i + 1) * 2);
> > +	td043mtea1_write(lcd, 0x11, val);
> > +
> > +	for (val = i = 0; i < 4; i++)
> > +		val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2);
> 
> Spaces around operators.
> 
> > +	td043mtea1_write(lcd, 0x12, val);
> > +
> > +	for (val = i = 0; i < 4; i++)
> > +		val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2);
> 
> Same here.
> 
> > +	td043mtea1_write(lcd, 0x13, val);
> > +
> > +	/* gamma bits [7:0] */
> > +	for (val = i = 0; i < 12; i++)
> > +		td043mtea1_write(lcd, 0x14 + i, gamma[i] & 0xff);
> > +}
> 
> This function (td043mtea1_write_gamma()) fails to check the result of
> the write operations. Maybe on purpose. But looks strange we do it in
> some places but not all.

The return value is only checked in td043mtea1_write_mirror(). I think
this should be fixed eventually, but again, no hardware to test :-( I'm
this a bit reluctant to make intrusive changes.

> > +
> > +static int td043mtea1_write_mirror(struct td043mtea1_device *lcd)
> > +{
> > +	u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V |
> > +		TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H;
> > +	if (lcd->vmirror)
> > +		reg4 &= ~TPO_R04_NFLIP_V;
> > +
> > +	return td043mtea1_write(lcd, 4, reg4);
> > +}
> 
> Add a:
> #define TPO_R4	4
> And then use it here?
> Looks bad that the register number is hardcoded.
> 
> Same goes for several other calls to the write() function.

Without any idea of what those registers are for (and without the panel
documentation), I think TPO_R4 is actually less readable than 4.

> > +
> > +static int td043mtea1_power_on(struct td043mtea1_device *lcd)
> > +{
> > +	int ret;
> > +
> > +	if (lcd->powered_on)
> > +		return 0;
> > +
> > +	ret = regulator_enable(lcd->vcc_reg);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	/* Wait for the panel to stabilize. */
> > +	msleep(160);
> > +
> > +	gpiod_set_value(lcd->reset_gpio, 0);
> > +
> > +	td043mtea1_write(lcd, 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING);
> > +	td043mtea1_write(lcd, 3, TPO_R03_VAL_NORMAL);
> > +	td043mtea1_write(lcd, 0x20, 0xf0);
> > +	td043mtea1_write(lcd, 0x21, 0xf0);
> > +	td043mtea1_write_mirror(lcd);
> > +	td043mtea1_write_gamma(lcd);
> > +
> > +	lcd->powered_on = true;
> > +
> > +	return 0;
> > +}
> 
> The above should be part of prepare()
> 
> > +
> > +static void td043mtea1_power_off(struct td043mtea1_device *lcd)
> > +{
> > +	if (!lcd->powered_on)
> > +		return;
> > +
> > +	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
> > +
> > +	gpiod_set_value(lcd->reset_gpio, 1);
> > +
> > +	/* wait for at least 2 vsyncs before cutting off power */
> > +	msleep(50);
> > +
> > +	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY);
> > +
> > +	regulator_disable(lcd->vcc_reg);
> > +
> > +	lcd->powered_on = false;
> > +}
> 
> The above should be part of unprepare()
> 
> > +
> > +/* -----------------------------------------------------------------------------
> > + * sysfs
> > + */
> > +
> > +static ssize_t vmirror_show(struct device *dev, struct device_attribute *attr,
> > +			    char *buf)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +
> > +	return snprintf(buf, PAGE_SIZE, "%d\n", lcd->vmirror);
> > +}
> > +
> > +static ssize_t vmirror_store(struct device *dev, struct device_attribute *attr,
> > +			     const char *buf, size_t count)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +	int val;
> > +	int ret;
> > +
> > +	ret = kstrtoint(buf, 0, &val);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	lcd->vmirror = !!val;
> > +
> > +	ret = td043mtea1_write_mirror(lcd);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	return count;
> > +}
> > +
> > +static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
> > +			 char *buf)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +
> > +	return snprintf(buf, PAGE_SIZE, "%d\n", lcd->mode);
> > +}
> > +
> > +static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
> > +			  const char *buf, size_t count)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +	long val;
> > +	int ret;
> > +
> > +	ret = kstrtol(buf, 0, &val);
> > +	if (ret != 0 || val & ~7)
> > +		return -EINVAL;
> > +
> > +	lcd->mode = val;
> > +
> > +	val |= TPO_R02_NCLK_RISING;
> > +	td043mtea1_write(lcd, 2, val);
> > +
> > +	return count;
> > +}
> > +
> > +static ssize_t gamma_show(struct device *dev, struct device_attribute *attr,
> > +			  char *buf)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +	ssize_t len = 0;
> > +	unsigned int i;
> > +	int ret;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) {
> > +		ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
> > +				lcd->gamma[i]);
> > +		if (ret < 0)
> > +			return ret;
> > +		len += ret;
> > +	}
> > +	buf[len - 1] = '\n';
> > +
> > +	return len;
> > +}
> > +
> > +static ssize_t gamma_store(struct device *dev, struct device_attribute *attr,
> > +			   const char *buf, size_t count)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +	unsigned int g[12];
> > +	unsigned int i;
> > +	int ret;
> > +
> > +	ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u",
> > +			&g[0], &g[1], &g[2], &g[3], &g[4], &g[5],
> > +			&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
> > +	if (ret != 12)
> > +		return -EINVAL;
> > +
> > +	for (i = 0; i < 12; i++)
> > +		lcd->gamma[i] = g[i];
> > +
> > +	td043mtea1_write_gamma(lcd);
> > +
> > +	return count;
> > +}
> > +
> > +static DEVICE_ATTR_RW(vmirror);
> > +static DEVICE_ATTR_RW(mode);
> > +static DEVICE_ATTR_RW(gamma);
> > +
> > +static struct attribute *td043mtea1_attrs[] = {
> > +	&dev_attr_vmirror.attr,
> > +	&dev_attr_mode.attr,
> > +	&dev_attr_gamma.attr,
> > +	NULL,
> > +};
> > +
> > +static const struct attribute_group td043mtea1_attr_group = {
> > +	.attrs = td043mtea1_attrs,
> > +};
> 
> I see what is done with mirror, mode and gamma - but the question is if
> they are really needed?
> And if needed, is it the right way to configure the panel?
> This is likely questiosn that are not easy to answer definitive, so best
> to keep this as it was before.

I'm afraid I have no idea how (and if) those are used :-S

> > +
> > +/* -----------------------------------------------------------------------------
> > + * Bridge Operations
> > + */
> 
> Panel operations, not bridge operations?
> 
> > +
> > +static int td043mtea1_disable(struct drm_panel *panel)
> > +{
> > +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> > +
> > +	if (!lcd->spi_suspended)
> > +		td043mtea1_power_off(lcd);
> > +
> > +	return 0;
> > +}
> > +
> > +static int td043mtea1_enable(struct drm_panel *panel)
> > +{
> > +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> > +	int ret;
> > +
> > +	/*
> > +	 * If we are resuming from system suspend, SPI might not be enabled
> > +	 * yet, so we'll program the LCD from SPI PM resume callback.
> > +	 */
> > +	if (lcd->spi_suspended)
> > +		return 0;
> 
> I do not recall this is needed in other panel drivers, so look at what
> other spi based panels do here.
> I think this is something that today is not required.

The problem here is that the display controller may be resumed before
the SPI bus. Has that been solved somewhere in core code ?

> > +
> > +	ret = td043mtea1_power_on(lcd);
> > +	if (ret) {
> > +		dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n",
> > +			__func__, ret);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct drm_display_mode td043mtea1_mode = {
> > +	.clock = 36000,
> > +	.hdisplay = 800,
> > +	.hsync_start = 800 + 68,
> > +	.hsync_end = 800 + 68 + 1,
> > +	.htotal = 800 + 68 + 1 + 214,
> > +	.vdisplay = 480,
> > +	.vsync_start = 480 + 39,
> > +	.vsync_end = 480 + 39 + 1,
> > +	.vtotal = 480 + 39 + 1 + 34,
> > +	.vrefresh = 60,
> > +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> > +	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
> > +};
> 
> height_mm, width_mm
> 
> > +
> > +static int td043mtea1_get_modes(struct drm_panel *panel)
> > +{
> > +	struct drm_connector *connector = panel->connector;
> > +	struct drm_display_mode *mode;
> > +
> > +	mode = drm_mode_duplicate(panel->drm, &td043mtea1_mode);
> > +	if (!mode)
> > +		return -ENOMEM;
> > +
> > +	drm_mode_set_name(mode);
> > +	drm_mode_probed_add(connector, mode);
> > +
> > +	connector->display_info.width_mm = 94;
> > +	connector->display_info.height_mm = 56;
> > +	/*
> > +	 * FIXME: According to the datasheet sync signals are sampled on the
> > +	 * rising edge of the clock, but the code running on the OMAP3 Pandora
> > +	 * indicates sampling on the falling edge. This should be tested on a
> > +	 * real device.
> > +	 */
> > +	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
> > +					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
> > +					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
> > +
> > +	return 1;
> > +}
> > +
> > +static const struct drm_panel_funcs td043mtea1_funcs = {
> > +	.disable = td043mtea1_disable,
> > +	.enable = td043mtea1_enable,
> > +	.get_modes = td043mtea1_get_modes,
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Power Management, Probe and Remove
> > + */
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int td043mtea1_suspend(struct device *dev)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +
> > +	if (lcd->powered_on) {
> > +		td043mtea1_power_off(lcd);
> > +		lcd->powered_on = true;
> > +	}
> > +
> > +	lcd->spi_suspended = true;
> > +
> > +	return 0;
> > +}
> > +
> > +static int td043mtea1_resume(struct device *dev)
> > +{
> > +	struct td043mtea1_device *lcd = dev_get_drvdata(dev);
> > +	int ret;
> > +
> > +	lcd->spi_suspended = false;
> > +
> > +	if (lcd->powered_on) {
> > +		lcd->powered_on = false;
> > +		ret = td043mtea1_power_on(lcd);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend,
> > +			 td043mtea1_resume);
> > +#endif
> > +
> > +static int td043mtea1_probe(struct spi_device *spi)
> > +{
> > +	struct td043mtea1_device *lcd;
> > +	int ret;
> > +
> > +	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
> > +	if (lcd == NULL)
> > +		return -ENOMEM;
> > +
> > +	spi_set_drvdata(spi, lcd);
> > +	lcd->spi = spi;
> > +	lcd->mode = TPO_R02_MODE_800x480;
> > +	memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma));
> > +
> > +	lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
> > +	if (IS_ERR(lcd->vcc_reg)) {
> > +		dev_err(&spi->dev, "failed to get VCC regulator\n");
> > +		return PTR_ERR(lcd->vcc_reg);
> > +	}
> > +
> > +	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
> > +	if (IS_ERR(lcd->reset_gpio)) {
> > +		dev_err(&spi->dev, "failed to get reset GPIO\n");
> > +		return PTR_ERR(lcd->reset_gpio);
> > +	}
> > +
> > +	spi->bits_per_word = 16;
> > +	spi->mode = SPI_MODE_0;
> > +
> > +	ret = spi_setup(spi);
> > +	if (ret < 0) {
> > +		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = sysfs_create_group(&spi->dev.kobj, &td043mtea1_attr_group);
> > +	if (ret < 0) {
> > +		dev_err(&spi->dev, "failed to create sysfs files\n");
> > +		return ret;
> > +	}
> > +
> > +	drm_panel_init(&lcd->panel);
> > +	lcd->panel.dev = &lcd->spi->dev;
> > +	lcd->panel.funcs = &td043mtea1_funcs;
> > +
> > +	ret = drm_panel_add(&lcd->panel);
> > +	if (ret < 0) {
> > +		sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int td043mtea1_remove(struct spi_device *spi)
> > +{
> > +	struct td043mtea1_device *lcd = spi_get_drvdata(spi);
> > +
> > +	drm_panel_remove(&lcd->panel);
> > +	td043mtea1_disable(&lcd->panel);
> > +
> > +	sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id td043mtea1_of_match[] = {
> > +	{ .compatible = "tpo,td043mtea1", },
> > +	{},
> 
> { /* sentinel */ },

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...]
  2019-07-16 11:08     ` drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...] Sam Ravnborg
@ 2019-08-08 16:07       ` Laurent Pinchart
  2019-08-08 16:52         ` Sam Ravnborg
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 16:07 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Sam,

On Tue, Jul 16, 2019 at 01:08:27PM +0200, Sam Ravnborg wrote:
> Hi Laurent et all.
> 
> > +static int panel_bridge_get_modes(struct drm_bridge *bridge,
> > +				  struct drm_connector *connector)
> > +{
> > +	struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
> > +
> > +	/*
> > +	 * FIXME: drm_panel_get_modes() should take the connector as an
> > +	 * argument.
> > +	 */
> > +	return drm_panel_get_modes(panel_bridge->panel);
> > +}
> 
> I took a look at this - it seems simple:
> - Update drm_panel.get_modes() to take controller as argument, and fix

I assume you meant connector, not controller.

>   all callers. All callers already have connector available.
> - Drop drm_panel_attach(), drm_panel_detach() and update all callers.
>   In reality just drop all code around attach(), detach().
>   drm_panel_attach(), drm_panel_detach() will be noops when the
>   connector stored in drm_panel is no longer used.
> 
> The semantic difference is that we supply the connector when we call
> drm_panel_get_modes() and not at panel creation time with an drm_panel_attach().
> 
> So it should be doable without any migration from one world to the other.
> 
> If someone can say "yes it should be that simple", then I will
> give it a spin.

Looking forward to that :-)

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 10/60] drm/bridge: Add bridge driver for display connectors
  2019-07-16  9:28     ` Sam Ravnborg
@ 2019-08-08 16:41       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 16:41 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Sam,

On Tue, Jul 16, 2019 at 11:28:57AM +0200, Sam Ravnborg wrote:
> On Sun, Jul 07, 2019 at 09:18:47PM +0300, Laurent Pinchart wrote:
> > Display connectors are modelled in DT as a device node, but have so far
> > been handled manually in several bridge drivers. This resulted in
> > duplicate code in several bridge drivers, with slightly different (and
> > thus confusing) logics.
> > 
> > In order to fix this, implement a bridge driver for display connectors.
> > The driver centralises logic for the DVI, HDMI, VGAn composite and
> > S-video connectors and exposes corresponding bridge operations.
> > 
> > This driver in itself doesn't solve the issue completely, changes in
> > bridge and display controller drivers are needed to make use of the new
> > connector driver.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/bridge/Kconfig             |  11 +
> >  drivers/gpu/drm/bridge/Makefile            |   1 +
> >  drivers/gpu/drm/bridge/display-connector.c | 327 +++++++++++++++++++++
> >  3 files changed, 339 insertions(+)
> >  create mode 100644 drivers/gpu/drm/bridge/display-connector.c
> > 
> > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > index a78392e2dbb9..295a62f65ef9 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -37,6 +37,17 @@ config DRM_CDNS_DSI
> >  	  Support Cadence DPI to DSI bridge. This is an internal
> >  	  bridge and is meant to be directly embedded in a SoC.
> >  
> > +config DRM_DISPLAY_CONNECTOR
> > +	tristate "Display connector support"
> > +	depends on OF
> > +	help
> > +	  Driver for display connectors with support for DDC and hot-plug
> > +	  detection. Most display controller handle display connectors
> > +	  internally and don't need this driver, but the DRM subsystem is
> > +	  moving towards separating connector handling from display controllers
> > +	  on ARM-based platforms. Saying Y here when this driver is not needed
> > +	  will not cause any issue.
> > +
> >  config DRM_LVDS_ENCODER
> >  	tristate "Transparent parallel to LVDS encoder support"
> >  	depends on OF
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index 6ff7f2adbb0e..e5987b3aaf62 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -1,6 +1,7 @@
> >  # SPDX-License-Identifier: GPL-2.0
> >  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
> >  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> > +obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
> >  obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
> >  obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> >  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> > diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
> > new file mode 100644
> > index 000000000000..2e1e7ee89275
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/display-connector.c
> > @@ -0,0 +1,327 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > + */
> > +
> ...
> > +
> > +static void display_connector_hpd_enable(struct drm_bridge *bridge)
> > +{
> > +}
> > +
> > +static void display_connector_hpd_disable(struct drm_bridge *bridge)
> > +{
> > +}
> 
> It seems wrong that a new driver needs empty implementation of
> hpd_enable() and hpd_disable().
> I noticed the same in a later patch too.
> 
> Can we do without these empty functions?

Absolutely, I'll fix that.

> > +
> > +static const struct drm_bridge_funcs display_connector_bridge_funcs = {
> > +	.attach = display_connector_attach,
> > +	.detect = display_connector_detect,
> > +	.get_edid = display_connector_get_edid,
> > +	.hpd_enable = display_connector_hpd_enable,
> > +	.hpd_disable = display_connector_hpd_disable,
> > +};
> > +
> > +	struct display_connector *conn = arg;
> > +	struct drm_bridge *bridge = &conn->bridge;
> > +
> > +	drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static const char *display_connector_type_name(struct display_connector *conn)
> > +{
> > +	switch (conn->bridge.type) {
> > +	case DRM_MODE_CONNECTOR_Composite:
> > +		return "Composite";
> > +	case DRM_MODE_CONNECTOR_DVIA:
> > +		return "DVI-A";
> > +	case DRM_MODE_CONNECTOR_DVID:
> > +		return "DVI-D";
> > +	case DRM_MODE_CONNECTOR_DVII:
> > +		return "DVI-I";
> > +	case DRM_MODE_CONNECTOR_HDMIA:
> > +		return "HDMI-A";
> > +	case DRM_MODE_CONNECTOR_HDMIB:
> > +		return "HDMI-B";
> > +	case DRM_MODE_CONNECTOR_SVIDEO:
> > +		return "S-Video";
> > +	case DRM_MODE_CONNECTOR_VGA:
> > +		return "VGA";
> > +	default:
> > +		return "unknown";
> > +	}
> > +}
> We already have the relation DRM_MODE_CONNECTOR <=> name in drm_connector -
> see drm_connector_enum_list.
> 
> Add a small function in drm_connector.c get the name, so we do not hardcode the
> name twice?

That will be in v2.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...]
  2019-08-08 16:07       ` Laurent Pinchart
@ 2019-08-08 16:52         ` Sam Ravnborg
  2019-08-08 18:37           ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-08-08 16:52 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Laurent.

As I said in another mail, you have managed to keep me busy...

> > I took a look at this - it seems simple:
> > - Update drm_panel.get_modes() to take drm_connector as argument, and fix
> >   all callers. All callers already have connector available.
> > - Drop drm_panel_attach(), drm_panel_detach() and update all callers.
> >   In reality just drop all code around attach(), detach().
> >   drm_panel_attach(), drm_panel_detach() will be noops when the
> >   connector stored in drm_panel is no longer used.
> > 
> > The semantic difference is that we supply the connector when we call
> > drm_panel_get_modes() and not at panel creation time with an drm_panel_attach().
> > 
> > So it should be doable without any migration from one world to the other.
> > 
> > If someone can say "yes it should be that simple", then I will
> > give it a spin.
> 
> Looking forward to that :-)

Almost there....
I have all the preparation patches on dri-devel, with positive
feedback on most.

And locally I have updated all get_modes() to take drm_connector as
argument.

A few drivers access drm_panel->connector, still need to look into this.

And then for drm_panel_attach(), drm_panel_detach() - so far they are
kept but changed to take a drm_device*.

Just sharing this so you do not jump at it and duplicate the work.
It will take a little time before I can invest time in this again.
Will post patches when something is ready for review.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-08-08 14:25       ` Laurent Pinchart
@ 2019-08-08 17:36         ` Andrzej Hajda
  2019-08-08 18:50           ` Laurent Pinchart
  2019-08-14  8:18           ` Daniel Vetter
  0 siblings, 2 replies; 166+ messages in thread
From: Andrzej Hajda @ 2019-08-08 17:36 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On 08.08.2019 16:25, Laurent Pinchart wrote:
> Hi Andrzej,
>
> On Wed, Jul 17, 2019 at 08:39:47AM +0200, Andrzej Hajda wrote:
>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>> Most bridge drivers create a DRM connector to model the connector at the
>>> output of the bridge. This model is historical and has worked pretty
>>> well so far, but causes several issues:
>>>
>>> - It prevents supporting more complex display pipelines where DRM
>>> connector operations are split over multiple components. For instance a
>>> pipeline with a bridge connected to the DDC signals to read EDID data,
>>> and another one connected to the HPD signal to detect connection and
>>> disconnection, will not be possible to support through this model.
>>>
>>> - It requires every bridge driver to implement similar connector
>>> handling code, resulting in code duplication.
>>>
>>> - It assumes that a bridge will either be wired to a connector or to
>>> another bridge, but doesn't support bridges that can be used in both
>>> positions very well (although there is some ad-hoc support for this in
>>> the analogix_dp bridge driver).
>>>
>>> In order to solve these issues, ownership of the connector should be
>>> moved to the display controller driver (where it can be implemented
>>> using helpers provided by the core).
>>>
>>> Extend the bridge API to allow disabling connector creation in bridge
>>> drivers as a first step towards the new model. The new create_connector
>>> argument to the bridge .attach() operation tells the bridge driver
>>> whether to create a connector. Set the argument to true unconditionally,
>>> and modify all existing bridge drivers to return an error when connector
>>> creation is not requested as they don't support this feature yet.
>>>
>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>> ---
>>>  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
>>>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
>>>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
>>>  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
>>>  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
>>>  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
>>>  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
>>>  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
>>>  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
>>>  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
>>>  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
>>>  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
>>>  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
>>>  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
>>>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
>>>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
>>>  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
>>>  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
>>>  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
>>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
>>>  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
>>>  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
>>>  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
>>>  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
>>>  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
>>>  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
>>>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
>>>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
>>>  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
>>>  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
>>>  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
>>>  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
>>>  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
>>>  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
>>>  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
>>>  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
>>>  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
>>>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
>>>  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
>>>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
>>>  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
>>>  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
>>>  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
>>>  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
>>>  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
>>>  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
>>>  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
>>>  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
>>>  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
>>>  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
>>>  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
>>>  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
>>>  include/drm/drm_bridge.h                                 | 4 ++--
>>>  53 files changed, 140 insertions(+), 67 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
>>> index 98aac743cc26..739f2358f1d5 100644
>>> --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
>>> +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
>>> @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
>>>  		return ret;
>>>  
>>>  	/* Link drm_bridge to encoder */
>>> -	ret = drm_bridge_attach(encoder, bridge, NULL);
>>> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
>> Few suggestions:
>>
>> 1. Maybe it would be more convenient to add flags argument instead of bool:
>>
>> - code should be more readable: ret = drm_bridge_attach(encoder, bridge,
>> NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)
>>
>> - it can be easily expanded later with other flags, there at least two
>> drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.
> Please note that I think this flag should disappear once drivers get
> converted to the new model. This will however take some time. I'm not
> opposed to turning the book into a flag though. I was hoping to receive
> more review comments on this particular patch, but as that's not the
> case, I can already proceed with the change.
>
> What would the DRM_BRIDGE_FLAG_NO_CHAINING flag be used for ?


To avoid setting encoder->bridge or previous_bridge->next field to
attached bridge (last lines of drm_bridge_attach code).

This is for sure the case of exynos_dsi and vc4_dsi, but I guess it can
affect other drivers as well, probably they just use other workarounds
or have more flexible hardware.

Generally idea that order of calling
pre_enable/enable/disable/post_disable callbacks in chained
encoder/bridges is fixed is wrong IMHO, only video source component
knows in which moment it should enable its sink and if it should do
something after. And it does not work at all with sources/sinks having
more than one output/input.


>
>> 2. If the patch can be applied atomically it is OK as is, if not you can
>> use preprocessor vararg magic to support new and old syntax, sth like:
>>
>> #define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
>> __drm_bridge_attach(encoder, bridge, prev, flags)
>>
>> #define drm_bridge_attach(encoder, bridge, prev, optarg...)
>> _drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)
> Good point. I'll try to do this atomically, but if it fails I'll follow
> your suggestion.
>
>> 3. Maybe more convenient would be to just set the flags directly before
>> attachment:
>>
>>     bridge->dont_create_connector = true;
>>
>>     ret = drm_bridge_attach(encoder, bridge, NULL);
>>
>>     This way it will be still expandable, and less changes.
> Bridges that are chained would need to set the dont_create_connector
> flag of the next bridge. It would be a bit ugly, but would make this
> patch smaller. On the other hand we would need to keep the if
> (!create_connector) check in the .attach() handlers, and it would be
> easier to miss it in bridge drivers (current or new) than with an
> explicit argument to the .attach() operation. I would thus have a
> preference for the new argument to .attach(). Especially if it can help
> you with DRM_BRIDGE_FLAG_NO_CHAINING :-)

bridge->dont_chain would work as well :)

Btw I wonder if it could be possible to disallow creating connectors at all by new bridges - it would speed-up transition.


Another long term idea. Since bridges can be attached to:
- encoder,
- another bridge,
- crtc (I have one example, but I guess there could be more),
- even before crtc (image postprocessing)
And since bridge output goes to:
- another bridge,
- panel.

Wouldn't be better to create drm_source and drm_sink (do not respond with xkcd picture :) ):
- drm_source will be embedded in source device context,
- drm_sink will be embedded in sink device context.
We could make then transitions of bridges to drm_sink with drm_source embeded in its context, and panels to drm_sink.
This way we could drop these crazy constructs:
- if sink is panel then do sth, elsif is bridge then do sth_else,
- if src is bridge then do sth, elsif is encoder ... elsif ....
- helpers of_find_panel_or_bridge,
- drm_panel_bridge,
Also we could implement easily multi input/output bridges/panels/crtcs whatever.
And hpd callbacks you have proposed in another patch would fit better to drm_source.ops.
...


Regards
Andrzej


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-11  7:35       ` Daniel Vetter
  2019-07-11 12:41         ` Andrzej Hajda
@ 2019-08-08 18:19         ` Laurent Pinchart
  2019-08-08 18:36           ` Laurent Pinchart
  2019-08-14 12:43           ` Daniel Vetter
  1 sibling, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 18:19 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hello Daniel and Andrzej,

On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > Hi Laurent,
> > 
> > I like the approach, current practice when almost every bridge should
> > optionally implement connector, or alternatively downstream bridge or
> > panel is very painful.
> 
> Yeah I think this looks mostly reasonable. Some api design comments on top
> of Andrzej', with the fair warning that I didn't bother to read up on how
> it's all used in the end. I probably should go and do that, at least to
> get a feeling for what your hpd_cb usually does.
> 
> > More comments inlined.
> > 
> > On 07.07.2019 20:18, Laurent Pinchart wrote:
> > > To support implementation of DRM connectors on top of DRM bridges
> > > instead of by bridges, the drm_bridge needs to expose new operations and
> > > data:
> > >
> > > - Output detection, hot-plug notification, mode retrieval and EDID
> > >   retrieval operations
> > > - Bitmask of supported operations
> > 
> > 
> > Why do we need these bitmask at all? Why cannot we rely on presence of
> > operation's callback?
> 
> Yeah also not a huge fan of these bitmasks. Smells like
> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> add, generally good excuse to not have to think through the design between
> different parts of drivers - "just" add another flag.

The reason is that a bridge may support an operation (as in implemented
in the bridge hardware), but that operation may not be supported on a
particular board. For instance an HDMI encoder may support reading EDID
when the DDC lines are connected to the encoder, but a board may connect
the DDC lines to an I2C port of the SoC. We thus need to decouple
if a particular instance of the device supports the operation (exposed
by the ops flags) from the function pointers.

We could of course allocate the drm_bridge_funcs structure dynamically
for each bridge instance, and fill it with function pointers manually,
leaving the unused ops always NULL, but that would require making the
structure writable, which is considered a security issue. That's why I
decided to keep the drm_bridge_funcs structure as a global static const
structure, and add an ops bitmask.

> > > - Bridge output type
> > >
> > > Add and document these.
> > >
> > > Three new bridge helper functions are also added to handle hot plug
> > > notification in a way that is as transparent as possible for the
> > > bridges.
> > 
> > Documentation of new opses does not explain how it should cooperate with
> > bridge chaining, I suppose they should be chained explicitly, am I
> > right? More comments about it later.

No, the whole point is that they should not be chained at all. A bridge
does not have to propagate, for instance, .get_edid() to the next
bridge. That's one of the core design principles in this series, I want
to keep the bridges as simple as possible, and move the complexity of
the boilerplate code that is currently copied all around to helpers. See
patch "drm: Add helper to create a connector for a chain of bridges" for
more information about how this is used, with a helper that delegates
the connector operations to the correct bridge in the chain based on the
ops reported by each bridge.

> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > >  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >  2 files changed, 261 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > > index 519577f363e3..3c2a255df7af 100644
> > > --- a/drivers/gpu/drm/drm_bridge.c
> > > +++ b/drivers/gpu/drm/drm_bridge.c
> > > @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >   */
> > >  void drm_bridge_add(struct drm_bridge *bridge)
> > >  {
> > > +	mutex_init(&bridge->hpd_mutex);
> > > +
> > >  	mutex_lock(&bridge_lock);
> > >  	list_add_tail(&bridge->list, &bridge_list);
> > >  	mutex_unlock(&bridge_lock);
> > > @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >  	mutex_lock(&bridge_lock);
> > >  	list_del_init(&bridge->list);
> > >  	mutex_unlock(&bridge_lock);
> > > +
> > > +	mutex_destroy(&bridge->hpd_mutex);
> > >  }
> > >  EXPORT_SYMBOL(drm_bridge_remove);
> > >  
> > > @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >  
> > > +/**
> > > + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > > + * @bridge: bridge control structure
> > > + * @cb: hot-plug detection callback
> > > + * @data: data to be passed to the hot-plug detection callback
> > > + *
> > > + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > > + * hot plug notification callback. From now on the @cb will be called with
> > > + * @data when an output status change is detected by the bridge, until hot plug
> > > + * notification gets disabled with drm_bridge_hpd_disable().
> > > + *
> > > + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > > + * bridge->ops. This function shall not be called when the flag is not set.
> > > + *
> > > + * Only one hot plug detection callback can be registered at a time, it is an
> > > + * error to call this function when hot plug detection is already enabled for
> > > + * the bridge.
> > > + */
> > 
> > To simplify architecture maybe would be better to enable hpd just on
> > bridge attach:
> > 
> > bridge->hpd_cb = cb;
> > 
> > bridge->hpd_data = data;
> > 
> > ret = drm_bridge_attach(...);
> 
> Yeah I like this more. The other problem here is, what if you need more
> than 1 callback registers on the same bridge hdp signal?

That's why I decided to hide hide HPD through helpers,
drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
side, and drm_bridge_hpd_notify() on the event reporter side. While the
current implementation is limited to a single listener, only the helpers
would need to be changed to extend that to multiple listeners.

Note that the .hpd_enable() and .hpd_disable() operations also allow the
bridge to disable HPD detection when not used. Doing so keeps the bridge
simple, it only needs to care about reporting HPD events when they're
enabled, without caring who (if anyone) is listening, and gets clear
instructions on whether to enable or disable the HPD hardware (in case
it can be disabled).

> > This way we could avoid adding new callbacks hpd_(enable|disable)
> > without big sacrifices.
> > 
> > One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > notifies about sink status change, how it translates to this cb?

This is something this series doesn't implement. I don't think it would
be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
here. If you can elaborate on what would be needed, I can implement
that.

> > > +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > > +			   void (*cb)(void *data,
> > > +				      enum drm_connector_status status),
> > > +			   void *data)
> > > +{
> > > +	if (!bridge || !bridge->funcs->hpd_enable)
> > > +		return;
> > > +
> > > +	mutex_lock(&bridge->hpd_mutex);
> > > +
> > > +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > > +		goto unlock;
> > > +
> > > +	bridge->hpd_cb = cb;
> > > +	bridge->hpd_data = data;
> > > +
> > > +	bridge->funcs->hpd_enable(bridge);
> > > +
> > > +unlock:
> > > +	mutex_unlock(&bridge->hpd_mutex);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > > +
> > > +/**
> > > + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > > + * @bridge: bridge control structure
> > > + *
> > > + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > > + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > > + * function returns the callback will not be called by the bridge when an
> > > + * output status change occurs.
> > > + *
> > > + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > > + * bridge->ops. This function shall not be called when the flag is not set.
> > > + */
> > > +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > > +{
> > > +	if (!bridge || !bridge->funcs->hpd_disable)
> > > +		return;
> > > +
> > > +	mutex_lock(&bridge->hpd_mutex);
> > > +	bridge->funcs->hpd_disable(bridge);
> > > +
> > > +	bridge->hpd_cb = NULL;
> > > +	bridge->hpd_data = NULL;
> > > +	mutex_unlock(&bridge->hpd_mutex);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > > +
> > > +/**
> > > + * drm_bridge_hpd_notify - notify hot plug detection events
> > > + * @bridge: bridge control structure
> > > + * @status: output connection status
> > > + *
> > > + * Bridge drivers shall call this function to report hot plug events when they
> > > + * detect a change in the output status, when hot plug detection has been
> > > + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > > + *
> > > + * This function shall be called in a context that can sleep.
> > > + */
> > > +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > > +			   enum drm_connector_status status)
> > > +{
> > > +	mutex_lock(&bridge->hpd_mutex);
> > > +	if (bridge->hpd_cb)
> > > +		bridge->hpd_cb(bridge->hpd_data, status);
> 
> So this isn't quite what I had in mind. Instead something like this:
> 
> 	/* iterates over all bridges in the chain containing @bridge */
> 	for_each_bridge(tmp_bridge, bridge) {
> 		if (tmp_bridge == bridge)
> 			continue;
> 		if (bridge->hpd_notify);
> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> 	}
> 
> 	encoder = encoder_for_bridge(bridge);
> 	if (encoder->helper_private->bridge_hpd_notify)
> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> 
> 	dev = bridge->dev
> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> 
> No register callback needed, no locking needed, everyone gets exactly the
> hpd they want/need.

I'll reply to this further down the mail thread, to address additional
comments.

> > > +	mutex_unlock(&bridge->hpd_mutex);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > > +
> > >  #ifdef CONFIG_OF
> > >  /**
> > >   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > > diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > > index 08dc15f93ded..b9445aa5b1ef 100644
> > > --- a/include/drm/drm_bridge.h
> > > +++ b/include/drm/drm_bridge.h
> > > @@ -23,8 +23,9 @@
> > >  #ifndef __DRM_BRIDGE_H__
> > >  #define __DRM_BRIDGE_H__
> > >  
> > > -#include <linux/list.h>
> > >  #include <linux/ctype.h>
> > > +#include <linux/list.h>
> > > +#include <linux/mutex.h>
> > >  #include <drm/drm_mode_object.h>
> > >  #include <drm/drm_modes.h>
> > >  
> > > @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >  	 */
> > >  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >  				    struct drm_atomic_state *state);
> > > +
> > > +	/**
> > > +	 * @detect:
> > > +	 *
> > > +	 * Check if anything is attached to the bridge output.
> > > +	 *
> > > +	 * This callback is optional, if not implemented the bridge will be
> > > +	 * considered as always having a component attached to its output.
> > > +	 * Bridges that implement this callback shall set the
> > > +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > > +	 *
> > > +	 * RETURNS:
> > > +	 *
> > > +	 * drm_connector_status indicating the bridge output status.
> > > +	 */
> > > +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > > +
> > > +	/**
> > > +	 * @get_modes:
> > > +	 *
> > > +	 * Fill all modes currently valid for the sink into the &drm_connector
> > > +	 * with drm_mode_probed_add().
> > > +	 *
> > > +	 * The @get_modes callback is mostly intended to support non-probable
> > > +	 * displays such as many fixed panels. Bridges that support reading
> > > +	 * EDID shall leave @get_modes unimplemented and implement the
> > > +	 * &drm_bridge_funcs->get_edid callback instead.
> > > +	 *
> > > +	 * This callback is optional. Bridges that implement it shall set the
> > > +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > > +	 *
> > > +	 * RETURNS:
> > > +	 *
> > > +	 * The number of modes added by calling drm_mode_probed_add().
> > > +	 */
> > > +	int (*get_modes)(struct drm_bridge *bridge,
> > > +			 struct drm_connector *connector);
> > > +
> > > +	/**
> > > +	 * @get_edid:
> > > +	 *
> > > +	 * Read and parse the EDID data of the connected display.
> > > +	 *
> > > +	 * The @get_edid callback is the preferred way of reporting mode
> > > +	 * information for a display connected to the bridge output. Bridges
> > > +	 * that support readind EDID shall implement this callback and leave
> > > +	 * the @get_modes callback unimplemented.
> > > +	 *
> > > +	 * The caller of this operation shall first verify the output
> > > +	 * connection status and refrain from reading EDID from a disconnected
> > > +	 * output.
> > > +	 *
> > > +	 * This callback is optional. Bridges that implement it shall set the
> > > +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > > +	 *
> > > +	 * RETURNS:
> > > +	 *
> > > +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > > +	 * success, or NULL otherwise. The caller is responsible for freeing
> > > +	 * the returned edid structure with kfree().
> > > +	 */
> > > +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > > +				 struct drm_connector *connector);
> > 
> > It overlaps with get_modes, I guess presence of one ops should disallow
> > presence of another one?
> > 
> > I am not really convinced we need this op at all, cannot we just assign
> > some helper function to .get_modes cb, which will do the same?
> 
> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> case, and require that if it has an edid it must fill out connector->info
> and connector->edid correctly.

I think that's doable, I'll have a look.

> Btw if a hpd happens, who's responible for making sure the edid/mode list
> in the connector is up-to-date? With your current callback design that's
> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> should guarantee that it'll first walk the connectors to update status and
> edid/mode list for the final drm_connector. And then instead of just
> passing the simple "status", it'll pass the connector, with everything
> correctly updated.
> 
> Otherwise everyone interested in that hpd signal will go and re-fetch the
> edid, which is not so awesome :-)

With the current design there's a single listener, so it's not a big
deal :-) Furthermore, the listener is the helper that creates a
connector on top of a chain of bridges, so it's a pretty good place to
handle this. See the call to drm_kms_helper_hotplug_event() in
drm_bridge_connector_hpd_cb().

I'm all for reworking HPD and mode fetching, but I think it's a bit too
big of a requirement as a prerequisite for this series (or as part of
this series). We have hardware that can report HPD with various level of
details (from "something happened on a connector" to "this particular
event happened on this particular connector"), and we channel that
through helpers such as drm_kms_helper_hotplug_event() that lose the
details and go through a heavy mechanism to refetch everything. I
understand this is needed in many cases, but I think there's room for
improvement. This series, in my opinion, doesn't go in the wrong
direction in that regard, as it eventually calls
drm_kms_helper_hotplug_event(), so I think improvements would make sense
on top of it. I'm even willing to work on this, provided I get feedback
on what is desired.

> > > +	/**
> > > +	 * @lost_hotplug:
> > > +	 *
> > > +	 * Notify the bridge of display disconnection.
> > > +	 *
> > > +	 * This callback is optional, it may be implemented by bridges that
> > > +	 * need to be notified of display disconnection for internal reasons.
> > > +	 * One use case is to reset the internal state of CEC controllers for
> > > +	 * HDMI bridges.
> > > +	 */
> > > +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > > +
> > > +	/**
> > > +	 * @hpd_enable:
> > > +	 *
> > > +	 * Enable hot plug detection. From now on the bridge shall call
> > > +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > > +	 * connection status, until hot plug detection gets disabled with
> > > +	 * @hpd_disable.
> > > +	 *
> > > +	 * This callback is optional and shall only be implemented by bridges
> > > +	 * that support hot-plug notification without polling. Bridges that
> > > +	 * implement it shall also implement the @hpd_disable callback and set
> > > +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > > +	 */
> > > +	void (*hpd_enable)(struct drm_bridge *bridge);
> > > +
> > > +	/**
> > > +	 * @hpd_disable:
> > > +	 *
> > > +	 * Disable hot plug detection. Once this function returns the bridge
> > > +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > > +	 * connection status occurs.
> > > +	 *
> > > +	 * This callback is optional and shall only be implemented by bridges
> > > +	 * that support hot-plug notification without polling. Bridges that
> > > +	 * implement it shall also implement the @hpd_enable callback and set
> > > +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > > +	 */
> > > +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >  };
> > >  
> > >  /**
> > > @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >  	bool dual_link;
> > >  };
> > >  
> > > +/**
> > > + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > > + */
> > > +enum drm_bridge_ops {
> > > +	/**
> > > +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > > +	 * its output. Bridges that set this flag shall implement the
> > > +	 * &drm_bridge_funcs->detect callback.
> > > +	 */
> > > +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > > +	/**
> > > +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > > +	 * connected to its output. Bridges that set this flag shall implement
> > > +	 * the &drm_bridge_funcs->get_edid callback.
> > > +	 */
> > > +	DRM_BRIDGE_OP_EDID = BIT(1),
> > > +	/**
> > > +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > > +	 * without requiring polling. Bridges that set this flag shall
> > > +	 * implement the &drm_bridge_funcs->hpd_enable and
> > > +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > > +	 */
> > > +	DRM_BRIDGE_OP_HPD = BIT(2),
> > > +	/**
> > > +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > > +	 * by the display at its output. This does not include readind EDID
> > > +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > > +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > > +	 */
> > > +	DRM_BRIDGE_OP_MODES = BIT(3),
> > > +};
> > > +
> > >  /**
> > >   * struct drm_bridge - central DRM bridge control structure
> > >   */
> > > @@ -398,6 +535,29 @@ struct drm_bridge {
> > >  	const struct drm_bridge_funcs *funcs;
> > >  	/** @driver_private: pointer to the bridge driver's internal context */
> > >  	void *driver_private;
> > > +	/** @ops: bitmask of operations supported by the bridge */
> > > +	enum drm_bridge_ops ops;
> > > +	/**
> > > +	 * @type: Type of the connection at the bridge output
> > > +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > > +	 * identifies the type of connected display.
> > > +	 */
> > > +	int type;
> > > +	/** private: */
> > > +	/**
> > > +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > > +	 */
> > > +	struct mutex hpd_mutex;
> > > +	/**
> > > +	 * @hpd_cb: Hot plug detection callback, registered with
> > > +	 * drm_bridge_hpd_enable().
> > > +	 */
> > > +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > > +	/**
> > > +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > > +	 * @hpd_cb.
> > > +	 */
> > > +	void *hpd_data;
> > >  };
> > >  
> > >  void drm_bridge_add(struct drm_bridge *bridge);
> > > @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >  			      struct drm_atomic_state *state);
> > >  
> > > +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > > +			   void (*cb)(void *data,
> > > +				      enum drm_connector_status status),
> > > +			   void *data);
> > > +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > > +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > > +			   enum drm_connector_status status);
> > > +
> > >  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-08 18:19         ` Laurent Pinchart
@ 2019-08-08 18:36           ` Laurent Pinchart
  2019-08-14 13:03             ` Daniel Vetter
  2019-08-14 12:43           ` Daniel Vetter
  1 sibling, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 18:36 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> > On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >> Hi Laurent,
> >> 
> >> I like the approach, current practice when almost every bridge should
> >> optionally implement connector, or alternatively downstream bridge or
> >> panel is very painful.
> > 
> > Yeah I think this looks mostly reasonable. Some api design comments on top
> > of Andrzej', with the fair warning that I didn't bother to read up on how
> > it's all used in the end. I probably should go and do that, at least to
> > get a feeling for what your hpd_cb usually does.
> > 
> >> More comments inlined.
> >> 
> >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>> To support implementation of DRM connectors on top of DRM bridges
> >>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>> data:
> >>>
> >>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>   retrieval operations
> >>> - Bitmask of supported operations
> >> 
> >> 
> >> Why do we need these bitmask at all? Why cannot we rely on presence of
> >> operation's callback?
> > 
> > Yeah also not a huge fan of these bitmasks. Smells like
> > DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > add, generally good excuse to not have to think through the design between
> > different parts of drivers - "just" add another flag.
> 
> The reason is that a bridge may support an operation (as in implemented
> in the bridge hardware), but that operation may not be supported on a
> particular board. For instance an HDMI encoder may support reading EDID
> when the DDC lines are connected to the encoder, but a board may connect
> the DDC lines to an I2C port of the SoC. We thus need to decouple
> if a particular instance of the device supports the operation (exposed
> by the ops flags) from the function pointers.
> 
> We could of course allocate the drm_bridge_funcs structure dynamically
> for each bridge instance, and fill it with function pointers manually,
> leaving the unused ops always NULL, but that would require making the
> structure writable, which is considered a security issue. That's why I
> decided to keep the drm_bridge_funcs structure as a global static const
> structure, and add an ops bitmask.
> 
> >>> - Bridge output type
> >>>
> >>> Add and document these.
> >>>
> >>> Three new bridge helper functions are also added to handle hot plug
> >>> notification in a way that is as transparent as possible for the
> >>> bridges.
> >> 
> >> Documentation of new opses does not explain how it should cooperate with
> >> bridge chaining, I suppose they should be chained explicitly, am I
> >> right? More comments about it later.
> 
> No, the whole point is that they should not be chained at all. A bridge
> does not have to propagate, for instance, .get_edid() to the next
> bridge. That's one of the core design principles in this series, I want
> to keep the bridges as simple as possible, and move the complexity of
> the boilerplate code that is currently copied all around to helpers. See
> patch "drm: Add helper to create a connector for a chain of bridges" for
> more information about how this is used, with a helper that delegates
> the connector operations to the correct bridge in the chain based on the
> ops reported by each bridge.
> 
> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> ---
> >>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>> index 519577f363e3..3c2a255df7af 100644
> >>> --- a/drivers/gpu/drm/drm_bridge.c
> >>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>   */
> >>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>  {
> >>> +	mutex_init(&bridge->hpd_mutex);
> >>> +
> >>>  	mutex_lock(&bridge_lock);
> >>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>  	mutex_unlock(&bridge_lock);
> >>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>  	mutex_lock(&bridge_lock);
> >>>  	list_del_init(&bridge->list);
> >>>  	mutex_unlock(&bridge_lock);
> >>> +
> >>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>  }
> >>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>  
> >>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>  }
> >>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>  
> >>> +/**
> >>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>> + * @bridge: bridge control structure
> >>> + * @cb: hot-plug detection callback
> >>> + * @data: data to be passed to the hot-plug detection callback
> >>> + *
> >>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>> + * hot plug notification callback. From now on the @cb will be called with
> >>> + * @data when an output status change is detected by the bridge, until hot plug
> >>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>> + *
> >>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>> + *
> >>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>> + * error to call this function when hot plug detection is already enabled for
> >>> + * the bridge.
> >>> + */
> >> 
> >> To simplify architecture maybe would be better to enable hpd just on
> >> bridge attach:
> >> 
> >> bridge->hpd_cb = cb;
> >> 
> >> bridge->hpd_data = data;
> >> 
> >> ret = drm_bridge_attach(...);
> > 
> > Yeah I like this more. The other problem here is, what if you need more
> > than 1 callback registers on the same bridge hdp signal?
> 
> That's why I decided to hide hide HPD through helpers,
> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> current implementation is limited to a single listener, only the helpers
> would need to be changed to extend that to multiple listeners.
> 
> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> bridge to disable HPD detection when not used. Doing so keeps the bridge
> simple, it only needs to care about reporting HPD events when they're
> enabled, without caring who (if anyone) is listening, and gets clear
> instructions on whether to enable or disable the HPD hardware (in case
> it can be disabled).
> 
> >> This way we could avoid adding new callbacks hpd_(enable|disable)
> >> without big sacrifices.
> >> 
> >> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >> notifies about sink status change, how it translates to this cb?
> 
> This is something this series doesn't implement. I don't think it would
> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> here. If you can elaborate on what would be needed, I can implement
> that.
> 
> >>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>> +			   void (*cb)(void *data,
> >>> +				      enum drm_connector_status status),
> >>> +			   void *data)
> >>> +{
> >>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>> +		return;
> >>> +
> >>> +	mutex_lock(&bridge->hpd_mutex);
> >>> +
> >>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>> +		goto unlock;
> >>> +
> >>> +	bridge->hpd_cb = cb;
> >>> +	bridge->hpd_data = data;
> >>> +
> >>> +	bridge->funcs->hpd_enable(bridge);
> >>> +
> >>> +unlock:
> >>> +	mutex_unlock(&bridge->hpd_mutex);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>> +
> >>> +/**
> >>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>> + * @bridge: bridge control structure
> >>> + *
> >>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>> + * function returns the callback will not be called by the bridge when an
> >>> + * output status change occurs.
> >>> + *
> >>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>> + */
> >>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>> +{
> >>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>> +		return;
> >>> +
> >>> +	mutex_lock(&bridge->hpd_mutex);
> >>> +	bridge->funcs->hpd_disable(bridge);
> >>> +
> >>> +	bridge->hpd_cb = NULL;
> >>> +	bridge->hpd_data = NULL;
> >>> +	mutex_unlock(&bridge->hpd_mutex);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>> +
> >>> +/**
> >>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>> + * @bridge: bridge control structure
> >>> + * @status: output connection status
> >>> + *
> >>> + * Bridge drivers shall call this function to report hot plug events when they
> >>> + * detect a change in the output status, when hot plug detection has been
> >>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>> + *
> >>> + * This function shall be called in a context that can sleep.
> >>> + */
> >>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>> +			   enum drm_connector_status status)
> >>> +{
> >>> +	mutex_lock(&bridge->hpd_mutex);
> >>> +	if (bridge->hpd_cb)
> >>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > 
> > So this isn't quite what I had in mind. Instead something like this:
> > 
> > 	/* iterates over all bridges in the chain containing @bridge */
> > 	for_each_bridge(tmp_bridge, bridge) {
> > 		if (tmp_bridge == bridge)
> > 			continue;
> > 		if (bridge->hpd_notify);
> > 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > 	}
> > 
> > 	encoder = encoder_for_bridge(bridge);
> > 	if (encoder->helper_private->bridge_hpd_notify)
> > 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > 
> > 	dev = bridge->dev
> > 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > 
> > No register callback needed, no locking needed, everyone gets exactly the
> > hpd they want/need.
> 
> I'll reply to this further down the mail thread, to address additional
> comments.
> 
> >>> +	mutex_unlock(&bridge->hpd_mutex);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>> +
> >>>  #ifdef CONFIG_OF
> >>>  /**
> >>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>> --- a/include/drm/drm_bridge.h
> >>> +++ b/include/drm/drm_bridge.h
> >>> @@ -23,8 +23,9 @@
> >>>  #ifndef __DRM_BRIDGE_H__
> >>>  #define __DRM_BRIDGE_H__
> >>>  
> >>> -#include <linux/list.h>
> >>>  #include <linux/ctype.h>
> >>> +#include <linux/list.h>
> >>> +#include <linux/mutex.h>
> >>>  #include <drm/drm_mode_object.h>
> >>>  #include <drm/drm_modes.h>
> >>>  
> >>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>  	 */
> >>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>  				    struct drm_atomic_state *state);
> >>> +
> >>> +	/**
> >>> +	 * @detect:
> >>> +	 *
> >>> +	 * Check if anything is attached to the bridge output.
> >>> +	 *
> >>> +	 * This callback is optional, if not implemented the bridge will be
> >>> +	 * considered as always having a component attached to its output.
> >>> +	 * Bridges that implement this callback shall set the
> >>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>> +	 *
> >>> +	 * RETURNS:
> >>> +	 *
> >>> +	 * drm_connector_status indicating the bridge output status.
> >>> +	 */
> >>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>> +
> >>> +	/**
> >>> +	 * @get_modes:
> >>> +	 *
> >>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>> +	 * with drm_mode_probed_add().
> >>> +	 *
> >>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>> +	 * displays such as many fixed panels. Bridges that support reading
> >>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>> +	 *
> >>> +	 * This callback is optional. Bridges that implement it shall set the
> >>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>> +	 *
> >>> +	 * RETURNS:
> >>> +	 *
> >>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>> +	 */
> >>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>> +			 struct drm_connector *connector);
> >>> +
> >>> +	/**
> >>> +	 * @get_edid:
> >>> +	 *
> >>> +	 * Read and parse the EDID data of the connected display.
> >>> +	 *
> >>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>> +	 * information for a display connected to the bridge output. Bridges
> >>> +	 * that support readind EDID shall implement this callback and leave
> >>> +	 * the @get_modes callback unimplemented.
> >>> +	 *
> >>> +	 * The caller of this operation shall first verify the output
> >>> +	 * connection status and refrain from reading EDID from a disconnected
> >>> +	 * output.
> >>> +	 *
> >>> +	 * This callback is optional. Bridges that implement it shall set the
> >>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>> +	 *
> >>> +	 * RETURNS:
> >>> +	 *
> >>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>> +	 * the returned edid structure with kfree().
> >>> +	 */
> >>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>> +				 struct drm_connector *connector);
> >> 
> >> It overlaps with get_modes, I guess presence of one ops should disallow
> >> presence of another one?
> >> 
> >> I am not really convinced we need this op at all, cannot we just assign
> >> some helper function to .get_modes cb, which will do the same?
> > 
> > Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > case, and require that if it has an edid it must fill out connector->info
> > and connector->edid correctly.
> 
> I think that's doable, I'll have a look.

So I had a look, and while this is doable, it would essentially mean
that all bridges that retrieve modes from EDID would have to roll out
their own version of the following code:

static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
					       struct drm_bridge *bridge)
{
	enum drm_connector_status status;
	struct edid *edid;
	int n;

	status = drm_bridge_connector_detect(connector, false);
	if (status != connector_status_connected)
		goto no_edid;

	edid = bridge->funcs->get_edid(bridge, connector);
	if (!edid || !drm_edid_is_valid(edid)) {
		kfree(edid);
		goto no_edid;
	}

	drm_connector_update_edid_property(connector, edid);
	n = drm_add_edid_modes(connector, edid);

	kfree(edid);
	return n;

no_edid:
	drm_connector_update_edid_property(connector, NULL);
	return 0;
}

Is this desired ?

> > Btw if a hpd happens, who's responible for making sure the edid/mode list
> > in the connector is up-to-date? With your current callback design that's
> > up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > should guarantee that it'll first walk the connectors to update status and
> > edid/mode list for the final drm_connector. And then instead of just
> > passing the simple "status", it'll pass the connector, with everything
> > correctly updated.
> > 
> > Otherwise everyone interested in that hpd signal will go and re-fetch the
> > edid, which is not so awesome :-)
> 
> With the current design there's a single listener, so it's not a big
> deal :-) Furthermore, the listener is the helper that creates a
> connector on top of a chain of bridges, so it's a pretty good place to
> handle this. See the call to drm_kms_helper_hotplug_event() in
> drm_bridge_connector_hpd_cb().
> 
> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> big of a requirement as a prerequisite for this series (or as part of
> this series). We have hardware that can report HPD with various level of
> details (from "something happened on a connector" to "this particular
> event happened on this particular connector"), and we channel that
> through helpers such as drm_kms_helper_hotplug_event() that lose the
> details and go through a heavy mechanism to refetch everything. I
> understand this is needed in many cases, but I think there's room for
> improvement. This series, in my opinion, doesn't go in the wrong
> direction in that regard, as it eventually calls
> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> on top of it. I'm even willing to work on this, provided I get feedback
> on what is desired.
> 
> >>> +	/**
> >>> +	 * @lost_hotplug:
> >>> +	 *
> >>> +	 * Notify the bridge of display disconnection.
> >>> +	 *
> >>> +	 * This callback is optional, it may be implemented by bridges that
> >>> +	 * need to be notified of display disconnection for internal reasons.
> >>> +	 * One use case is to reset the internal state of CEC controllers for
> >>> +	 * HDMI bridges.
> >>> +	 */
> >>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>> +
> >>> +	/**
> >>> +	 * @hpd_enable:
> >>> +	 *
> >>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>> +	 * connection status, until hot plug detection gets disabled with
> >>> +	 * @hpd_disable.
> >>> +	 *
> >>> +	 * This callback is optional and shall only be implemented by bridges
> >>> +	 * that support hot-plug notification without polling. Bridges that
> >>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>> +	 */
> >>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>> +
> >>> +	/**
> >>> +	 * @hpd_disable:
> >>> +	 *
> >>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>> +	 * connection status occurs.
> >>> +	 *
> >>> +	 * This callback is optional and shall only be implemented by bridges
> >>> +	 * that support hot-plug notification without polling. Bridges that
> >>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>> +	 */
> >>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>  };
> >>>  
> >>>  /**
> >>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>  	bool dual_link;
> >>>  };
> >>>  
> >>> +/**
> >>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>> + */
> >>> +enum drm_bridge_ops {
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>> +	 * its output. Bridges that set this flag shall implement the
> >>> +	 * &drm_bridge_funcs->detect callback.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>> +	 * connected to its output. Bridges that set this flag shall implement
> >>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>> +	 * without requiring polling. Bridges that set this flag shall
> >>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>> +	/**
> >>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>> +	 * by the display at its output. This does not include readind EDID
> >>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>> +	 */
> >>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>> +};
> >>> +
> >>>  /**
> >>>   * struct drm_bridge - central DRM bridge control structure
> >>>   */
> >>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>  	const struct drm_bridge_funcs *funcs;
> >>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>  	void *driver_private;
> >>> +	/** @ops: bitmask of operations supported by the bridge */
> >>> +	enum drm_bridge_ops ops;
> >>> +	/**
> >>> +	 * @type: Type of the connection at the bridge output
> >>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>> +	 * identifies the type of connected display.
> >>> +	 */
> >>> +	int type;
> >>> +	/** private: */
> >>> +	/**
> >>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>> +	 */
> >>> +	struct mutex hpd_mutex;
> >>> +	/**
> >>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>> +	 * drm_bridge_hpd_enable().
> >>> +	 */
> >>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>> +	/**
> >>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>> +	 * @hpd_cb.
> >>> +	 */
> >>> +	void *hpd_data;
> >>>  };
> >>>  
> >>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>  			      struct drm_atomic_state *state);
> >>>  
> >>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>> +			   void (*cb)(void *data,
> >>> +				      enum drm_connector_status status),
> >>> +			   void *data);
> >>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>> +			   enum drm_connector_status status);
> >>> +
> >>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...]
  2019-08-08 16:52         ` Sam Ravnborg
@ 2019-08-08 18:37           ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 18:37 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Sam,

On Thu, Aug 08, 2019 at 06:52:53PM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> As I said in another mail, you have managed to keep me busy...
> 
> > > I took a look at this - it seems simple:
> > > - Update drm_panel.get_modes() to take drm_connector as argument, and fix
> > >   all callers. All callers already have connector available.
> > > - Drop drm_panel_attach(), drm_panel_detach() and update all callers.
> > >   In reality just drop all code around attach(), detach().
> > >   drm_panel_attach(), drm_panel_detach() will be noops when the
> > >   connector stored in drm_panel is no longer used.
> > > 
> > > The semantic difference is that we supply the connector when we call
> > > drm_panel_get_modes() and not at panel creation time with an drm_panel_attach().
> > > 
> > > So it should be doable without any migration from one world to the other.
> > > 
> > > If someone can say "yes it should be that simple", then I will
> > > give it a spin.
> > 
> > Looking forward to that :-)
> 
> Almost there....
> I have all the preparation patches on dri-devel, with positive
> feedback on most.
> 
> And locally I have updated all get_modes() to take drm_connector as
> argument.
> 
> A few drivers access drm_panel->connector, still need to look into this.
> 
> And then for drm_panel_attach(), drm_panel_detach() - so far they are
> kept but changed to take a drm_device*.
> 
> Just sharing this so you do not jump at it and duplicate the work.
> It will take a little time before I can invest time in this again.
> Will post patches when something is ready for review.

Thanks for the update. Take your time, this isn't blocking me.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-08-08 17:36         ` Andrzej Hajda
@ 2019-08-08 18:50           ` Laurent Pinchart
  2019-08-14  8:18           ` Daniel Vetter
  1 sibling, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 18:50 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Thu, Aug 08, 2019 at 07:36:49PM +0200, Andrzej Hajda wrote:
> On 08.08.2019 16:25, Laurent Pinchart wrote:
> > On Wed, Jul 17, 2019 at 08:39:47AM +0200, Andrzej Hajda wrote:
> >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>> Most bridge drivers create a DRM connector to model the connector at the
> >>> output of the bridge. This model is historical and has worked pretty
> >>> well so far, but causes several issues:
> >>>
> >>> - It prevents supporting more complex display pipelines where DRM
> >>> connector operations are split over multiple components. For instance a
> >>> pipeline with a bridge connected to the DDC signals to read EDID data,
> >>> and another one connected to the HPD signal to detect connection and
> >>> disconnection, will not be possible to support through this model.
> >>>
> >>> - It requires every bridge driver to implement similar connector
> >>> handling code, resulting in code duplication.
> >>>
> >>> - It assumes that a bridge will either be wired to a connector or to
> >>> another bridge, but doesn't support bridges that can be used in both
> >>> positions very well (although there is some ad-hoc support for this in
> >>> the analogix_dp bridge driver).
> >>>
> >>> In order to solve these issues, ownership of the connector should be
> >>> moved to the display controller driver (where it can be implemented
> >>> using helpers provided by the core).
> >>>
> >>> Extend the bridge API to allow disabling connector creation in bridge
> >>> drivers as a first step towards the new model. The new create_connector
> >>> argument to the bridge .attach() operation tells the bridge driver
> >>> whether to create a connector. Set the argument to true unconditionally,
> >>> and modify all existing bridge drivers to return an error when connector
> >>> creation is not requested as they don't support this feature yet.
> >>>
> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> ---
> >>>  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
> >>>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
> >>>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
> >>>  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
> >>>  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
> >>>  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
> >>>  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
> >>>  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
> >>>  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
> >>>  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
> >>>  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
> >>>  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
> >>>  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
> >>>  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
> >>>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
> >>>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
> >>>  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
> >>>  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
> >>>  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
> >>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
> >>>  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
> >>>  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
> >>>  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
> >>>  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
> >>>  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
> >>>  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
> >>>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
> >>>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
> >>>  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
> >>>  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
> >>>  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
> >>>  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
> >>>  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
> >>>  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
> >>>  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
> >>>  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
> >>>  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
> >>>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
> >>>  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
> >>>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
> >>>  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
> >>>  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
> >>>  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
> >>>  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
> >>>  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
> >>>  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
> >>>  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
> >>>  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
> >>>  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
> >>>  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
> >>>  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
> >>>  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
> >>>  include/drm/drm_bridge.h                                 | 4 ++--
> >>>  53 files changed, 140 insertions(+), 67 deletions(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> >>> index 98aac743cc26..739f2358f1d5 100644
> >>> --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
> >>> +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> >>> @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
> >>>  		return ret;
> >>>  
> >>>  	/* Link drm_bridge to encoder */
> >>> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> >>> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >> Few suggestions:
> >>
> >> 1. Maybe it would be more convenient to add flags argument instead of bool:
> >>
> >> - code should be more readable: ret = drm_bridge_attach(encoder, bridge,
> >> NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)
> >>
> >> - it can be easily expanded later with other flags, there at least two
> >> drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.
> > Please note that I think this flag should disappear once drivers get
> > converted to the new model. This will however take some time. I'm not
> > opposed to turning the book into a flag though. I was hoping to receive
> > more review comments on this particular patch, but as that's not the
> > case, I can already proceed with the change.
> >
> > What would the DRM_BRIDGE_FLAG_NO_CHAINING flag be used for ?
> 
> To avoid setting encoder->bridge or previous_bridge->next field to
> attached bridge (last lines of drm_bridge_attach code).
> 
> This is for sure the case of exynos_dsi and vc4_dsi, but I guess it can
> affect other drivers as well, probably they just use other workarounds
> or have more flexible hardware.
> 
> Generally idea that order of calling
> pre_enable/enable/disable/post_disable callbacks in chained
> encoder/bridges is fixed is wrong IMHO, only video source component
> knows in which moment it should enable its sink and if it should do
> something after. And it does not work at all with sources/sinks having
> more than one output/input.

Makes sense. I think I would however still set the ->next field, in
order to track bridges, and only skip chaining of the enable/disable
operations.

> >> 2. If the patch can be applied atomically it is OK as is, if not you can
> >> use preprocessor vararg magic to support new and old syntax, sth like:
> >>
> >> #define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
> >> __drm_bridge_attach(encoder, bridge, prev, flags)
> >>
> >> #define drm_bridge_attach(encoder, bridge, prev, optarg...)
> >> _drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)
> >
> > Good point. I'll try to do this atomically, but if it fails I'll follow
> > your suggestion.
> >
> >> 3. Maybe more convenient would be to just set the flags directly before
> >> attachment:
> >>
> >>     bridge->dont_create_connector = true;
> >>
> >>     ret = drm_bridge_attach(encoder, bridge, NULL);
> >>
> >>     This way it will be still expandable, and less changes.
> >
> > Bridges that are chained would need to set the dont_create_connector
> > flag of the next bridge. It would be a bit ugly, but would make this
> > patch smaller. On the other hand we would need to keep the if
> > (!create_connector) check in the .attach() handlers, and it would be
> > easier to miss it in bridge drivers (current or new) than with an
> > explicit argument to the .attach() operation. I would thus have a
> > preference for the new argument to .attach(). Especially if it can help
> > you with DRM_BRIDGE_FLAG_NO_CHAINING :-)
> 
> bridge->dont_chain would work as well :)
> 
> Btw I wonder if it could be possible to disallow creating connectors
> at all by new bridges - it would speed-up transition.

If we can agree on that, that would be wonderful !

> Another long term idea. Since bridges can be attached to:
> - encoder,
> - another bridge,
> - crtc (I have one example, but I guess there could be more),
> - even before crtc (image postprocessing)
> And since bridge output goes to:
> - another bridge,
> - panel.
> 
> Wouldn't be better to create drm_source and drm_sink (do not respond
> with xkcd picture :) ):
>
> - drm_source will be embedded in source device context,
> - drm_sink will be embedded in sink device context.
>
> We could make then transitions of bridges to drm_sink with drm_source
> embeded in its context, and panels to drm_sink.
> This way we could drop these crazy constructs:
>
> - if sink is panel then do sth, elsif is bridge then do sth_else,
> - if src is bridge then do sth, elsif is encoder ... elsif ....
> - helpers of_find_panel_or_bridge,
> - drm_panel_bridge,
>
> Also we could implement easily multi input/output bridges/panels/crtcs whatever.
> And hpd callbacks you have proposed in another patch would fit better
> to drm_source.ops.
> ...

My best answer to this is: one step at a time :-) Yes, that's the
direction I want to take (there will of course be lots of bikeshedding
on the details), and this series is one step in that direction. What I'm
really trying to do here is simplify bridge implementations by creating
a clear API that shrinks as much as possible the duties of bridge
drivers. This moves existing complexity outside of bridges, and we of
course don't want to duplicate it in all display controller drivers, so
I'm also creating helpers that centralise the complexity. Their use is
not mandatory (Daniel has hammered the "no midlayer" message enough),
but for the vast majority of display controllers, it should greatly
simplify the code.

My next planned steps are:

- Convert more bridge drivers to support DRM_BRIDGE_FLAG_NO_CONNECTOR
- Convert more display controller drivers to use DRM_BRIDGE_FLAG_NO_CONNECTOR
- Drop support for !DRM_BRIDGE_FLAG_NO_CONNECTOR in bridge drivers once
  they are only used by display controllers that set
  DRM_BRIDGE_FLAG_NO_CONNECTOR

In parallel, I want to address drm_panel ad drm_panel_bridge. With the
extension of the bridge API to expose connector-related operations done
in this series, the distinction between panel and bridges becomes much
thinner, opening the door to lots of simplifications.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-07-16 13:57                     ` Andrzej Hajda
@ 2019-08-08 19:32                       ` Laurent Pinchart
  2019-08-09 11:55                         ` Andrzej Hajda
  2019-08-14 12:35                         ` Daniel Vetter
  0 siblings, 2 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 19:32 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hello,

On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> On 16.07.2019 11:00, Daniel Vetter wrote:
> > On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>> Hi Laurent,
> >>>>>>>>
> >>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>> panel is very painful.
> >>>>>>>
> >>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>
> >>>>>>>> More comments inlined.
> >>>>>>>>
> >>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>> data:
> >>>>>>>>>
> >>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>   retrieval operations
> >>>>>>>>> - Bitmask of supported operations
> >>>>>>>>
> >>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>> operation's callback?
> >>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>
> >>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>
> >>>>>>>>> - Bridge output type
> >>>>>>>>>
> >>>>>>>>> Add and document these.
> >>>>>>>>>
> >>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>> bridges.
> >>>>>>>>
> >>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>> right? More comments about it later.
> >>>>>>>>
> >>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>> ---
> >>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>
> >>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>   */
> >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>  {
> >>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>> +
> >>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>> +
> >>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>  }
> >>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>  
> >>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>  }
> >>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>  
> >>>>>>>>> +/**
> >>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>> + *
> >>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>> + *
> >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>> + *
> >>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>> + * the bridge.
> >>>>>>>>> + */
> >>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>> bridge attach:
> >>>>>>>>
> >>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>
> >>>>>>>> bridge->hpd_data = data;
> >>>>>>>>
> >>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>
> >>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>
> >>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>> without big sacrifices.
> >>>>>>>>
> >>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>
> >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>> +			   void *data)
> >>>>>>>>> +{
> >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>> +		return;
> >>>>>>>>> +
> >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>> +
> >>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>> +		goto unlock;
> >>>>>>>>> +
> >>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>> +
> >>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>> +
> >>>>>>>>> +unlock:
> >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>> +}
> >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>> +
> >>>>>>>>> +/**
> >>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>> + *
> >>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>> + * output status change occurs.
> >>>>>>>>> + *
> >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>> + */
> >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>> +{
> >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>> +		return;
> >>>>>>>>> +
> >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>> +
> >>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>> +}
> >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>> +
> >>>>>>>>> +/**
> >>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>> + * @status: output connection status
> >>>>>>>>> + *
> >>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>> + *
> >>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>> + */
> >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>> +{
> >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>
> >>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>
> >>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>> 			continue;
> >>>>>>> 		if (bridge->hpd_notify);
> >>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>> 	}
> >>>>>>>
> >>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>
> >>>>>>> 	dev = bridge->dev
> >>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>
> >>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>> hpd they want/need.
> >>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>
> >>>>>> I think it should be enough to notify only the source, and then source
> >>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>
> >>>>>> It looks more generic for me.
> >>>>>
> >>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>> the one from Laurent? Kinda confused here.
> >>>>
> >>>> Regarding general idea:
> >>>>
> >>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>> source.
> >>>>
> >>>> 2. Your is to notify all other bridges and encoder.
> >>>>
> >>>> And I prefer 1st approach, why:
> >>>>
> >>>> - the source can decide if/when and to who propagate the signal,
> >>>>
> >>>> - is more generic, for example if bridge send signal to two
> >>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>
> >>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>> consumer. There's only 1 callback. So you're example doesn't work.
> >>
> >> If there will be two consumers, there will be two bridge attachments,
> >> thus there will be two notifications, it should work.
> >
> > 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> > callback is registered on the produce bridge, not on the consumer bridge
> > (or I'm totallly misreading what Laurent does here).
> 
> I have assumed that if devices exposes two hardware sink interfaces it
> will expose two separate bridges - of course it will not work with
> "bridge chaining" thing, but this is a different story.

Daniel is right that the current implementation only allows one
consumer. This is however not a limitation of the API, but of its
implementation, as I only needed a single consumer. The helpers in this
series ensure that neither the consumer nor the producer poke in the
drm_bridge structure to call back to the HPD handler:

- The consumer calls drm_bridge_hpd_enable() and
  drm_bridge_hpd_disable(), which could offer a reference-counted
  behaviour if desired without changes to the consumer.

- The producer gets configured by .hpd_enable() and .hpd_disable(),
  which could also easily accommodate reference-counting in the drm
  bridge core without changes to the producer.

- The producer notifies HPD with drm_bridge_hpd_notify(), which could
  easily be extended to support multiple consumers without changes to
  the producer.

This is actually my second version of the HPD mechanism. The first
version was never posted, poked into drm_bridge, and required the
producer to be aware of the callbacks. After discussing this privately
with Daniel, I came up with the implementation in this series that,
while not supporting multiple consumers now, makes it easy to extend
later without minimal effort.

Daniel's proposed implementation above looks reasonable to me, provided
we can iterate over the bridges in an order that don't depend on the
position of the producer in the chain (should be easy to solve by
starting at the encoder for instance). It however looks a bit like a
midlayer to me :-) That's why I have a similar implementation in the
connector-bridge helper, which could be extended to call
encoder->helper_private->bridge_hpd_notify() and
dev->mode_config.helper_private->bridge_hpd_notify() instead of
hardcoding drm_kms_helper_hotplug_event(). Moving the code to
drm_bridge_hpd_notify() would on the other hand set the notification
sequence towards the encoder and driver in stone. Daniel, do you think
that would be better ?

I would like to remind everybody that this series isn't the last I will
ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
open to suggestions, and can address problems on top of these patches,
provided obviously that this series doesn't go in the wrong direction.
I'm of course also willing to rework this series, but given the amount
of work we have in the drm_bridge realm, I can't fix everything in one
go :-)

> >>>> - it resembles hardware wires :)
> >>>
> >>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>> interested in that hpd singal. This includes:
> >>> - Other bridges, e.g. if they provide CEC support.
> >>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>> - Overall driver, so it can update the modes/connector status and send the
> >>>   uevent to the driver.
> >>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>   shut down/re-enable the pipe because $reasons.
> >>>  
> >>> That's at least my understanding from lots of chats with Laurent about
> >>> what he wants to do here.

That's correct, and that's what I was trying to implement :-) The
notification, in this patch series, goes from the producer bridge to a
central place (namely the connector, with a helper implementation
available as part of this series, but custom implementations in display
drivers are fine if needed) that then dispatches the notification to all
bridges (through the .lost_hotplug() operation, which we could replace
by an .hpd_notify() operation) for the first two purposes listed above,
and then to the overall driver. The only thing I don't support yet is
dispatching to the display pipeline (item 4 in the list above) as I had
no need for that, and didn't want to develop an API with no user. This
would however not be difficult to do when needed, the need is taken into
account in the proposed implementation.

> >> I do not know the full picture, but the solution where particular bridge
> >> notifies everything unconditionally seems to me much less flexible.
> >>
> >> If HPD signals is received by the consumer, if there are no obstacles it
> >> can propagate it further, upstream bridge/encoder or to drm core - it
> >> will mimic your scenario.
> >>
> >> But there are also other scenarios where bridge does not want to
> >> propagate signal, because for example:
> >>
> >> - it wants to wait for other sinks to wake up,
> >
> > The other sink can just do that in their hpd callback.
> >
> >> - it propagates HPD signal via hardware wire,
> >
> > Again, the other sink can just not listen to sw hpd in that case, and use
> > the wire/hw hpd interrupt.
> 
> If it should ignore HPD, why it should receive it at all - it is
> unnecessary noise. And I am afraid with more complicated pipelines it
> will be impossible for particular component (bridge/encoder/whatever) to
> distinguish if HPD notification which came from non-directly connected
> component should be ignored or not.
> 
> >> - first it wants to verify if the sink is valid/compatible/authorized
> >> device.
> >
> > Now you lost me. Why would someone glue incompatible IP into a SoC or
> > board?
> 
> Bridge can have external connectors, and the user can connect there
> anything.
> 
> >> In general HPD is input signal for notify of state changes on particular
> >> bus, in case of typical video bridge on its output video bus.
> >>
> >> In case of bridges they have also input video buses, and they can send
> >> HPD signal via this bus, but this is indeed different HPD signal, even
> >> if for most cases they looks similar.
> >
> > Ah, I think this is a problem we will eventually have. But it's not
> > something we're currently solving here at all I think.
> 
> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> line, so this is not something from far future. And I guess with HPD
> broadcasting it could be racy/error prone, for example EDID reading can
> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> controller via hw wires also).
> 
> >>>> And regarding implementation:
> >>>>
> >>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>
> >>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>
> >>>> Your proposition is more straightforward, but if we want to notify only
> >>>> source we should locate it by parsing notification chain (what about
> >>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>
> >>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>
> >>> Uh I think we're not talking about the same thing really. My understanding
> >>> is that this callback is if someone (outside of this bridge) is interested
> >>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>> listener.
> >>
> >> Do we have real life examples?
> >>
> >> I want to distinguish two situations:
> >>
> >> - another device wants to know if input bus of the bridge has changed state,
> >>
> >> - another device wants to know if output bus of the bridge has changed
> >> state.
> >
> > Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> > bridges can exchange state and information about each another. hpd is
> > about the physical world, i.e. "is there a cable plugged into the port
> > I'm driving?". We're not going to use fake hpd to update bridge state and
> > fun stuff like that, we have the atomic_check machinery for this.
> 
> My question was if we have real examples that upstream device requires
> knowledge about state of output line of the bridge?
> 
> To be more precise, we have following display pipeline:
> 
> A-->B-->C
> 
> And C sends HPD to B (ie signal that state of line between B and C
> changed). Does A really wants to know this information? or it should
> just need to know if state of line A-->B changed?

There's one real life example, where A is an HDMI encoder, B is an HDMI
ESD protector and level shifter, and C is the physical HDMI connector.
When the HDMI cable is unplugged, the CEC controller part of A needs to
be notified in order to reset the CEC state machine. One could however
argue that in that case the A-B link state changes too, but the
important part is that HPD detection is not performed by A, while A
needs to be informed of lost hotplug.

> >>> You seem to have some other idea here.
> >>>
> >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>> +}
> >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>> +
> >>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>  /**
> >>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>  
> >>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>> +#include <linux/list.h>
> >>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>  
> >>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>  	 */
> >>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @detect:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * RETURNS:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>> +	 */
> >>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @get_modes:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * RETURNS:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>> +	 */
> >>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @get_edid:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>> +	 * output.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * RETURNS:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>> +	 */
> >>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>> presence of another one?
> >>>>>>>>
> >>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>> and connector->edid correctly.
> >>>>>>>
> >>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>> correctly updated.
> >>>>>>>
> >>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>> edid, which is not so awesome :-)
> >>>>>>>
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>> +	 * connection status occurs.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>  };
> >>>>>>>>>  
> >>>>>>>>>  /**
> >>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>  	bool dual_link;
> >>>>>>>>>  };
> >>>>>>>>>  
> >>>>>>>>> +/**
> >>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>> + */
> >>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>>  /**
> >>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>   */
> >>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>  	void *driver_private;
> >>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>> +	 */
> >>>>>>>>> +	int type;
> >>>>>>>>> +	/** private: */
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>> +	 */
> >>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void *hpd_data;
> >>>>>>>>>  };
> >>>>>>>>>  
> >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>  
> >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>> +			   void *data);
> >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>> +
> >>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges
  2019-07-18 17:01     ` Daniel Vetter
@ 2019-08-08 19:48       ` Laurent Pinchart
  2019-08-14 15:01         ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-08 19:48 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Thu, Jul 18, 2019 at 07:01:03PM +0200, Daniel Vetter wrote:
> On Sun, Jul 07, 2019 at 09:19:02PM +0300, Laurent Pinchart wrote:
> > Most bridge drivers create a DRM connector to model the connector at the
> > output of the bridge. This model is historical and has worked pretty
> > well so far, but causes several issues:
> > 
> > - It prevents supporting more complex display pipelines where DRM
> > connector operations are split over multiple components. For instance a
> > pipeline with a bridge connected to the DDC signals to read EDID data,
> > and another one connected to the HPD signal to detect connection and
> > disconnection, will not be possible to support through this model.
> > 
> > - It requires every bridge driver to implement similar connector
> > handling code, resulting in code duplication.
> > 
> > - It assumes that a bridge will either be wired to a connector or to
> > another bridge, but doesn't support bridges that can be used in both
> > positions very well (although there is some ad-hoc support for this in
> > the analogix_dp bridge driver).
> > 
> > In order to solve these issues, ownership of the connector needs to be
> > moved to the display controller driver.
> > 
> > To avoid code duplication in display controller drivers, add a new
> > helper to create and manage a DRM connector backed by a chain of
> > bridges. All connector operations are delegating to the appropriate
> > bridge in the chain.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  drivers/gpu/drm/Makefile               |   3 +-
> >  drivers/gpu/drm/drm_bridge_connector.c | 385 +++++++++++++++++++++++++
> >  include/drm/drm_bridge_connector.h     |  18 ++
> >  3 files changed, 405 insertions(+), 1 deletion(-)
> >  create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
> >  create mode 100644 include/drm/drm_bridge_connector.h
> > 
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 9f0d2ee35794..1b74653c9db9 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -37,7 +37,8 @@ drm_vram_helper-y := drm_gem_vram_helper.o \
> >  		     drm_vram_mm_helper.o
> >  obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
> >  
> > -drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper.o \
> > +drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
> > +		drm_dsc.o drm_probe_helper.o \
> >  		drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
> >  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
> >  		drm_simple_kms_helper.o drm_modeset_helper.o \
> > diff --git a/drivers/gpu/drm/drm_bridge_connector.c b/drivers/gpu/drm/drm_bridge_connector.c
> > new file mode 100644
> > index 000000000000..09f2d6bfb561
> > --- /dev/null
> > +++ b/drivers/gpu/drm/drm_bridge_connector.c
> > @@ -0,0 +1,385 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +
> > +#include <drm/drm_atomic_state_helper.h>
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_bridge_connector.h>
> > +#include <drm/drm_connector.h>
> > +#include <drm/drm_device.h>
> > +#include <drm/drm_edid.h>
> > +#include <drm/drm_modeset_helper_vtables.h>
> > +#include <drm/drm_probe_helper.h>
> > +
> > +/**
> > + * DOC: overview
> > + *
> > + * The DRM bridge connector helper object provides a DRM connector
> > + * implementation that wraps a chain of &struct drm_bridge. The connector
> > + * operations are fully implemented based on the operations of the bridges in
> > + * the chain, and don't require any intervention from the display controller
> > + * driver at runtime.
> > + *
> > + * To use the helper, display controller drivers create a bridge connector with
> > + * a call to drm_bridge_connector_init(). This associates the newly created
> > + * connector with the chain of bridges passed to the function and registers it
> > + * with the DRM device. At that point the connector becomes fully usable, no
> > + * further operation is needed.
> > + *
> > + * The DRM bridge connector operations are implemented based on the operations
> > + * provided by the bridges in the chain. Each connector operation is delegated
> > + * to the bridge closest to the connector (at the end of the chain) that
> > + * provides the relevant functionality.
> > + *
> > + * To make use of this helper, all bridges in the chain shall report bridge
> > + * operation flags (&drm_bridge->ops) and bridge output type
> > + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> > + */
> > +
> > +/**
> > + * struct drm_bridge_connector - A connector backed by a chain of bridges
> > + */
> > +struct drm_bridge_connector {
> > +	/**
> > +	 * @base: The base DRM connector
> > +	 */
> > +	struct drm_connector base;
> > +	/**
> > +	 * @bridge:
> > +	 *
> > +	 * The first bridge in the chain (connected to the output of the CRTC).
> > +	 */
> > +	struct drm_bridge *bridge;
> > +	/**
> > +	 * @bridge_edid:
> > +	 *
> > +	 * The last bridge in the chain (closest to the connector) that provides
> > +	 * EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
> > +	 */
> > +	struct drm_bridge *bridge_edid;
> > +	/**
> > +	 * @bridge_hpd:
> > +	 *
> > +	 * The last bridge in the chain (closest to the connector) that provides
> > +	 * hot-plug detection notification, if any (see &DRM_BRIDGE_OP_HPD).
> > +	 */
> > +	struct drm_bridge *bridge_hpd;
> > +	/**
> > +	 * @bridge_detect:
> > +	 *
> > +	 * The last bridge in the chain (closest to the connector) that provides
> > +	 * connector detection, if any (see &DRM_BRIDGE_OP_DETECT).
> > +	 */
> > +	struct drm_bridge *bridge_detect;
> > +	/**
> > +	 * @bridge_detect:
> > +	 *
> > +	 * The last bridge in the chain (closest to the connector) that provides
> > +	 * connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
> > +	 */
> > +	struct drm_bridge *bridge_modes;
> > +	/**
> > +	 * @hdmi_mode: Valid for HDMI connectors only.
> > +	 */
> > +	bool hdmi_mode;
> 
> This should probably be in drm_display_info somewhere, not here?

Yes, and it's unused in this patch, I've just noticed that. Field
dropped.

> Wrt the overall design ... why do we need a new struct? If we assume (at
> least for now) that we only allow one encoder for such a bridge chain
> (currently still true), then you can always go from the connector to it's
> only possibel encoder. And from there to the bridge chain.
> 
> Furthermore all the special bridge pointers here can just be found at
> runtime by walking the bridge links. And none of these paths are hot
> enough to make this a problem.
> 
> With that your drm_bridge_connector here would become just a pile of
> functions as default implementations for connectors. Making it more
> modular and more helper-y and easier to transition gradually.

The main purpose of this structure is indeed to cache the bridge
pointers, which could be recalculated at runtime. I agree there's no
real hot path, but caching them still feels nice :-)

How do you go from the connector to its encoder though ? The
drm_connector encoder field is valid for non-atomic drivers only, and
the encoder_ids field is marked as not to be accessed directly. Should I
use drm_connector_for_each_possible_encoder() and pick the first encoder
?

> > +};
> > +
> > +#define to_drm_bridge_connector(x) \
> > +	container_of(x, struct drm_bridge_connector, base)
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Bridge Connector Hot-Plug Handling
> > + */
> > +
> > +static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
> > +					    enum drm_connector_status status)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +	struct drm_bridge *bridge;
> > +
> > +	if (status != connector_status_disconnected)
> > +		return;
> > +
> > +	/*
> > +	 * Notify all bridges in the pipeline of disconnection. This is required
> > +	 * to let the HDMI encoders reset their internal state related to
> > +	 * connection status, such as the CEC address.
> > +	 */
> > +	for (bridge = bridge_connector->bridge; bridge; bridge = bridge->next) {
> > +		if (bridge->funcs->lost_hotplug)
> > +			bridge->funcs->lost_hotplug(bridge);
> 
> So looking at this you pretty much implement my idea for hdp handling
> already, except you're calling it ->lost_hotplug and not ->notify_hpd.

Renamed already in my private tree, will be in v2 :-)

> Plus you require some callback registration. Essentially my design (that I
> explained in my reply to your bridge patch) would just make
> drm_bridge_connector_hpd_cb() the one and only hpd_cb, and punt all hpd
> handling to bridge drivers like you do here.
> 
> Ofc that leaves us with "who's calling drm_kms_helper_hotplug_event()",
> and that's what the new hdp_notify on the encoder and the global
> drm_mode_config_helper_funcs would be for.

Let's discuss that in the replies to the other patch, as the discussion
is already longer there. I'm not opposed to your proposal, but I've
asked a few questions to clarify it.

> > +	}
> > +}
> > +
> > +static void drm_bridge_connector_hpd_cb(void *cb_data,
> > +					enum drm_connector_status status)
> > +{
> > +	struct drm_bridge_connector *drm_bridge_connector = cb_data;
> > +	struct drm_connector *connector = &drm_bridge_connector->base;
> > +	struct drm_device *dev = connector->dev;
> > +	enum drm_connector_status old_status;
> > +
> > +	mutex_lock(&dev->mode_config.mutex);
> > +	old_status = connector->status;
> > +	connector->status = status;
> > +	mutex_unlock(&dev->mode_config.mutex);
> > +
> > +	if (old_status == status)
> > +		return;
> > +
> > +	drm_bridge_connector_hpd_notify(connector, status);
> > +
> > +	drm_kms_helper_hotplug_event(dev);
> > +}
> > +
> > +/**
> > + * drm_bridge_connector_enable_hpd - Enable hot-plug detection for the connector
> > + * @connector: The DRM bridge connector
> > + *
> > + * This function enables hot-plug detection for the given bridge connector.
> > + * This is typically used by display drivers in their resume handler.
> > + */
> > +void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> > +
> > +	if (hpd)
> > +		drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
> > +				      bridge_connector);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_bridge_connector_enable_hpd);
> > +
> > +/**
> > + * drm_bridge_connector_disable_hpd - Disable hot-plug detection for the
> > + * connector
> > + * @connector: The DRM bridge connector
> > + *
> > + * This function disables hot-plug detection for the given bridge connector.
> > + * This is typically used by display drivers in their suspend handler.
> > + */
> > +void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> > +
> > +	if (hpd)
> > +		drm_bridge_hpd_disable(hpd);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_bridge_connector_disable_hpd);
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Bridge Connector Functions
> > + */
> > +
> > +static enum drm_connector_status
> > +drm_bridge_connector_detect(struct drm_connector *connector, bool force)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +	struct drm_bridge *detect = bridge_connector->bridge_detect;
> > +	enum drm_connector_status status;
> > +
> > +	if (detect) {
> > +		status = detect->funcs->detect(detect);
> > +
> > +		drm_bridge_connector_hpd_notify(connector, status);
> > +	} else {
> > +		switch (connector->connector_type) {
> > +		case DRM_MODE_CONNECTOR_DPI:
> > +		case DRM_MODE_CONNECTOR_LVDS:
> > +		case DRM_MODE_CONNECTOR_DSI:
> > +			status = connector_status_connected;
> > +			break;
> > +		default:
> > +			status = connector_status_unknown;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return status;
> > +}
> > +
> > +static void drm_bridge_connector_destroy(struct drm_connector *connector)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +
> > +	if (bridge_connector->bridge_hpd) {
> > +		struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> > +
> > +		drm_bridge_hpd_disable(hpd);
> > +	}
> > +
> > +	drm_connector_unregister(connector);
> > +	drm_connector_cleanup(connector);
> > +
> > +	kfree(bridge_connector);
> > +}
> > +
> > +static const struct drm_connector_funcs drm_bridge_connector_funcs = {
> > +	.reset = drm_atomic_helper_connector_reset,
> > +	.detect = drm_bridge_connector_detect,
> 
> For that smooht helper library feeling I think we should export _detect
> and get_modes at least.

Already, even without a user ? Or should we wait until someone needs
them ?

> > +	.fill_modes = drm_helper_probe_single_connector_modes,
> > +	.destroy = drm_bridge_connector_destroy,
> > +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> > +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Bridge Connector Helper Functions
> > + */
> > +
> > +#define MAX_EDID  512
> > +
> > +static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> > +					       struct drm_bridge *bridge)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +	enum drm_connector_status status;
> > +	struct edid *edid;
> > +	int n;
> > +
> > +	status = drm_bridge_connector_detect(connector, false);
> > +	if (status != connector_status_connected)
> > +		goto no_edid;
> > +
> > +	edid = bridge->funcs->get_edid(bridge, connector);
> > +	if (!edid || !drm_edid_is_valid(edid)) {
> > +		kfree(edid);
> > +		goto no_edid;
> > +	}
> > +
> > +	drm_connector_update_edid_property(connector, edid);
> > +	n = drm_add_edid_modes(connector, edid);
> > +
> > +	bridge_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
> > +
> > +	kfree(edid);
> > +	return n;
> > +
> > +no_edid:
> > +	drm_connector_update_edid_property(connector, NULL);
> > +	return 0;
> > +}
> > +
> > +static int drm_bridge_connector_get_modes(struct drm_connector *connector)
> > +{
> > +	struct drm_bridge_connector *bridge_connector =
> > +		to_drm_bridge_connector(connector);
> > +	struct drm_bridge *bridge;
> > +
> > +	/*
> > +	 * If display exposes EDID, then we parse that in the normal way to
> > +	 * build table of supported modes.
> > +	 */
> > +	bridge = bridge_connector->bridge_edid;
> > +	if (bridge)
> > +		return drm_bridge_connector_get_modes_edid(connector, bridge);
> > +
> > +	/*
> > +	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
> > +	 * resolution panel or an analog TV output), query it.
> > +	 */
> > +	bridge = bridge_connector->bridge_modes;
> > +	if (bridge)
> > +		return bridge->funcs->get_modes(bridge, connector);
> > +
> > +	/*
> > +	 * We can't retrieve modes, which can happen for instance for a DVI or
> > +	 * VGA output with the DDC bus unconnected. The KMS core will add the
> > +	 * default modes.
> > +	 */
> > +	return 0;
> > +}
> > +
> > +static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
> > +	.get_modes = drm_bridge_connector_get_modes,
> > +	/* No need for .mode_valid(), the bridges are checked by the core. */
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Bridge Connector Initialisation
> > + */
> > +
> > +/**
> > + * drm_bridge_connector_init - Initialise a connector for a chain of bridges
> > + * @drm: the DRM device
> > + * @bridge: the bridge closest to the CRTC output
> > + *
> > + * Allocate, initialise and register a &drm_bridge_connector with the @drm
> > + * device. The connector is associated with a chain of bridges that starts at
> > + * the CRTC output with @bridge. All bridges in the chain shall report bridge
> > + * operation flags (&drm_bridge->ops) and bridge output type
> > + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> > + *
> > + * Returns a pointer to the new connector on success, or a negative error
> > + * pointer otherwise.
> > + */
> > +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> > +						struct drm_bridge *bridge)
> > +{
> > +	struct drm_bridge_connector *bridge_connector;
> > +	struct drm_connector *connector;
> > +	int connector_type;
> > +
> > +	bridge_connector = kzalloc(sizeof(*bridge_connector), GFP_KERNEL);
> > +	if (!bridge_connector)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	bridge_connector->bridge = bridge;
> > +
> > +	/*
> > +	 * Initialise connector status handling. First locate the furthest
> > +	 * bridges in the pipeline that support HPD and output detection. Then
> > +	 * initialise the connector polling mode, using HPD if available and
> > +	 * falling back to polling if supported. If neither HPD nor output
> > +	 * detection are available, we don't support hotplug detection at all.
> > +	 */
> > +	connector_type = DRM_MODE_CONNECTOR_Unknown;
> > +	for ( ; bridge; bridge = bridge->next) {
> > +		if (bridge->ops & DRM_BRIDGE_OP_EDID)
> > +			bridge_connector->bridge_edid = bridge;
> > +		if (bridge->ops & DRM_BRIDGE_OP_HPD)
> > +			bridge_connector->bridge_hpd = bridge;
> > +		if (bridge->ops & DRM_BRIDGE_OP_DETECT)
> > +			bridge_connector->bridge_detect = bridge;
> > +		if (bridge->ops & DRM_BRIDGE_OP_MODES)
> > +			bridge_connector->bridge_modes = bridge;
> > +
> > +		if (!bridge->next)
> > +			connector_type = bridge->type;
> > +	}
> > +
> > +	if (connector_type == DRM_MODE_CONNECTOR_Unknown) {
> > +		kfree(bridge_connector);
> > +		return ERR_PTR(-EINVAL);
> > +	}
> > +
> > +	/*
> > +	 * TODO: Handle interlace_allowed, doublescan_allowed, stereo_allowed
> > +	 * and ycbcr_420_allowed.
> > +	 */
> > +	connector = &bridge_connector->base;
> > +	drm_connector_init(drm, connector, &drm_bridge_connector_funcs,
> > +			   connector_type);
> > +	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
> > +
> > +	if (bridge_connector->bridge_hpd)
> > +		connector->polled = DRM_CONNECTOR_POLL_HPD;
> > +	else if (bridge_connector->bridge_detect)
> > +		connector->polled = DRM_CONNECTOR_POLL_CONNECT
> > +				  | DRM_CONNECTOR_POLL_DISCONNECT;
> > +
> > +	return connector;
> > +}
> > +EXPORT_SYMBOL_GPL(drm_bridge_connector_init);
> > diff --git a/include/drm/drm_bridge_connector.h b/include/drm/drm_bridge_connector.h
> > new file mode 100644
> > index 000000000000..ec33b44954b8
> > --- /dev/null
> > +++ b/include/drm/drm_bridge_connector.h
> > @@ -0,0 +1,18 @@
> > +/* SPDX-License-Identifier: GPL-2.0+ */
> > +/*
> > + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > + */
> > +
> > +#ifndef __DRM_BRIDGE_CONNECTOR_H__
> > +#define __DRM_BRIDGE_CONNECTOR_H__
> > +
> > +struct drm_bridge;
> > +struct drm_connector;
> > +struct drm_device;
> > +
> > +void drm_bridge_connector_enable_hpd(struct drm_connector *connector);
> > +void drm_bridge_connector_disable_hpd(struct drm_connector *connector);
> > +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> > +						struct drm_bridge *bridge);
> > +
> > +#endif /* __DRM_BRIDGE_CONNECTOR_H__ */

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-08 19:32                       ` Laurent Pinchart
@ 2019-08-09 11:55                         ` Andrzej Hajda
  2019-08-10 22:43                           ` Laurent Pinchart
  2019-08-14 12:35                         ` Daniel Vetter
  1 sibling, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-08-09 11:55 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On 08.08.2019 21:32, Laurent Pinchart wrote:
> Hello,
>
> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
>> On 16.07.2019 11:00, Daniel Vetter wrote:
>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>>>>>> Hi Laurent,
>>>>>>>>>>
>>>>>>>>>> I like the approach, current practice when almost every bridge should
>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>>>>>> panel is very painful.
>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>>>>>> get a feeling for what your hpd_cb usually does.
>>>>>>>>>
>>>>>>>>>> More comments inlined.
>>>>>>>>>>
>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>>>>>> data:
>>>>>>>>>>>
>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>>>>>   retrieval operations
>>>>>>>>>>> - Bitmask of supported operations
>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>>>>>> operation's callback?
>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>>>>>>
>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>>>>>> add, generally good excuse to not have to think through the design between
>>>>>>>>> different parts of drivers - "just" add another flag.
>>>>>>>>>
>>>>>>>>>>> - Bridge output type
>>>>>>>>>>>
>>>>>>>>>>> Add and document these.
>>>>>>>>>>>
>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>>>>>> bridges.
>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>>>>>> right? More comments about it later.
>>>>>>>>>>
>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>>>>>> ---
>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>>>>>
>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>>>>>   */
>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>>>>>  {
>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>>>>>> +
>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>> +
>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>>>>>  }
>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>>>>>  
>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>  }
>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>>>>>  
>>>>>>>>>>> +/**
>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>> + * @cb: hot-plug detection callback
>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>>>>>> + *
>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>>>>>> + *
>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>> + *
>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>>>>>> + * the bridge.
>>>>>>>>>>> + */
>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>>>>>> bridge attach:
>>>>>>>>>>
>>>>>>>>>> bridge->hpd_cb = cb;
>>>>>>>>>>
>>>>>>>>>> bridge->hpd_data = data;
>>>>>>>>>>
>>>>>>>>>> ret = drm_bridge_attach(...);
>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>>>>>
>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>>>>>> without big sacrifices.
>>>>>>>>>>
>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>>>>>
>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>> +			   void *data)
>>>>>>>>>>> +{
>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>>>>>> +		return;
>>>>>>>>>>> +
>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>> +
>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>>>>>> +		goto unlock;
>>>>>>>>>>> +
>>>>>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>>>>>> +	bridge->hpd_data = data;
>>>>>>>>>>> +
>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>>>>>> +
>>>>>>>>>>> +unlock:
>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>> +}
>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>>>>>> +
>>>>>>>>>>> +/**
>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>> + *
>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>>>>>> + * output status change occurs.
>>>>>>>>>>> + *
>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>> + */
>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>>>>>> +{
>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>>>>>> +		return;
>>>>>>>>>>> +
>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>>>>>> +
>>>>>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>> +}
>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>>>>>> +
>>>>>>>>>>> +/**
>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>> + * @status: output connection status
>>>>>>>>>>> + *
>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>>>>>> + *
>>>>>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>>>>>> + */
>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>> +			   enum drm_connector_status status)
>>>>>>>>>>> +{
>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>> +	if (bridge->hpd_cb)
>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>>>>>
>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>>>>>> 		if (tmp_bridge == bridge)
>>>>>>>>> 			continue;
>>>>>>>>> 		if (bridge->hpd_notify);
>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>>>>>> 	}
>>>>>>>>>
>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>>>>>
>>>>>>>>> 	dev = bridge->dev
>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>>>>>
>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>>>>>> hpd they want/need.
>>>>>>>> As I understand you want to notify every member of the pipeline.
>>>>>>>>
>>>>>>>> I think it should be enough to notify only the source, and then source
>>>>>>>> should decide if/when the hpd should be propagated upstream.
>>>>>>>>
>>>>>>>> It looks more generic for me.
>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>>>>>> the one from Laurent? Kinda confused here.
>>>>>> Regarding general idea:
>>>>>>
>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
>>>>>> source.
>>>>>>
>>>>>> 2. Your is to notify all other bridges and encoder.
>>>>>>
>>>>>> And I prefer 1st approach, why:
>>>>>>
>>>>>> - the source can decide if/when and to who propagate the signal,
>>>>>>
>>>>>> - is more generic, for example if bridge send signal to two
>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
>>>>> consumer. There's only 1 callback. So you're example doesn't work.
>>>> If there will be two consumers, there will be two bridge attachments,
>>>> thus there will be two notifications, it should work.
>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
>>> callback is registered on the produce bridge, not on the consumer bridge
>>> (or I'm totallly misreading what Laurent does here).
>> I have assumed that if devices exposes two hardware sink interfaces it
>> will expose two separate bridges - of course it will not work with
>> "bridge chaining" thing, but this is a different story.
> Daniel is right that the current implementation only allows one
> consumer. This is however not a limitation of the API, but of its
> implementation, as I only needed a single consumer. The helpers in this
> series ensure that neither the consumer nor the producer poke in the
> drm_bridge structure to call back to the HPD handler:
>
> - The consumer calls drm_bridge_hpd_enable() and
>   drm_bridge_hpd_disable(), which could offer a reference-counted
>   behaviour if desired without changes to the consumer.
>
> - The producer gets configured by .hpd_enable() and .hpd_disable(),
>   which could also easily accommodate reference-counting in the drm
>   bridge core without changes to the producer.
>
> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
>   easily be extended to support multiple consumers without changes to
>   the producer.
>
> This is actually my second version of the HPD mechanism. The first
> version was never posted, poked into drm_bridge, and required the
> producer to be aware of the callbacks. After discussing this privately
> with Daniel, I came up with the implementation in this series that,
> while not supporting multiple consumers now, makes it easy to extend
> later without minimal effort.
>
> Daniel's proposed implementation above looks reasonable to me, provided
> we can iterate over the bridges in an order that don't depend on the
> position of the producer in the chain (should be easy to solve by
> starting at the encoder for instance). It however looks a bit like a
> midlayer to me :-) That's why I have a similar implementation in the
> connector-bridge helper, which could be extended to call
> encoder->helper_private->bridge_hpd_notify() and
> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> drm_bridge_hpd_notify() would on the other hand set the notification
> sequence towards the encoder and driver in stone. Daniel, do you think
> that would be better ?
>
> I would like to remind everybody that this series isn't the last I will
> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> open to suggestions, and can address problems on top of these patches,
> provided obviously that this series doesn't go in the wrong direction.
> I'm of course also willing to rework this series, but given the amount
> of work we have in the drm_bridge realm, I can't fix everything in one
> go :-)
>
>>>>>> - it resembles hardware wires :)
>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
>>>>> interested in that hpd singal. This includes:
>>>>> - Other bridges, e.g. if they provide CEC support.
>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
>>>>> - Overall driver, so it can update the modes/connector status and send the
>>>>>   uevent to the driver.
>>>>> - Overall display pipeline for this specific bridge, maybe you need to
>>>>>   shut down/re-enable the pipe because $reasons.
>>>>>  
>>>>> That's at least my understanding from lots of chats with Laurent about
>>>>> what he wants to do here.
> That's correct, and that's what I was trying to implement :-) The
> notification, in this patch series, goes from the producer bridge to a
> central place (namely the connector, with a helper implementation
> available as part of this series, but custom implementations in display
> drivers are fine if needed) that then dispatches the notification to all
> bridges (through the .lost_hotplug() operation, which we could replace
> by an .hpd_notify() operation) for the first two purposes listed above,
> and then to the overall driver. The only thing I don't support yet is
> dispatching to the display pipeline (item 4 in the list above) as I had
> no need for that, and didn't want to develop an API with no user. This
> would however not be difficult to do when needed, the need is taken into
> account in the proposed implementation.
>
>>>> I do not know the full picture, but the solution where particular bridge
>>>> notifies everything unconditionally seems to me much less flexible.
>>>>
>>>> If HPD signals is received by the consumer, if there are no obstacles it
>>>> can propagate it further, upstream bridge/encoder or to drm core - it
>>>> will mimic your scenario.
>>>>
>>>> But there are also other scenarios where bridge does not want to
>>>> propagate signal, because for example:
>>>>
>>>> - it wants to wait for other sinks to wake up,
>>> The other sink can just do that in their hpd callback.
>>>
>>>> - it propagates HPD signal via hardware wire,
>>> Again, the other sink can just not listen to sw hpd in that case, and use
>>> the wire/hw hpd interrupt.
>> If it should ignore HPD, why it should receive it at all - it is
>> unnecessary noise. And I am afraid with more complicated pipelines it
>> will be impossible for particular component (bridge/encoder/whatever) to
>> distinguish if HPD notification which came from non-directly connected
>> component should be ignored or not.
>>
>>>> - first it wants to verify if the sink is valid/compatible/authorized
>>>> device.
>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
>>> board?
>> Bridge can have external connectors, and the user can connect there
>> anything.
>>
>>>> In general HPD is input signal for notify of state changes on particular
>>>> bus, in case of typical video bridge on its output video bus.
>>>>
>>>> In case of bridges they have also input video buses, and they can send
>>>> HPD signal via this bus, but this is indeed different HPD signal, even
>>>> if for most cases they looks similar.
>>> Ah, I think this is a problem we will eventually have. But it's not
>>> something we're currently solving here at all I think.
>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
>> line, so this is not something from far future. And I guess with HPD
>> broadcasting it could be racy/error prone, for example EDID reading can
>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
>> controller via hw wires also).
>>
>>>>>> And regarding implementation:
>>>>>>
>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>>>>>
>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>>>>>
>>>>>> Your proposition is more straightforward, but if we want to notify only
>>>>>> source we should locate it by parsing notification chain (what about
>>>>>> unchained bridges), or store pointer somewhere during attachment.
>>>>>>
>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
>>>>> Uh I think we're not talking about the same thing really. My understanding
>>>>> is that this callback is if someone (outside of this bridge) is interested
>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
>>>>> listener.
>>>> Do we have real life examples?
>>>>
>>>> I want to distinguish two situations:
>>>>
>>>> - another device wants to know if input bus of the bridge has changed state,
>>>>
>>>> - another device wants to know if output bus of the bridge has changed
>>>> state.
>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
>>> bridges can exchange state and information about each another. hpd is
>>> about the physical world, i.e. "is there a cable plugged into the port
>>> I'm driving?". We're not going to use fake hpd to update bridge state and
>>> fun stuff like that, we have the atomic_check machinery for this.
>> My question was if we have real examples that upstream device requires
>> knowledge about state of output line of the bridge?
>>
>> To be more precise, we have following display pipeline:
>>
>> A-->B-->C
>>
>> And C sends HPD to B (ie signal that state of line between B and C
>> changed). Does A really wants to know this information? or it should
>> just need to know if state of line A-->B changed?
> There's one real life example, where A is an HDMI encoder, B is an HDMI
> ESD protector and level shifter, and C is the physical HDMI connector.
> When the HDMI cable is unplugged, the CEC controller part of A needs to
> be notified in order to reset the CEC state machine. One could however
> argue that in that case the A-B link state changes too, but the
> important part is that HPD detection is not performed by A, while A
> needs to be informed of lost hotplug.


I have no full picture but I guess in this case C sends HPD to B using
hardware wire, and then B sends HPD to A also via wire, so I wouldn't
say that B does not participate in HPD transmission/forwarding,

some shifters with 'advanced power saving' can even perform wake-up of
upstream pin logic after receiving HPD on downstream, so HPD sent from B
to A is indeed different than HPD sent from C to B.

Btw, with the above logic of propagation of HPD callback (proposed by
Daniel) I guess it will work this way:

- A will receive HPD signal via HW,

- then B and C will receive HPD callback via framework.

Am I right?


Regards

Andrzej


>
>>>>> You seem to have some other idea here.
>>>>>
>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>> +}
>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>>>>>> +
>>>>>>>>>>>  #ifdef CONFIG_OF
>>>>>>>>>>>  /**
>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>>>>>  
>>>>>>>>>>> -#include <linux/list.h>
>>>>>>>>>>>  #include <linux/ctype.h>
>>>>>>>>>>> +#include <linux/list.h>
>>>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>>>>>  
>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>>>>>  	 */
>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>>>>>> +
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @detect:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>>>>>> +
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @get_modes:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>>>>>> +			 struct drm_connector *connector);
>>>>>>>>>>> +
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @get_edid:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>>>>>> +	 * output.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>>>>>> +				 struct drm_connector *connector);
>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>>>>>> presence of another one?
>>>>>>>>>>
>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>>>>>> and connector->edid correctly.
>>>>>>>>>
>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>>>>>> in the connector is up-to-date? With your current callback design that's
>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>>>>>> correctly updated.
>>>>>>>>>
>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>>>>>> edid, which is not so awesome :-)
>>>>>>>>>
>>>>>>>>>>> +
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @lost_hotplug:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>>>>>> +	 * HDMI bridges.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>>>>>> +
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @hpd_enable:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>>>>>> +	 * @hpd_disable.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>>>>>> +
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @hpd_disable:
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>>>>>> +	 * connection status occurs.
>>>>>>>>>>> +	 *
>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>>>>>  };
>>>>>>>>>>>  
>>>>>>>>>>>  /**
>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>>>>>  	bool dual_link;
>>>>>>>>>>>  };
>>>>>>>>>>>  
>>>>>>>>>>> +/**
>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>>>>>> + */
>>>>>>>>>>> +enum drm_bridge_ops {
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>>  /**
>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>>>>>   */
>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>>>>>  	void *driver_private;
>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>>>>>> +	 * identifies the type of connected display.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	int type;
>>>>>>>>>>> +	/** private: */
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	struct mutex hpd_mutex;
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>>>>>> +	/**
>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>>>>>> +	 * @hpd_cb.
>>>>>>>>>>> +	 */
>>>>>>>>>>> +	void *hpd_data;
>>>>>>>>>>>  };
>>>>>>>>>>>  
>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>>>>>  
>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>> +			   void *data);
>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>> +			   enum drm_connector_status status);
>>>>>>>>>>> +
>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>>>>>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel
  2019-08-08 15:54       ` Laurent Pinchart
@ 2019-08-09 13:33         ` Sam Ravnborg
  2019-08-09 17:51           ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Sam Ravnborg @ 2019-08-09 13:33 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Laurent.

> > > +static int td043mtea1_disable(struct drm_panel *panel)
> > > +{
> > > +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> > > +
> > > +	if (!lcd->spi_suspended)
> > > +		td043mtea1_power_off(lcd);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int td043mtea1_enable(struct drm_panel *panel)
> > > +{
> > > +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> > > +	int ret;
> > > +
> > > +	/*
> > > +	 * If we are resuming from system suspend, SPI might not be enabled
> > > +	 * yet, so we'll program the LCD from SPI PM resume callback.
> > > +	 */
> > > +	if (lcd->spi_suspended)
> > > +		return 0;
> > 
> > I do not recall this is needed in other panel drivers, so look at what
> > other spi based panels do here.
> > I think this is something that today is not required.
> 
> The problem here is that the display controller may be resumed before
> the SPI bus. Has that been solved somewhere in core code ?

I dunno. So the conclusion is to keep it as is, and any change
will wait until someone with HW can step up.

As for all your other feedback to this and the other panel drivers
they did not trigger any repsonse from me.

Looks forward for next iteration of this nice set of patches.
Can we maybe get the panel drivers in before some of the infrastructure
work?
I know the users then may come a bit later, but I think thats OK.

	Sam
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel
  2019-08-09 13:33         ` Sam Ravnborg
@ 2019-08-09 17:51           ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-09 17:51 UTC (permalink / raw)
  To: Sam Ravnborg
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Thierry Reding, Sean Paul

Hi Sam,

On Fri, Aug 09, 2019 at 03:33:08PM +0200, Sam Ravnborg wrote:
> Hi Laurent.
> 
> > > > +static int td043mtea1_disable(struct drm_panel *panel)
> > > > +{
> > > > +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> > > > +
> > > > +	if (!lcd->spi_suspended)
> > > > +		td043mtea1_power_off(lcd);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int td043mtea1_enable(struct drm_panel *panel)
> > > > +{
> > > > +	struct td043mtea1_device *lcd = to_td043mtea1_device(panel);
> > > > +	int ret;
> > > > +
> > > > +	/*
> > > > +	 * If we are resuming from system suspend, SPI might not be enabled
> > > > +	 * yet, so we'll program the LCD from SPI PM resume callback.
> > > > +	 */
> > > > +	if (lcd->spi_suspended)
> > > > +		return 0;
> > > 
> > > I do not recall this is needed in other panel drivers, so look at what
> > > other spi based panels do here.
> > > I think this is something that today is not required.
> > 
> > The problem here is that the display controller may be resumed before
> > the SPI bus. Has that been solved somewhere in core code ?
> 
> I dunno. So the conclusion is to keep it as is, and any change
> will wait until someone with HW can step up.

Great, thanks.

> As for all your other feedback to this and the other panel drivers
> they did not trigger any repsonse from me.
> 
> Looks forward for next iteration of this nice set of patches.
> Can we maybe get the panel drivers in before some of the infrastructure
> work?
> I know the users then may come a bit later, but I think thats OK.

Sure. I'll post the next version soon.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-09 11:55                         ` Andrzej Hajda
@ 2019-08-10 22:43                           ` Laurent Pinchart
  2019-08-14  6:23                             ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-10 22:43 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
> On 08.08.2019 21:32, Laurent Pinchart wrote:
> > On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>> Hi Laurent,
> >>>>>>>>>>
> >>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>> panel is very painful.
> >>>>>>>>>
> >>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>
> >>>>>>>>>> More comments inlined.
> >>>>>>>>>>
> >>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>> data:
> >>>>>>>>>>>
> >>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>   retrieval operations
> >>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>
> >>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>> operation's callback?
> >>>>>>>>>
> >>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>
> >>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>
> >>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>
> >>>>>>>>>>> Add and document these.
> >>>>>>>>>>>
> >>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>> bridges.
> >>>>>>>>>>
> >>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>
> >>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>> ---
> >>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>
> >>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>   */
> >>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>  {
> >>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>> +
> >>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>  }
> >>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>  
> >>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>  }
> >>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>  
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>> + * the bridge.
> >>>>>>>>>>> + */
> >>>>>>>>>>
> >>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>> bridge attach:
> >>>>>>>>>>
> >>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>
> >>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>
> >>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>
> >>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>
> >>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>> without big sacrifices.
> >>>>>>>>>>
> >>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>
> >>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>> +			   void *data)
> >>>>>>>>>>> +{
> >>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>> +		return;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +unlock:
> >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>> +}
> >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>> +
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>> + */
> >>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>> +{
> >>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>> +		return;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>> +}
> >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>> +
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>> + */
> >>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>> +{
> >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>
> >>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>
> >>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>> 			continue;
> >>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>> 	}
> >>>>>>>>>
> >>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>
> >>>>>>>>> 	dev = bridge->dev
> >>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>
> >>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>> hpd they want/need.
> >>>>>>>>
> >>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>
> >>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>
> >>>>>>>> It looks more generic for me.
> >>>>>>>
> >>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>
> >>>>>> Regarding general idea:
> >>>>>>
> >>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>> source.
> >>>>>>
> >>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>
> >>>>>> And I prefer 1st approach, why:
> >>>>>>
> >>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>
> >>>>>> - is more generic, for example if bridge send signal to two
> >>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>
> >>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>
> >>>> If there will be two consumers, there will be two bridge attachments,
> >>>> thus there will be two notifications, it should work.
> >>>
> >>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>> callback is registered on the produce bridge, not on the consumer bridge
> >>> (or I'm totallly misreading what Laurent does here).
> >>
> >> I have assumed that if devices exposes two hardware sink interfaces it
> >> will expose two separate bridges - of course it will not work with
> >> "bridge chaining" thing, but this is a different story.
> >
> > Daniel is right that the current implementation only allows one
> > consumer. This is however not a limitation of the API, but of its
> > implementation, as I only needed a single consumer. The helpers in this
> > series ensure that neither the consumer nor the producer poke in the
> > drm_bridge structure to call back to the HPD handler:
> >
> > - The consumer calls drm_bridge_hpd_enable() and
> >   drm_bridge_hpd_disable(), which could offer a reference-counted
> >   behaviour if desired without changes to the consumer.
> >
> > - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >   which could also easily accommodate reference-counting in the drm
> >   bridge core without changes to the producer.
> >
> > - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >   easily be extended to support multiple consumers without changes to
> >   the producer.
> >
> > This is actually my second version of the HPD mechanism. The first
> > version was never posted, poked into drm_bridge, and required the
> > producer to be aware of the callbacks. After discussing this privately
> > with Daniel, I came up with the implementation in this series that,
> > while not supporting multiple consumers now, makes it easy to extend
> > later without minimal effort.
> >
> > Daniel's proposed implementation above looks reasonable to me, provided
> > we can iterate over the bridges in an order that don't depend on the
> > position of the producer in the chain (should be easy to solve by
> > starting at the encoder for instance). It however looks a bit like a
> > midlayer to me :-) That's why I have a similar implementation in the
> > connector-bridge helper, which could be extended to call
> > encoder->helper_private->bridge_hpd_notify() and
> > dev->mode_config.helper_private->bridge_hpd_notify() instead of
> > hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> > drm_bridge_hpd_notify() would on the other hand set the notification
> > sequence towards the encoder and driver in stone. Daniel, do you think
> > that would be better ?
> >
> > I would like to remind everybody that this series isn't the last I will
> > ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> > open to suggestions, and can address problems on top of these patches,
> > provided obviously that this series doesn't go in the wrong direction.
> > I'm of course also willing to rework this series, but given the amount
> > of work we have in the drm_bridge realm, I can't fix everything in one
> > go :-)
> >
> >>>>>> - it resembles hardware wires :)
> >>>>>
> >>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>> interested in that hpd singal. This includes:
> >>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>   uevent to the driver.
> >>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>  
> >>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>> what he wants to do here.
> >
> > That's correct, and that's what I was trying to implement :-) The
> > notification, in this patch series, goes from the producer bridge to a
> > central place (namely the connector, with a helper implementation
> > available as part of this series, but custom implementations in display
> > drivers are fine if needed) that then dispatches the notification to all
> > bridges (through the .lost_hotplug() operation, which we could replace
> > by an .hpd_notify() operation) for the first two purposes listed above,
> > and then to the overall driver. The only thing I don't support yet is
> > dispatching to the display pipeline (item 4 in the list above) as I had
> > no need for that, and didn't want to develop an API with no user. This
> > would however not be difficult to do when needed, the need is taken into
> > account in the proposed implementation.
> >
> >>>> I do not know the full picture, but the solution where particular bridge
> >>>> notifies everything unconditionally seems to me much less flexible.
> >>>>
> >>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>> will mimic your scenario.
> >>>>
> >>>> But there are also other scenarios where bridge does not want to
> >>>> propagate signal, because for example:
> >>>>
> >>>> - it wants to wait for other sinks to wake up,
> >>>>
> >>> The other sink can just do that in their hpd callback.
> >>>
> >>>> - it propagates HPD signal via hardware wire,
> >>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>> the wire/hw hpd interrupt.
> >>>
> >> If it should ignore HPD, why it should receive it at all - it is
> >> unnecessary noise. And I am afraid with more complicated pipelines it
> >> will be impossible for particular component (bridge/encoder/whatever) to
> >> distinguish if HPD notification which came from non-directly connected
> >> component should be ignored or not.
> >>
> >>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>> device.
> >>>
> >>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>> board?
> >>
> >> Bridge can have external connectors, and the user can connect there
> >> anything.
> >>
> >>>> In general HPD is input signal for notify of state changes on particular
> >>>> bus, in case of typical video bridge on its output video bus.
> >>>>
> >>>> In case of bridges they have also input video buses, and they can send
> >>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>> if for most cases they looks similar.
> >>>
> >>> Ah, I think this is a problem we will eventually have. But it's not
> >>> something we're currently solving here at all I think.
> >>
> >> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >> line, so this is not something from far future. And I guess with HPD
> >> broadcasting it could be racy/error prone, for example EDID reading can
> >> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >> controller via hw wires also).
> >>
> >>>>>> And regarding implementation:
> >>>>>>
> >>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>
> >>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>
> >>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>> source we should locate it by parsing notification chain (what about
> >>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>
> >>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>
> >>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>> listener.
> >>>>
> >>>> Do we have real life examples?
> >>>>
> >>>> I want to distinguish two situations:
> >>>>
> >>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>
> >>>> - another device wants to know if output bus of the bridge has changed
> >>>> state.
> >>>
> >>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>> bridges can exchange state and information about each another. hpd is
> >>> about the physical world, i.e. "is there a cable plugged into the port
> >>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>> fun stuff like that, we have the atomic_check machinery for this.
> >>
> >> My question was if we have real examples that upstream device requires
> >> knowledge about state of output line of the bridge?
> >>
> >> To be more precise, we have following display pipeline:
> >>
> >> A-->B-->C
> >>
> >> And C sends HPD to B (ie signal that state of line between B and C
> >> changed). Does A really wants to know this information? or it should
> >> just need to know if state of line A-->B changed?
> >
> > There's one real life example, where A is an HDMI encoder, B is an HDMI
> > ESD protector and level shifter, and C is the physical HDMI connector.
> > When the HDMI cable is unplugged, the CEC controller part of A needs to
> > be notified in order to reset the CEC state machine. One could however
> > argue that in that case the A-B link state changes too, but the
> > important part is that HPD detection is not performed by A, while A
> > needs to be informed of lost hotplug.
> 
> I have no full picture but I guess in this case C sends HPD to B using
> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
> say that B does not participate in HPD transmission/forwarding,

No, in this case A doesn't receive any hardware HPD signal, it requires
HPD notification through software.

> some shifters with 'advanced power saving' can even perform wake-up of
> upstream pin logic after receiving HPD on downstream, so HPD sent from B
> to A is indeed different than HPD sent from C to B.
> 
> Btw, with the above logic of propagation of HPD callback (proposed by
> Daniel) I guess it will work this way:
> 
> - A will receive HPD signal via HW,
> 
> - then B and C will receive HPD callback via framework.
> 
> Am I right?

It's the other way around.

In this case the HPD signal from the connector (C) is routed to an input
of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
IRQ and receive the hardware HPD notification. The driver for the HDMI
encoder (A) needs to receive HPD notification in software, through the
framework.

> >>>>> You seem to have some other idea here.
> >>>>>
> >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>> +}
> >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>> +
> >>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>  /**
> >>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>  
> >>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>  
> >>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>  	 */
> >>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>> +	 * output.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>>
> >>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>> presence of another one?
> >>>>>>>>>>
> >>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>>
> >>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>> and connector->edid correctly.
> >>>>>>>>>
> >>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>> correctly updated.
> >>>>>>>>>
> >>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>  };
> >>>>>>>>>>>  
> >>>>>>>>>>>  /**
> >>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>  };
> >>>>>>>>>>>  
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>> + */
> >>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>> +};
> >>>>>>>>>>> +
> >>>>>>>>>>>  /**
> >>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>   */
> >>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	int type;
> >>>>>>>>>>> +	/** private: */
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>  };
> >>>>>>>>>>>  
> >>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>  
> >>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>> +			   void *data);
> >>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>> +
> >>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 26/60] drm/omap: Detach from panels at remove time
  2019-07-07 18:19   ` [PATCH 26/60] drm/omap: Detach from panels at remove time Laurent Pinchart
@ 2019-08-13  7:28     ` Tomi Valkeinen
  2019-08-13 13:50       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:28 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> The omapdrm driver attaches to panels with drm_panel_attach() at probe
> time but never calls drm_panel_detach(). Fix it.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/omapdrm/omap_drv.c | 24 +++++++++++++++++++-----
>   1 file changed, 19 insertions(+), 5 deletions(-)

drm_panel_detach() is called in omap_disconnect_pipelines().

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 27/60] drm/omap: Simplify HDMI mode and infoframe configuration
  2019-07-07 18:19   ` [PATCH 27/60] drm/omap: Simplify HDMI mode and infoframe configuration Laurent Pinchart
@ 2019-08-13  7:29     ` Tomi Valkeinen
  0 siblings, 0 replies; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:29 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> Remove the omap_connector_get_hdmi_mode() function as the HDMI mode can
> be accessed directly from the connector's display info.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/omapdrm/omap_connector.c | 11 -----------
>   drivers/gpu/drm/omapdrm/omap_connector.h |  1 -
>   drivers/gpu/drm/omapdrm/omap_encoder.c   |  4 +---
>   3 files changed, 1 insertion(+), 15 deletions(-)

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 28/60] drm/omap: Factor out display type to connector type conversion
  2019-07-07 18:19   ` [PATCH 28/60] drm/omap: Factor out display type to connector type conversion Laurent Pinchart
@ 2019-08-13  7:32     ` Tomi Valkeinen
  2019-08-13 15:18       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:32 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> Move the code that computes the DRM connector type for the
> omapdss_device display type to a new omapdss_device_connector_type()
> function for later reuse.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/omapdrm/dss/base.c       | 23 +++++++++++++++++++++++
>   drivers/gpu/drm/omapdrm/dss/omapdss.h    |  1 +
>   drivers/gpu/drm/omapdrm/omap_connector.c | 19 +------------------
>   3 files changed, 25 insertions(+), 18 deletions(-)
> 
> diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c
> index a1970b9db6ab..cae5687822e2 100644
> --- a/drivers/gpu/drm/omapdrm/dss/base.c
> +++ b/drivers/gpu/drm/omapdrm/dss/base.c
> @@ -285,6 +285,29 @@ void omapdss_device_post_disable(struct omap_dss_device *dssdev)
>   }
>   EXPORT_SYMBOL_GPL(omapdss_device_post_disable);
>   
> +unsigned int omapdss_device_connector_type(enum omap_display_type type)
> +{
> +	switch (type) {
> +	case OMAP_DISPLAY_TYPE_HDMI:
> +		return DRM_MODE_CONNECTOR_HDMIA;
> +	case OMAP_DISPLAY_TYPE_DVI:
> +		return DRM_MODE_CONNECTOR_DVID;
> +	case OMAP_DISPLAY_TYPE_DSI:
> +		return DRM_MODE_CONNECTOR_DSI;
> +	case OMAP_DISPLAY_TYPE_DPI:
> +	case OMAP_DISPLAY_TYPE_DBI:
> +		return DRM_MODE_CONNECTOR_DPI;
> +	case OMAP_DISPLAY_TYPE_VENC:
> +		/* TODO: This could also be composite */
> +		return DRM_MODE_CONNECTOR_SVIDEO;
> +	case OMAP_DISPLAY_TYPE_SDI:
> +		return DRM_MODE_CONNECTOR_LVDS;
> +	default:
> +		return DRM_MODE_CONNECTOR_Unknown;
> +	}
> +}
> +EXPORT_SYMBOL_GPL(omapdss_device_connector_type);

Why do we need to export this? In the end enum omap_display_type should 
go away or be private to omapdrm, right?

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 29/60] drm/omap: Use the drm_panel_bridge API
  2019-07-07 18:19   ` [PATCH 29/60] drm/omap: Use the drm_panel_bridge API Laurent Pinchart
@ 2019-08-13  7:36     ` Tomi Valkeinen
  0 siblings, 0 replies; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:36 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> Replace the manual panel handling code by a drm_panel_bridge. This
> simplifies the driver and allows all components in the display pipeline
> to be treated as bridges, paving the way to generic connector handling.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/omapdrm/dss/base.c       | 12 ++++-----
>   drivers/gpu/drm/omapdrm/dss/output.c     | 33 +++++++++++++++++++++---
>   drivers/gpu/drm/omapdrm/omap_connector.c |  9 -------
>   drivers/gpu/drm/omapdrm/omap_drv.c       | 13 ----------
>   drivers/gpu/drm/omapdrm/omap_encoder.c   | 13 ----------
>   5 files changed, 34 insertions(+), 46 deletions(-)

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 30/60] drm/omap: dss: Fix output next device lookup in DT
  2019-07-07 18:19   ` [PATCH 30/60] drm/omap: dss: Fix output next device lookup in DT Laurent Pinchart
@ 2019-08-13  7:38     ` Tomi Valkeinen
  0 siblings, 0 replies; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:38 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> The DSS core looks up the next device connected to an output by
> traversing the OF graph. It currently hardcodes the local port number to
> 0, which breaks any output with a different port number (SDI on OMAP3
> and any DPI output but the first one). Fix this by repurposing the
> currently unused of_ports bitmask in omap_dss_device with an of_port
> output port number, and use it to traverse the OF graph.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional
  2019-07-07 18:19   ` [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional Laurent Pinchart
@ 2019-08-13  7:48     ` Tomi Valkeinen
  2019-08-13 13:55       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:48 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> As part of the move to drm_bridge ops for some of the internal encoders
> will be removed. Make them optional in the driver to ease the
> transition.

I don't seem to be able to decipher the first sentence. Is there a word 
or two missing from it?

Other than that:

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 33/60] drm/omap: hdmi: Allocate EDID in the .read_edid() operation
  2019-07-07 18:19   ` [PATCH 33/60] drm/omap: hdmi: Allocate EDID in the .read_edid() operation Laurent Pinchart
@ 2019-08-13  7:52     ` Tomi Valkeinen
  0 siblings, 0 replies; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-13  7:52 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:19, Laurent Pinchart wrote:
> Bring the omapdss-specific .read_edid() operation in sync with the
> drm_bridge .get_edid() operation to ease code reuse.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/omapdrm/dss/hdmi4.c      | 34 ++++++++++++++++--------
>   drivers/gpu/drm/omapdrm/dss/hdmi5.c      | 22 ++++++++++-----
>   drivers/gpu/drm/omapdrm/dss/omapdss.h    |  2 +-
>   drivers/gpu/drm/omapdrm/omap_connector.c | 12 +++------
>   4 files changed, 43 insertions(+), 27 deletions(-)
> 
> diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
> index 0a0bda7f686f..f0586108b41e 100644
> --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c
> +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c
> @@ -416,31 +416,43 @@ static void hdmi_disconnect(struct omap_dss_device *src,
>   	omapdss_device_disconnect(dst, dst->next);
>   }
>   
> -static int hdmi_read_edid(struct omap_dss_device *dssdev,
> -		u8 *edid, int len)
> +static struct edid *hdmi_read_edid(struct omap_dss_device *dssdev)
>   {
>   	struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
>   	bool need_enable;
> +	u8 *edid;
>   	int r;
>   
> +	edid = kzalloc(512, GFP_KERNEL);

512 bytes is enough for everyone? =)

Maybe still keep it as a define for clarity?

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 26/60] drm/omap: Detach from panels at remove time
  2019-08-13  7:28     ` Tomi Valkeinen
@ 2019-08-13 13:50       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-13 13:50 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

Hi Tomi,

On Tue, Aug 13, 2019 at 10:28:52AM +0300, Tomi Valkeinen wrote:
> On 07/07/2019 21:19, Laurent Pinchart wrote:
> > The omapdrm driver attaches to panels with drm_panel_attach() at probe
> > time but never calls drm_panel_detach(). Fix it.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   drivers/gpu/drm/omapdrm/omap_drv.c | 24 +++++++++++++++++++-----
> >   1 file changed, 19 insertions(+), 5 deletions(-)
> 
> drm_panel_detach() is called in omap_disconnect_pipelines().

I wonder how I missed that :-S I'll drop this patch.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional
  2019-08-13  7:48     ` Tomi Valkeinen
@ 2019-08-13 13:55       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-13 13:55 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

Hi Tomi,

On Tue, Aug 13, 2019 at 10:48:01AM +0300, Tomi Valkeinen wrote:
> On 07/07/2019 21:19, Laurent Pinchart wrote:
> > As part of the move to drm_bridge ops for some of the internal encoders
> > will be removed. Make them optional in the driver to ease the
> > transition.
> 
> I don't seem to be able to decipher the first sentence. Is there a word 
> or two missing from it?

Indeed. I've rewritten this as

As part of the move to drm_bridge ops, the dssdev ops will become empty
for some of the internal encoders. Make them optional in the driver to
allow them to be removed completely, easing the transition.

> Other than that:
> 
> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 28/60] drm/omap: Factor out display type to connector type conversion
  2019-08-13  7:32     ` Tomi Valkeinen
@ 2019-08-13 15:18       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-13 15:18 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

Hi Tomi,

On Tue, Aug 13, 2019 at 10:32:10AM +0300, Tomi Valkeinen wrote:
> On 07/07/2019 21:19, Laurent Pinchart wrote:
> > Move the code that computes the DRM connector type for the
> > omapdss_device display type to a new omapdss_device_connector_type()
> > function for later reuse.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   drivers/gpu/drm/omapdrm/dss/base.c       | 23 +++++++++++++++++++++++
> >   drivers/gpu/drm/omapdrm/dss/omapdss.h    |  1 +
> >   drivers/gpu/drm/omapdrm/omap_connector.c | 19 +------------------
> >   3 files changed, 25 insertions(+), 18 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/omapdrm/dss/base.c b/drivers/gpu/drm/omapdrm/dss/base.c
> > index a1970b9db6ab..cae5687822e2 100644
> > --- a/drivers/gpu/drm/omapdrm/dss/base.c
> > +++ b/drivers/gpu/drm/omapdrm/dss/base.c
> > @@ -285,6 +285,29 @@ void omapdss_device_post_disable(struct omap_dss_device *dssdev)
> >   }
> >   EXPORT_SYMBOL_GPL(omapdss_device_post_disable);
> >   
> > +unsigned int omapdss_device_connector_type(enum omap_display_type type)
> > +{
> > +	switch (type) {
> > +	case OMAP_DISPLAY_TYPE_HDMI:
> > +		return DRM_MODE_CONNECTOR_HDMIA;
> > +	case OMAP_DISPLAY_TYPE_DVI:
> > +		return DRM_MODE_CONNECTOR_DVID;
> > +	case OMAP_DISPLAY_TYPE_DSI:
> > +		return DRM_MODE_CONNECTOR_DSI;
> > +	case OMAP_DISPLAY_TYPE_DPI:
> > +	case OMAP_DISPLAY_TYPE_DBI:
> > +		return DRM_MODE_CONNECTOR_DPI;
> > +	case OMAP_DISPLAY_TYPE_VENC:
> > +		/* TODO: This could also be composite */
> > +		return DRM_MODE_CONNECTOR_SVIDEO;
> > +	case OMAP_DISPLAY_TYPE_SDI:
> > +		return DRM_MODE_CONNECTOR_LVDS;
> > +	default:
> > +		return DRM_MODE_CONNECTOR_Unknown;
> > +	}
> > +}
> > +EXPORT_SYMBOL_GPL(omapdss_device_connector_type);
> 
> Why do we need to export this? In the end enum omap_display_type should 
> go away or be private to omapdrm, right?

Eventually :-) The function is implemented in dss/base.c and used in
omap_connector.c in this patch, so it has to be exported. However, patch
"drm/omap: Simplify connector implementation" then removes the need to
export the function, so I'll remove the EXPORT_SYMBOL_GPL in that patch.

> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-10 22:43                           ` Laurent Pinchart
@ 2019-08-14  6:23                             ` Andrzej Hajda
  2019-08-14 10:04                               ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-08-14  6:23 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Laurent,

Sorry for late response.


On 11.08.2019 00:43, Laurent Pinchart wrote:
> Hi Andrzej,
>
> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
>> On 08.08.2019 21:32, Laurent Pinchart wrote:
>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>> Hi Laurent,
>>>>>>>>>>>>
>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>>>>>>>> panel is very painful.
>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
>>>>>>>>>>>
>>>>>>>>>>>> More comments inlined.
>>>>>>>>>>>>
>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>>>>>>>> data:
>>>>>>>>>>>>>
>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>>>>>>>   retrieval operations
>>>>>>>>>>>>> - Bitmask of supported operations
>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>>>>>>>> operation's callback?
>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>>>>>>>>
>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>>>>>>>> add, generally good excuse to not have to think through the design between
>>>>>>>>>>> different parts of drivers - "just" add another flag.
>>>>>>>>>>>
>>>>>>>>>>>>> - Bridge output type
>>>>>>>>>>>>>
>>>>>>>>>>>>> Add and document these.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>>>>>>>> bridges.
>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>>>>>>>> right? More comments about it later.
>>>>>>>>>>>>
>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>>>>>>>> ---
>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>
>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>>>>>>>   */
>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>>>>>>>  {
>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>>>>>>>> +
>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>>>>>>>  }
>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>>>>>>>  
>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>  }
>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>>>>>>>  
>>>>>>>>>>>>> +/**
>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>> + * @cb: hot-plug detection callback
>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>>>>>>>> + * the bridge.
>>>>>>>>>>>>> + */
>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>>>>>>>> bridge attach:
>>>>>>>>>>>>
>>>>>>>>>>>> bridge->hpd_cb = cb;
>>>>>>>>>>>>
>>>>>>>>>>>> bridge->hpd_data = data;
>>>>>>>>>>>>
>>>>>>>>>>>> ret = drm_bridge_attach(...);
>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>>>>>>>
>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>>>>>>>> without big sacrifices.
>>>>>>>>>>>>
>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>>>>>>>
>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>> +			   void *data)
>>>>>>>>>>>>> +{
>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>>>>>>>> +		goto unlock;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>>>>>>>> +	bridge->hpd_data = data;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +unlock:
>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +/**
>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>>>>>>>> + * output status change occurs.
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>> + */
>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>>>>>>>> +{
>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +/**
>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>> + * @status: output connection status
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>>>>>>>> + *
>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>>>>>>>> + */
>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>> +			   enum drm_connector_status status)
>>>>>>>>>>>>> +{
>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>> +	if (bridge->hpd_cb)
>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>>>>>>>
>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>>>>>>>> 		if (tmp_bridge == bridge)
>>>>>>>>>>> 			continue;
>>>>>>>>>>> 		if (bridge->hpd_notify);
>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>>>>>>>> 	}
>>>>>>>>>>>
>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>>>>>>>
>>>>>>>>>>> 	dev = bridge->dev
>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>>>>>>>
>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>>>>>>>> hpd they want/need.
>>>>>>>>>> As I understand you want to notify every member of the pipeline.
>>>>>>>>>>
>>>>>>>>>> I think it should be enough to notify only the source, and then source
>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
>>>>>>>>>>
>>>>>>>>>> It looks more generic for me.
>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>>>>>>>> the one from Laurent? Kinda confused here.
>>>>>>>> Regarding general idea:
>>>>>>>>
>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
>>>>>>>> source.
>>>>>>>>
>>>>>>>> 2. Your is to notify all other bridges and encoder.
>>>>>>>>
>>>>>>>> And I prefer 1st approach, why:
>>>>>>>>
>>>>>>>> - the source can decide if/when and to who propagate the signal,
>>>>>>>>
>>>>>>>> - is more generic, for example if bridge send signal to two
>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
>>>>>> If there will be two consumers, there will be two bridge attachments,
>>>>>> thus there will be two notifications, it should work.
>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
>>>>> callback is registered on the produce bridge, not on the consumer bridge
>>>>> (or I'm totallly misreading what Laurent does here).
>>>> I have assumed that if devices exposes two hardware sink interfaces it
>>>> will expose two separate bridges - of course it will not work with
>>>> "bridge chaining" thing, but this is a different story.
>>> Daniel is right that the current implementation only allows one
>>> consumer. This is however not a limitation of the API, but of its
>>> implementation, as I only needed a single consumer. The helpers in this
>>> series ensure that neither the consumer nor the producer poke in the
>>> drm_bridge structure to call back to the HPD handler:
>>>
>>> - The consumer calls drm_bridge_hpd_enable() and
>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
>>>   behaviour if desired without changes to the consumer.
>>>
>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
>>>   which could also easily accommodate reference-counting in the drm
>>>   bridge core without changes to the producer.
>>>
>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
>>>   easily be extended to support multiple consumers without changes to
>>>   the producer.
>>>
>>> This is actually my second version of the HPD mechanism. The first
>>> version was never posted, poked into drm_bridge, and required the
>>> producer to be aware of the callbacks. After discussing this privately
>>> with Daniel, I came up with the implementation in this series that,
>>> while not supporting multiple consumers now, makes it easy to extend
>>> later without minimal effort.
>>>
>>> Daniel's proposed implementation above looks reasonable to me, provided
>>> we can iterate over the bridges in an order that don't depend on the
>>> position of the producer in the chain (should be easy to solve by
>>> starting at the encoder for instance). It however looks a bit like a
>>> midlayer to me :-) That's why I have a similar implementation in the
>>> connector-bridge helper, which could be extended to call
>>> encoder->helper_private->bridge_hpd_notify() and
>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
>>> drm_bridge_hpd_notify() would on the other hand set the notification
>>> sequence towards the encoder and driver in stone. Daniel, do you think
>>> that would be better ?
>>>
>>> I would like to remind everybody that this series isn't the last I will
>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
>>> open to suggestions, and can address problems on top of these patches,
>>> provided obviously that this series doesn't go in the wrong direction.
>>> I'm of course also willing to rework this series, but given the amount
>>> of work we have in the drm_bridge realm, I can't fix everything in one
>>> go :-)
>>>
>>>>>>>> - it resembles hardware wires :)
>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
>>>>>>> interested in that hpd singal. This includes:
>>>>>>> - Other bridges, e.g. if they provide CEC support.
>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
>>>>>>> - Overall driver, so it can update the modes/connector status and send the
>>>>>>>   uevent to the driver.
>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
>>>>>>>   shut down/re-enable the pipe because $reasons.
>>>>>>>  
>>>>>>> That's at least my understanding from lots of chats with Laurent about
>>>>>>> what he wants to do here.
>>> That's correct, and that's what I was trying to implement :-) The
>>> notification, in this patch series, goes from the producer bridge to a
>>> central place (namely the connector, with a helper implementation
>>> available as part of this series, but custom implementations in display
>>> drivers are fine if needed) that then dispatches the notification to all
>>> bridges (through the .lost_hotplug() operation, which we could replace
>>> by an .hpd_notify() operation) for the first two purposes listed above,
>>> and then to the overall driver. The only thing I don't support yet is
>>> dispatching to the display pipeline (item 4 in the list above) as I had
>>> no need for that, and didn't want to develop an API with no user. This
>>> would however not be difficult to do when needed, the need is taken into
>>> account in the proposed implementation.
>>>
>>>>>> I do not know the full picture, but the solution where particular bridge
>>>>>> notifies everything unconditionally seems to me much less flexible.
>>>>>>
>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
>>>>>> will mimic your scenario.
>>>>>>
>>>>>> But there are also other scenarios where bridge does not want to
>>>>>> propagate signal, because for example:
>>>>>>
>>>>>> - it wants to wait for other sinks to wake up,
>>>>>>
>>>>> The other sink can just do that in their hpd callback.
>>>>>
>>>>>> - it propagates HPD signal via hardware wire,
>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
>>>>> the wire/hw hpd interrupt.
>>>>>
>>>> If it should ignore HPD, why it should receive it at all - it is
>>>> unnecessary noise. And I am afraid with more complicated pipelines it
>>>> will be impossible for particular component (bridge/encoder/whatever) to
>>>> distinguish if HPD notification which came from non-directly connected
>>>> component should be ignored or not.
>>>>
>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
>>>>>> device.
>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
>>>>> board?
>>>> Bridge can have external connectors, and the user can connect there
>>>> anything.
>>>>
>>>>>> In general HPD is input signal for notify of state changes on particular
>>>>>> bus, in case of typical video bridge on its output video bus.
>>>>>>
>>>>>> In case of bridges they have also input video buses, and they can send
>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
>>>>>> if for most cases they looks similar.
>>>>> Ah, I think this is a problem we will eventually have. But it's not
>>>>> something we're currently solving here at all I think.
>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
>>>> line, so this is not something from far future. And I guess with HPD
>>>> broadcasting it could be racy/error prone, for example EDID reading can
>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
>>>> controller via hw wires also).
>>>>
>>>>>>>> And regarding implementation:
>>>>>>>>
>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>>>>>>>
>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>>>>>>>
>>>>>>>> Your proposition is more straightforward, but if we want to notify only
>>>>>>>> source we should locate it by parsing notification chain (what about
>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
>>>>>>>>
>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
>>>>>>> Uh I think we're not talking about the same thing really. My understanding
>>>>>>> is that this callback is if someone (outside of this bridge) is interested
>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
>>>>>>> listener.
>>>>>> Do we have real life examples?
>>>>>>
>>>>>> I want to distinguish two situations:
>>>>>>
>>>>>> - another device wants to know if input bus of the bridge has changed state,
>>>>>>
>>>>>> - another device wants to know if output bus of the bridge has changed
>>>>>> state.
>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
>>>>> bridges can exchange state and information about each another. hpd is
>>>>> about the physical world, i.e. "is there a cable plugged into the port
>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
>>>>> fun stuff like that, we have the atomic_check machinery for this.
>>>> My question was if we have real examples that upstream device requires
>>>> knowledge about state of output line of the bridge?
>>>>
>>>> To be more precise, we have following display pipeline:
>>>>
>>>> A-->B-->C
>>>>
>>>> And C sends HPD to B (ie signal that state of line between B and C
>>>> changed). Does A really wants to know this information? or it should
>>>> just need to know if state of line A-->B changed?
>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
>>> ESD protector and level shifter, and C is the physical HDMI connector.
>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
>>> be notified in order to reset the CEC state machine. One could however
>>> argue that in that case the A-B link state changes too, but the
>>> important part is that HPD detection is not performed by A, while A
>>> needs to be informed of lost hotplug.
>> I have no full picture but I guess in this case C sends HPD to B using
>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
>> say that B does not participate in HPD transmission/forwarding,
> No, in this case A doesn't receive any hardware HPD signal, it requires
> HPD notification through software.
>
>> some shifters with 'advanced power saving' can even perform wake-up of
>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
>> to A is indeed different than HPD sent from C to B.
>>
>> Btw, with the above logic of propagation of HPD callback (proposed by
>> Daniel) I guess it will work this way:
>>
>> - A will receive HPD signal via HW,
>>
>> - then B and C will receive HPD callback via framework.
>>
>> Am I right?
> It's the other way around.
>
> In this case the HPD signal from the connector (C) is routed to an input
> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
> IRQ and receive the hardware HPD notification. The driver for the HDMI
> encoder (A) needs to receive HPD notification in software, through the
> framework.


If this is GPIO I wonder why do not query this gpio by encoder directly,
rules of ownership of such gpios seems to be grey area, so in such case
I would advise to put it in the driver who really needs it.

This way it will be much simpler.


Going back to HPD notifications, as I said earlier broadcasting HPD
notification unconditionally to every member of the chain with hope that
the member will be able to filter-out undesired notification seems to me
incorrect - maybe it can solve some problems but is not flexible enough
to be usable in other scenarios.

If my arguments do not convince you please just continue with your
ideas, we can always add NO_HPD_BROADCAST somewhere :)


Regards

Andrzej


>
>>>>>>> You seem to have some other idea here.
>>>>>>>
>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>>>>>>>> +
>>>>>>>>>>>>>  #ifdef CONFIG_OF
>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>>>>>>>  
>>>>>>>>>>>>> -#include <linux/list.h>
>>>>>>>>>>>>>  #include <linux/ctype.h>
>>>>>>>>>>>>> +#include <linux/list.h>
>>>>>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>>>>>>>  
>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>>>>>>>  	 */
>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @detect:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @get_modes:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>>>>>>>> +			 struct drm_connector *connector);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @get_edid:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>>>>>>>> +	 * output.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>>>>>>>> +				 struct drm_connector *connector);
>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>>>>>>>> presence of another one?
>>>>>>>>>>>>
>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>>>>>>>> and connector->edid correctly.
>>>>>>>>>>>
>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>>>>>>>> correctly updated.
>>>>>>>>>>>
>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>>>>>>>> edid, which is not so awesome :-)
>>>>>>>>>>>
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @lost_hotplug:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>>>>>>>> +	 * HDMI bridges.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @hpd_enable:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>>>>>>>> +	 * @hpd_disable.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @hpd_disable:
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>>>>>>>> +	 * connection status occurs.
>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>  };
>>>>>>>>>>>>>  
>>>>>>>>>>>>>  /**
>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>>>>>>>  	bool dual_link;
>>>>>>>>>>>>>  };
>>>>>>>>>>>>>  
>>>>>>>>>>>>> +/**
>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>>>>>>>> + */
>>>>>>>>>>>>> +enum drm_bridge_ops {
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>>>>>>>> +};
>>>>>>>>>>>>> +
>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>>>>>>>   */
>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>>>>>>>  	void *driver_private;
>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>>>>>>>> +	 * identifies the type of connected display.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	int type;
>>>>>>>>>>>>> +	/** private: */
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	struct mutex hpd_mutex;
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>>>>>>>> +	 * @hpd_cb.
>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>> +	void *hpd_data;
>>>>>>>>>>>>>  };
>>>>>>>>>>>>>  
>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>>>>>>>  
>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>> +			   void *data);
>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>> +			   enum drm_connector_status status);
>>>>>>>>>>>>> +
>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>>>>>>>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-08-08 17:36         ` Andrzej Hajda
  2019-08-08 18:50           ` Laurent Pinchart
@ 2019-08-14  8:18           ` Daniel Vetter
  2019-08-14  9:55             ` Laurent Pinchart
  1 sibling, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14  8:18 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen,
	Laurent Pinchart, Sean Paul

On Thu, Aug 08, 2019 at 07:36:49PM +0200, Andrzej Hajda wrote:
> On 08.08.2019 16:25, Laurent Pinchart wrote:
> > Hi Andrzej,
> >
> > On Wed, Jul 17, 2019 at 08:39:47AM +0200, Andrzej Hajda wrote:
> >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>> Most bridge drivers create a DRM connector to model the connector at the
> >>> output of the bridge. This model is historical and has worked pretty
> >>> well so far, but causes several issues:
> >>>
> >>> - It prevents supporting more complex display pipelines where DRM
> >>> connector operations are split over multiple components. For instance a
> >>> pipeline with a bridge connected to the DDC signals to read EDID data,
> >>> and another one connected to the HPD signal to detect connection and
> >>> disconnection, will not be possible to support through this model.
> >>>
> >>> - It requires every bridge driver to implement similar connector
> >>> handling code, resulting in code duplication.
> >>>
> >>> - It assumes that a bridge will either be wired to a connector or to
> >>> another bridge, but doesn't support bridges that can be used in both
> >>> positions very well (although there is some ad-hoc support for this in
> >>> the analogix_dp bridge driver).
> >>>
> >>> In order to solve these issues, ownership of the connector should be
> >>> moved to the display controller driver (where it can be implemented
> >>> using helpers provided by the core).
> >>>
> >>> Extend the bridge API to allow disabling connector creation in bridge
> >>> drivers as a first step towards the new model. The new create_connector
> >>> argument to the bridge .attach() operation tells the bridge driver
> >>> whether to create a connector. Set the argument to true unconditionally,
> >>> and modify all existing bridge drivers to return an error when connector
> >>> creation is not requested as they don't support this feature yet.
> >>>
> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> ---
> >>>  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
> >>>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
> >>>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
> >>>  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
> >>>  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
> >>>  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
> >>>  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
> >>>  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
> >>>  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
> >>>  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
> >>>  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
> >>>  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
> >>>  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
> >>>  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
> >>>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
> >>>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
> >>>  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
> >>>  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
> >>>  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
> >>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
> >>>  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
> >>>  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
> >>>  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
> >>>  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
> >>>  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
> >>>  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
> >>>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
> >>>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
> >>>  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
> >>>  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
> >>>  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
> >>>  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
> >>>  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
> >>>  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
> >>>  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
> >>>  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
> >>>  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
> >>>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
> >>>  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
> >>>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
> >>>  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
> >>>  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
> >>>  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
> >>>  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
> >>>  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
> >>>  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
> >>>  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
> >>>  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
> >>>  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
> >>>  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
> >>>  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
> >>>  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
> >>>  include/drm/drm_bridge.h                                 | 4 ++--
> >>>  53 files changed, 140 insertions(+), 67 deletions(-)
> >>>
> >>> diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> >>> index 98aac743cc26..739f2358f1d5 100644
> >>> --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
> >>> +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> >>> @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
> >>>  		return ret;
> >>>  
> >>>  	/* Link drm_bridge to encoder */
> >>> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> >>> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> >> Few suggestions:
> >>
> >> 1. Maybe it would be more convenient to add flags argument instead of bool:
> >>
> >> - code should be more readable: ret = drm_bridge_attach(encoder, bridge,
> >> NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)
> >>
> >> - it can be easily expanded later with other flags, there at least two
> >> drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.
> > Please note that I think this flag should disappear once drivers get
> > converted to the new model. This will however take some time. I'm not
> > opposed to turning the book into a flag though. I was hoping to receive
> > more review comments on this particular patch, but as that's not the
> > case, I can already proceed with the change.
> >
> > What would the DRM_BRIDGE_FLAG_NO_CHAINING flag be used for ?
> 
> 
> To avoid setting encoder->bridge or previous_bridge->next field to
> attached bridge (last lines of drm_bridge_attach code).
> 
> This is for sure the case of exynos_dsi and vc4_dsi, but I guess it can
> affect other drivers as well, probably they just use other workarounds
> or have more flexible hardware.
> 
> Generally idea that order of calling
> pre_enable/enable/disable/post_disable callbacks in chained
> encoder/bridges is fixed is wrong IMHO, only video source component
> knows in which moment it should enable its sink and if it should do
> something after. And it does not work at all with sources/sinks having
> more than one output/input.

This doesn't make sense to me ... if you don't want to have a chained
bridge, don't attach it? You can just open-code the attach and call the
bridge functions directly when appropriate. I think that's the better
long-term plan than trying to have flags for every possible topology
existing out there ...
-Daniel

> 
> 
> >
> >> 2. If the patch can be applied atomically it is OK as is, if not you can
> >> use preprocessor vararg magic to support new and old syntax, sth like:
> >>
> >> #define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
> >> __drm_bridge_attach(encoder, bridge, prev, flags)
> >>
> >> #define drm_bridge_attach(encoder, bridge, prev, optarg...)
> >> _drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)
> > Good point. I'll try to do this atomically, but if it fails I'll follow
> > your suggestion.
> >
> >> 3. Maybe more convenient would be to just set the flags directly before
> >> attachment:
> >>
> >>     bridge->dont_create_connector = true;
> >>
> >>     ret = drm_bridge_attach(encoder, bridge, NULL);
> >>
> >>     This way it will be still expandable, and less changes.
> > Bridges that are chained would need to set the dont_create_connector
> > flag of the next bridge. It would be a bit ugly, but would make this
> > patch smaller. On the other hand we would need to keep the if
> > (!create_connector) check in the .attach() handlers, and it would be
> > easier to miss it in bridge drivers (current or new) than with an
> > explicit argument to the .attach() operation. I would thus have a
> > preference for the new argument to .attach(). Especially if it can help
> > you with DRM_BRIDGE_FLAG_NO_CHAINING :-)
> 
> bridge->dont_chain would work as well :)
> 
> Btw I wonder if it could be possible to disallow creating connectors at all by new bridges - it would speed-up transition.
> 
> 
> Another long term idea. Since bridges can be attached to:
> - encoder,
> - another bridge,
> - crtc (I have one example, but I guess there could be more),
> - even before crtc (image postprocessing)
> And since bridge output goes to:
> - another bridge,
> - panel.
> 
> Wouldn't be better to create drm_source and drm_sink (do not respond with xkcd picture :) ):
> - drm_source will be embedded in source device context,
> - drm_sink will be embedded in sink device context.
> We could make then transitions of bridges to drm_sink with drm_source embeded in its context, and panels to drm_sink.
> This way we could drop these crazy constructs:
> - if sink is panel then do sth, elsif is bridge then do sth_else,
> - if src is bridge then do sth, elsif is encoder ... elsif ....
> - helpers of_find_panel_or_bridge,
> - drm_panel_bridge,
> Also we could implement easily multi input/output bridges/panels/crtcs whatever.
> And hpd callbacks you have proposed in another patch would fit better to drm_source.ops.
> ...
> 
> 
> Regards
> Andrzej
> 
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-08-14  8:18           ` Daniel Vetter
@ 2019-08-14  9:55             ` Laurent Pinchart
  2019-08-14 12:24               ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-14  9:55 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Wed, Aug 14, 2019 at 10:18:40AM +0200, Daniel Vetter wrote:
> On Thu, Aug 08, 2019 at 07:36:49PM +0200, Andrzej Hajda wrote:
> > On 08.08.2019 16:25, Laurent Pinchart wrote:
> > > On Wed, Jul 17, 2019 at 08:39:47AM +0200, Andrzej Hajda wrote:
> > >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>> Most bridge drivers create a DRM connector to model the connector at the
> > >>> output of the bridge. This model is historical and has worked pretty
> > >>> well so far, but causes several issues:
> > >>>
> > >>> - It prevents supporting more complex display pipelines where DRM
> > >>> connector operations are split over multiple components. For instance a
> > >>> pipeline with a bridge connected to the DDC signals to read EDID data,
> > >>> and another one connected to the HPD signal to detect connection and
> > >>> disconnection, will not be possible to support through this model.
> > >>>
> > >>> - It requires every bridge driver to implement similar connector
> > >>> handling code, resulting in code duplication.
> > >>>
> > >>> - It assumes that a bridge will either be wired to a connector or to
> > >>> another bridge, but doesn't support bridges that can be used in both
> > >>> positions very well (although there is some ad-hoc support for this in
> > >>> the analogix_dp bridge driver).
> > >>>
> > >>> In order to solve these issues, ownership of the connector should be
> > >>> moved to the display controller driver (where it can be implemented
> > >>> using helpers provided by the core).
> > >>>
> > >>> Extend the bridge API to allow disabling connector creation in bridge
> > >>> drivers as a first step towards the new model. The new create_connector
> > >>> argument to the bridge .attach() operation tells the bridge driver
> > >>> whether to create a connector. Set the argument to true unconditionally,
> > >>> and modify all existing bridge drivers to return an error when connector
> > >>> creation is not requested as they don't support this feature yet.
> > >>>
> > >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>> ---
> > >>>  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
> > >>>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
> > >>>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
> > >>>  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
> > >>>  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
> > >>>  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
> > >>>  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
> > >>>  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
> > >>>  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
> > >>>  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
> > >>>  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
> > >>>  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
> > >>>  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
> > >>>  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
> > >>>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
> > >>>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
> > >>>  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
> > >>>  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
> > >>>  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
> > >>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
> > >>>  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
> > >>>  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
> > >>>  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
> > >>>  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
> > >>>  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
> > >>>  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
> > >>>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
> > >>>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
> > >>>  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
> > >>>  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
> > >>>  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
> > >>>  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
> > >>>  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
> > >>>  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
> > >>>  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
> > >>>  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
> > >>>  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
> > >>>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
> > >>>  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
> > >>>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
> > >>>  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
> > >>>  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
> > >>>  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
> > >>>  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
> > >>>  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
> > >>>  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
> > >>>  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
> > >>>  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
> > >>>  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
> > >>>  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
> > >>>  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
> > >>>  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
> > >>>  include/drm/drm_bridge.h                                 | 4 ++--
> > >>>  53 files changed, 140 insertions(+), 67 deletions(-)
> > >>>
> > >>> diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > >>> index 98aac743cc26..739f2358f1d5 100644
> > >>> --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > >>> +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > >>> @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
> > >>>  		return ret;
> > >>>  
> > >>>  	/* Link drm_bridge to encoder */
> > >>> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > >>> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> > >> Few suggestions:
> > >>
> > >> 1. Maybe it would be more convenient to add flags argument instead of bool:
> > >>
> > >> - code should be more readable: ret = drm_bridge_attach(encoder, bridge,
> > >> NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)
> > >>
> > >> - it can be easily expanded later with other flags, there at least two
> > >> drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.
> > >
> > > Please note that I think this flag should disappear once drivers get
> > > converted to the new model. This will however take some time. I'm not
> > > opposed to turning the book into a flag though. I was hoping to receive
> > > more review comments on this particular patch, but as that's not the
> > > case, I can already proceed with the change.
> > >
> > > What would the DRM_BRIDGE_FLAG_NO_CHAINING flag be used for ?
> > 
> > To avoid setting encoder->bridge or previous_bridge->next field to
> > attached bridge (last lines of drm_bridge_attach code).
> > 
> > This is for sure the case of exynos_dsi and vc4_dsi, but I guess it can
> > affect other drivers as well, probably they just use other workarounds
> > or have more flexible hardware.
> > 
> > Generally idea that order of calling
> > pre_enable/enable/disable/post_disable callbacks in chained
> > encoder/bridges is fixed is wrong IMHO, only video source component
> > knows in which moment it should enable its sink and if it should do
> > something after. And it does not work at all with sources/sinks having
> > more than one output/input.
> 
> This doesn't make sense to me ... if you don't want to have a chained
> bridge, don't attach it? You can just open-code the attach and call the
> bridge functions directly when appropriate. I think that's the better
> long-term plan than trying to have flags for every possible topology
> existing out there ...

I think the whole topology should be known to the display driver, so the
next (and possibly previous if we later need it) pointers should be set
in all cases. Otherwise we can't, for instance, notify all bridges of
HPD events. Sure, a bridge could decide not to attach at all, but then
it would have to reimplement lots of logic to forward everything
manually, when the need for manual forwarding is usually limited to a
certain category of operations.

> > >> 2. If the patch can be applied atomically it is OK as is, if not you can
> > >> use preprocessor vararg magic to support new and old syntax, sth like:
> > >>
> > >> #define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
> > >> __drm_bridge_attach(encoder, bridge, prev, flags)
> > >>
> > >> #define drm_bridge_attach(encoder, bridge, prev, optarg...)
> > >> _drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)
> > > Good point. I'll try to do this atomically, but if it fails I'll follow
> > > your suggestion.
> > >
> > >> 3. Maybe more convenient would be to just set the flags directly before
> > >> attachment:
> > >>
> > >>     bridge->dont_create_connector = true;
> > >>
> > >>     ret = drm_bridge_attach(encoder, bridge, NULL);
> > >>
> > >>     This way it will be still expandable, and less changes.
> > > Bridges that are chained would need to set the dont_create_connector
> > > flag of the next bridge. It would be a bit ugly, but would make this
> > > patch smaller. On the other hand we would need to keep the if
> > > (!create_connector) check in the .attach() handlers, and it would be
> > > easier to miss it in bridge drivers (current or new) than with an
> > > explicit argument to the .attach() operation. I would thus have a
> > > preference for the new argument to .attach(). Especially if it can help
> > > you with DRM_BRIDGE_FLAG_NO_CHAINING :-)
> > 
> > bridge->dont_chain would work as well :)
> > 
> > Btw I wonder if it could be possible to disallow creating connectors at all by new bridges - it would speed-up transition.
> > 
> > 
> > Another long term idea. Since bridges can be attached to:
> > - encoder,
> > - another bridge,
> > - crtc (I have one example, but I guess there could be more),
> > - even before crtc (image postprocessing)
> > And since bridge output goes to:
> > - another bridge,
> > - panel.
> > 
> > Wouldn't be better to create drm_source and drm_sink (do not respond with xkcd picture :) ):
> > - drm_source will be embedded in source device context,
> > - drm_sink will be embedded in sink device context.
> > We could make then transitions of bridges to drm_sink with drm_source embeded in its context, and panels to drm_sink.
> > This way we could drop these crazy constructs:
> > - if sink is panel then do sth, elsif is bridge then do sth_else,
> > - if src is bridge then do sth, elsif is encoder ... elsif ....
> > - helpers of_find_panel_or_bridge,
> > - drm_panel_bridge,
> > Also we could implement easily multi input/output bridges/panels/crtcs whatever.
> > And hpd callbacks you have proposed in another patch would fit better to drm_source.ops.
> > ...
> > 
> > 
> > Regards
> > Andrzej
> > 
> > 
> 
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14  6:23                             ` Andrzej Hajda
@ 2019-08-14 10:04                               ` Laurent Pinchart
  2019-08-14 12:40                                 ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-14 10:04 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
> Hi Laurent,
> 
> Sorry for late response.

No worries.

> On 11.08.2019 00:43, Laurent Pinchart wrote:
> > On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
> >> On 08.08.2019 21:32, Laurent Pinchart wrote:
> >>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >>>> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>> Hi Laurent,
> >>>>>>>>>>>>
> >>>>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>>>> panel is very painful.
> >>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>>>
> >>>>>>>>>>>> More comments inlined.
> >>>>>>>>>>>>
> >>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>>>> data:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>>>   retrieval operations
> >>>>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>>>> operation's callback?
> >>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>>>
> >>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>>>
> >>>>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> Add and document these.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>>>> bridges.
> >>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>>>
> >>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>>>> ---
> >>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>>>   */
> >>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>>>  {
> >>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>>>  }
> >>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  }
> >>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>>>> + * the bridge.
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>>>> bridge attach:
> >>>>>>>>>>>>
> >>>>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>>>
> >>>>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>>>
> >>>>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>>>
> >>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>>>> without big sacrifices.
> >>>>>>>>>>>>
> >>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>>>
> >>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>> +			   void *data)
> >>>>>>>>>>>>> +{
> >>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +unlock:
> >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +}
> >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>>>> +{
> >>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +}
> >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>>>> +{
> >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>>>
> >>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>>>> 			continue;
> >>>>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>>>> 	}
> >>>>>>>>>>>
> >>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>>>
> >>>>>>>>>>> 	dev = bridge->dev
> >>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>>>
> >>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>>>> hpd they want/need.
> >>>>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>>>
> >>>>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>>>
> >>>>>>>>>> It looks more generic for me.
> >>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>>> Regarding general idea:
> >>>>>>>>
> >>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>>>> source.
> >>>>>>>>
> >>>>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>>>
> >>>>>>>> And I prefer 1st approach, why:
> >>>>>>>>
> >>>>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>>>
> >>>>>>>> - is more generic, for example if bridge send signal to two
> >>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>>> If there will be two consumers, there will be two bridge attachments,
> >>>>>> thus there will be two notifications, it should work.
> >>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>>>> callback is registered on the produce bridge, not on the consumer bridge
> >>>>> (or I'm totallly misreading what Laurent does here).
> >>>> I have assumed that if devices exposes two hardware sink interfaces it
> >>>> will expose two separate bridges - of course it will not work with
> >>>> "bridge chaining" thing, but this is a different story.
> >>> Daniel is right that the current implementation only allows one
> >>> consumer. This is however not a limitation of the API, but of its
> >>> implementation, as I only needed a single consumer. The helpers in this
> >>> series ensure that neither the consumer nor the producer poke in the
> >>> drm_bridge structure to call back to the HPD handler:
> >>>
> >>> - The consumer calls drm_bridge_hpd_enable() and
> >>>   drm_bridge_hpd_disable(), which could offer a reference-counted
> >>>   behaviour if desired without changes to the consumer.
> >>>
> >>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >>>   which could also easily accommodate reference-counting in the drm
> >>>   bridge core without changes to the producer.
> >>>
> >>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >>>   easily be extended to support multiple consumers without changes to
> >>>   the producer.
> >>>
> >>> This is actually my second version of the HPD mechanism. The first
> >>> version was never posted, poked into drm_bridge, and required the
> >>> producer to be aware of the callbacks. After discussing this privately
> >>> with Daniel, I came up with the implementation in this series that,
> >>> while not supporting multiple consumers now, makes it easy to extend
> >>> later without minimal effort.
> >>>
> >>> Daniel's proposed implementation above looks reasonable to me, provided
> >>> we can iterate over the bridges in an order that don't depend on the
> >>> position of the producer in the chain (should be easy to solve by
> >>> starting at the encoder for instance). It however looks a bit like a
> >>> midlayer to me :-) That's why I have a similar implementation in the
> >>> connector-bridge helper, which could be extended to call
> >>> encoder->helper_private->bridge_hpd_notify() and
> >>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> >>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> >>> drm_bridge_hpd_notify() would on the other hand set the notification
> >>> sequence towards the encoder and driver in stone. Daniel, do you think
> >>> that would be better ?
> >>>
> >>> I would like to remind everybody that this series isn't the last I will
> >>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> >>> open to suggestions, and can address problems on top of these patches,
> >>> provided obviously that this series doesn't go in the wrong direction.
> >>> I'm of course also willing to rework this series, but given the amount
> >>> of work we have in the drm_bridge realm, I can't fix everything in one
> >>> go :-)
> >>>
> >>>>>>>> - it resembles hardware wires :)
> >>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>>>> interested in that hpd singal. This includes:
> >>>>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>>>   uevent to the driver.
> >>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>>>  
> >>>>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>>>> what he wants to do here.
> >>> That's correct, and that's what I was trying to implement :-) The
> >>> notification, in this patch series, goes from the producer bridge to a
> >>> central place (namely the connector, with a helper implementation
> >>> available as part of this series, but custom implementations in display
> >>> drivers are fine if needed) that then dispatches the notification to all
> >>> bridges (through the .lost_hotplug() operation, which we could replace
> >>> by an .hpd_notify() operation) for the first two purposes listed above,
> >>> and then to the overall driver. The only thing I don't support yet is
> >>> dispatching to the display pipeline (item 4 in the list above) as I had
> >>> no need for that, and didn't want to develop an API with no user. This
> >>> would however not be difficult to do when needed, the need is taken into
> >>> account in the proposed implementation.
> >>>
> >>>>>> I do not know the full picture, but the solution where particular bridge
> >>>>>> notifies everything unconditionally seems to me much less flexible.
> >>>>>>
> >>>>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>>>> will mimic your scenario.
> >>>>>>
> >>>>>> But there are also other scenarios where bridge does not want to
> >>>>>> propagate signal, because for example:
> >>>>>>
> >>>>>> - it wants to wait for other sinks to wake up,
> >>>>>>
> >>>>> The other sink can just do that in their hpd callback.
> >>>>>
> >>>>>> - it propagates HPD signal via hardware wire,
> >>>>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>>>> the wire/hw hpd interrupt.
> >>>>>
> >>>> If it should ignore HPD, why it should receive it at all - it is
> >>>> unnecessary noise. And I am afraid with more complicated pipelines it
> >>>> will be impossible for particular component (bridge/encoder/whatever) to
> >>>> distinguish if HPD notification which came from non-directly connected
> >>>> component should be ignored or not.
> >>>>
> >>>>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>>>> device.
> >>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>>>> board?
> >>>> Bridge can have external connectors, and the user can connect there
> >>>> anything.
> >>>>
> >>>>>> In general HPD is input signal for notify of state changes on particular
> >>>>>> bus, in case of typical video bridge on its output video bus.
> >>>>>>
> >>>>>> In case of bridges they have also input video buses, and they can send
> >>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>>>> if for most cases they looks similar.
> >>>>> Ah, I think this is a problem we will eventually have. But it's not
> >>>>> something we're currently solving here at all I think.
> >>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >>>> line, so this is not something from far future. And I guess with HPD
> >>>> broadcasting it could be racy/error prone, for example EDID reading can
> >>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >>>> controller via hw wires also).
> >>>>
> >>>>>>>> And regarding implementation:
> >>>>>>>>
> >>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>>>
> >>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>>>
> >>>>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>>>> source we should locate it by parsing notification chain (what about
> >>>>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>>>
> >>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>>>> listener.
> >>>>>> Do we have real life examples?
> >>>>>>
> >>>>>> I want to distinguish two situations:
> >>>>>>
> >>>>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>>>
> >>>>>> - another device wants to know if output bus of the bridge has changed
> >>>>>> state.
> >>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>>>> bridges can exchange state and information about each another. hpd is
> >>>>> about the physical world, i.e. "is there a cable plugged into the port
> >>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>>>> fun stuff like that, we have the atomic_check machinery for this.
> >>>> My question was if we have real examples that upstream device requires
> >>>> knowledge about state of output line of the bridge?
> >>>>
> >>>> To be more precise, we have following display pipeline:
> >>>>
> >>>> A-->B-->C
> >>>>
> >>>> And C sends HPD to B (ie signal that state of line between B and C
> >>>> changed). Does A really wants to know this information? or it should
> >>>> just need to know if state of line A-->B changed?
> >>> There's one real life example, where A is an HDMI encoder, B is an HDMI
> >>> ESD protector and level shifter, and C is the physical HDMI connector.
> >>> When the HDMI cable is unplugged, the CEC controller part of A needs to
> >>> be notified in order to reset the CEC state machine. One could however
> >>> argue that in that case the A-B link state changes too, but the
> >>> important part is that HPD detection is not performed by A, while A
> >>> needs to be informed of lost hotplug.
> >> I have no full picture but I guess in this case C sends HPD to B using
> >> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
> >> say that B does not participate in HPD transmission/forwarding,
> > No, in this case A doesn't receive any hardware HPD signal, it requires
> > HPD notification through software.
> >
> >> some shifters with 'advanced power saving' can even perform wake-up of
> >> upstream pin logic after receiving HPD on downstream, so HPD sent from B
> >> to A is indeed different than HPD sent from C to B.
> >>
> >> Btw, with the above logic of propagation of HPD callback (proposed by
> >> Daniel) I guess it will work this way:
> >>
> >> - A will receive HPD signal via HW,
> >>
> >> - then B and C will receive HPD callback via framework.
> >>
> >> Am I right?
> 
> > It's the other way around.
> >
> > In this case the HPD signal from the connector (C) is routed to an input
> > of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
> > connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
> > IRQ and receive the hardware HPD notification. The driver for the HDMI
> > encoder (A) needs to receive HPD notification in software, through the
> > framework.
> 
> If this is GPIO I wonder why do not query this gpio by encoder directly,
> rules of ownership of such gpios seems to be grey area, so in such case
> I would advise to put it in the driver who really needs it.
> 
> This way it will be much simpler.

First to fall, multiple drivers may need to be informed of HPD events
coming from a GPIO, so we would need to duplicate it in multiple places,
and I don't think the GPIO framework allows acquiring a GPIO multiple
times.

Then, the GPIO is described in DT, and DT doesn't care about which
driver needs HPD events. DT specifies the GPIO in the node of the device
it belongs to, this is defined in DT bindings, and must be the same on
all boards, while depending on the board different devices may need to
be informed of HPD events.

For those two reasons HPD GPIO handling and consumption of HPD events
can't always be grouped in the same driver.

> Going back to HPD notifications, as I said earlier broadcasting HPD
> notification unconditionally to every member of the chain with hope that
> the member will be able to filter-out undesired notification seems to me
> incorrect - maybe it can solve some problems but is not flexible enough
> to be usable in other scenarios.
> 
> If my arguments do not convince you please just continue with your
> ideas, we can always add NO_HPD_BROADCAST somewhere :)

:-) I would like to understand the problems you're referring to though,
and hopefully solve them. If you could describe one of the scenarios
where you think this mechanism wouldn't be usable that would help. In
the meantime I will post a new version of the series with these
operations kept as-is to get the rest of the patches reviewed.

> >>>>>>> You seem to have some other idea here.
> >>>>>>>
> >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +}
> >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>>>  	 */
> >>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>>>> +	 * output.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>>>> presence of another one?
> >>>>>>>>>>>>
> >>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>>>> and connector->edid correctly.
> >>>>>>>>>>>
> >>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>>>> correctly updated.
> >>>>>>>>>>>
> >>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>>>
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>  };
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>>  /**
> >>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>>>  };
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>>>> +};
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>>>   */
> >>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	int type;
> >>>>>>>>>>>>> +	/** private: */
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>>>  };
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>> +			   void *data);
> >>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation
  2019-08-14  9:55             ` Laurent Pinchart
@ 2019-08-14 12:24               ` Daniel Vetter
  0 siblings, 0 replies; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 12:24 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Wed, Aug 14, 2019 at 12:55:29PM +0300, Laurent Pinchart wrote:
> On Wed, Aug 14, 2019 at 10:18:40AM +0200, Daniel Vetter wrote:
> > On Thu, Aug 08, 2019 at 07:36:49PM +0200, Andrzej Hajda wrote:
> > > On 08.08.2019 16:25, Laurent Pinchart wrote:
> > > > On Wed, Jul 17, 2019 at 08:39:47AM +0200, Andrzej Hajda wrote:
> > > >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > > >>> Most bridge drivers create a DRM connector to model the connector at the
> > > >>> output of the bridge. This model is historical and has worked pretty
> > > >>> well so far, but causes several issues:
> > > >>>
> > > >>> - It prevents supporting more complex display pipelines where DRM
> > > >>> connector operations are split over multiple components. For instance a
> > > >>> pipeline with a bridge connected to the DDC signals to read EDID data,
> > > >>> and another one connected to the HPD signal to detect connection and
> > > >>> disconnection, will not be possible to support through this model.
> > > >>>
> > > >>> - It requires every bridge driver to implement similar connector
> > > >>> handling code, resulting in code duplication.
> > > >>>
> > > >>> - It assumes that a bridge will either be wired to a connector or to
> > > >>> another bridge, but doesn't support bridges that can be used in both
> > > >>> positions very well (although there is some ad-hoc support for this in
> > > >>> the analogix_dp bridge driver).
> > > >>>
> > > >>> In order to solve these issues, ownership of the connector should be
> > > >>> moved to the display controller driver (where it can be implemented
> > > >>> using helpers provided by the core).
> > > >>>
> > > >>> Extend the bridge API to allow disabling connector creation in bridge
> > > >>> drivers as a first step towards the new model. The new create_connector
> > > >>> argument to the bridge .attach() operation tells the bridge driver
> > > >>> whether to create a connector. Set the argument to true unconditionally,
> > > >>> and modify all existing bridge drivers to return an error when connector
> > > >>> creation is not requested as they don't support this feature yet.
> > > >>>
> > > >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > >>> ---
> > > >>>  drivers/gpu/drm/arc/arcpgu_hdmi.c                        | 2 +-
> > > >>>  drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c         | 2 +-
> > > >>>  drivers/gpu/drm/bridge/adv7511/adv7511_drv.c             | 6 +++++-
> > > >>>  drivers/gpu/drm/bridge/analogix-anx78xx.c                | 6 +++++-
> > > >>>  drivers/gpu/drm/bridge/analogix/analogix_dp_core.c       | 8 ++++++--
> > > >>>  drivers/gpu/drm/bridge/cdns-dsi.c                        | 6 ++++--
> > > >>>  drivers/gpu/drm/bridge/lvds-encoder.c                    | 4 ++--
> > > >>>  drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 6 +++++-
> > > >>>  drivers/gpu/drm/bridge/nxp-ptn3460.c                     | 6 +++++-
> > > >>>  drivers/gpu/drm/bridge/panel.c                           | 5 ++++-
> > > >>>  drivers/gpu/drm/bridge/parade-ps8622.c                   | 5 ++++-
> > > >>>  drivers/gpu/drm/bridge/sii902x.c                         | 6 +++++-
> > > >>>  drivers/gpu/drm/bridge/sil-sii8620.c                     | 2 +-
> > > >>>  drivers/gpu/drm/bridge/simple-bridge.c                   | 6 +++++-
> > > >>>  drivers/gpu/drm/bridge/synopsys/dw-hdmi.c                | 8 ++++++--
> > > >>>  drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c            | 8 +++++---
> > > >>>  drivers/gpu/drm/bridge/tc358764.c                        | 5 ++++-
> > > >>>  drivers/gpu/drm/bridge/tc358767.c                        | 5 ++++-
> > > >>>  drivers/gpu/drm/bridge/thc63lvd1024.c                    | 5 +++--
> > > >>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c                    | 5 ++++-
> > > >>>  drivers/gpu/drm/bridge/ti-tfp410.c                       | 5 ++++-
> > > >>>  drivers/gpu/drm/drm_bridge.c                             | 5 +++--
> > > >>>  drivers/gpu/drm/drm_simple_kms_helper.c                  | 2 +-
> > > >>>  drivers/gpu/drm/exynos/exynos_dp.c                       | 3 ++-
> > > >>>  drivers/gpu/drm/exynos/exynos_drm_dsi.c                  | 4 ++--
> > > >>>  drivers/gpu/drm/exynos/exynos_hdmi.c                     | 2 +-
> > > >>>  drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c                | 2 +-
> > > >>>  drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c             | 2 +-
> > > >>>  drivers/gpu/drm/i2c/tda998x_drv.c                        | 8 ++++++--
> > > >>>  drivers/gpu/drm/imx/imx-ldb.c                            | 2 +-
> > > >>>  drivers/gpu/drm/imx/parallel-display.c                   | 2 +-
> > > >>>  drivers/gpu/drm/mcde/mcde_dsi.c                          | 6 +++++-
> > > >>>  drivers/gpu/drm/mediatek/mtk_dpi.c                       | 2 +-
> > > >>>  drivers/gpu/drm/mediatek/mtk_dsi.c                       | 2 +-
> > > >>>  drivers/gpu/drm/mediatek/mtk_hdmi.c                      | 8 ++++++--
> > > >>>  drivers/gpu/drm/msm/dsi/dsi_manager.c                    | 4 ++--
> > > >>>  drivers/gpu/drm/msm/edp/edp_bridge.c                     | 2 +-
> > > >>>  drivers/gpu/drm/msm/hdmi/hdmi_bridge.c                   | 2 +-
> > > >>>  drivers/gpu/drm/omapdrm/omap_drv.c                       | 3 ++-
> > > >>>  drivers/gpu/drm/rcar-du/rcar_du_encoder.c                | 2 +-
> > > >>>  drivers/gpu/drm/rcar-du/rcar_lvds.c                      | 7 +++++--
> > > >>>  drivers/gpu/drm/rockchip/rockchip_lvds.c                 | 2 +-
> > > >>>  drivers/gpu/drm/rockchip/rockchip_rgb.c                  | 2 +-
> > > >>>  drivers/gpu/drm/sti/sti_dvo.c                            | 2 +-
> > > >>>  drivers/gpu/drm/sti/sti_hda.c                            | 2 +-
> > > >>>  drivers/gpu/drm/sti/sti_hdmi.c                           | 2 +-
> > > >>>  drivers/gpu/drm/stm/ltdc.c                               | 2 +-
> > > >>>  drivers/gpu/drm/sun4i/sun4i_lvds.c                       | 2 +-
> > > >>>  drivers/gpu/drm/sun4i/sun4i_rgb.c                        | 2 +-
> > > >>>  drivers/gpu/drm/tilcdc/tilcdc_external.c                 | 2 +-
> > > >>>  drivers/gpu/drm/vc4/vc4_dpi.c                            | 2 +-
> > > >>>  drivers/gpu/drm/vc4/vc4_dsi.c                            | 2 +-
> > > >>>  include/drm/drm_bridge.h                                 | 4 ++--
> > > >>>  53 files changed, 140 insertions(+), 67 deletions(-)
> > > >>>
> > > >>> diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > > >>> index 98aac743cc26..739f2358f1d5 100644
> > > >>> --- a/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > > >>> +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c
> > > >>> @@ -39,7 +39,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
> > > >>>  		return ret;
> > > >>>  
> > > >>>  	/* Link drm_bridge to encoder */
> > > >>> -	ret = drm_bridge_attach(encoder, bridge, NULL);
> > > >>> +	ret = drm_bridge_attach(encoder, bridge, NULL, true);
> > > >> Few suggestions:
> > > >>
> > > >> 1. Maybe it would be more convenient to add flags argument instead of bool:
> > > >>
> > > >> - code should be more readable: ret = drm_bridge_attach(encoder, bridge,
> > > >> NULL, DRM_BRIDGE_FLAG_NO_CONNECTOR)
> > > >>
> > > >> - it can be easily expanded later with other flags, there at least two
> > > >> drivers which would benefit from DRM_BRIDGE_FLAG_NO_CHAINING flag.
> > > >
> > > > Please note that I think this flag should disappear once drivers get
> > > > converted to the new model. This will however take some time. I'm not
> > > > opposed to turning the book into a flag though. I was hoping to receive
> > > > more review comments on this particular patch, but as that's not the
> > > > case, I can already proceed with the change.
> > > >
> > > > What would the DRM_BRIDGE_FLAG_NO_CHAINING flag be used for ?
> > > 
> > > To avoid setting encoder->bridge or previous_bridge->next field to
> > > attached bridge (last lines of drm_bridge_attach code).
> > > 
> > > This is for sure the case of exynos_dsi and vc4_dsi, but I guess it can
> > > affect other drivers as well, probably they just use other workarounds
> > > or have more flexible hardware.
> > > 
> > > Generally idea that order of calling
> > > pre_enable/enable/disable/post_disable callbacks in chained
> > > encoder/bridges is fixed is wrong IMHO, only video source component
> > > knows in which moment it should enable its sink and if it should do
> > > something after. And it does not work at all with sources/sinks having
> > > more than one output/input.
> > 
> > This doesn't make sense to me ... if you don't want to have a chained
> > bridge, don't attach it? You can just open-code the attach and call the
> > bridge functions directly when appropriate. I think that's the better
> > long-term plan than trying to have flags for every possible topology
> > existing out there ...
> 
> I think the whole topology should be known to the display driver, so the
> next (and possibly previous if we later need it) pointers should be set
> in all cases. Otherwise we can't, for instance, notify all bridges of
> HPD events. Sure, a bridge could decide not to attach at all, but then
> it would have to reimplement lots of logic to forward everything
> manually, when the need for manual forwarding is usually limited to a
> certain category of operations.

That's kinda what I meant, if you have special topology it's going to be
more typing. Once we have more examples then we can lift that to helpers
and make it generic. But setting some kind of topology that isn't correct
feels a bit fishy ...

Another option is that we have an overall function for enable/disable,
with he current helpers as default implementations. But at that point you
might as well type your own I think.
-Daniel

> 
> > > >> 2. If the patch can be applied atomically it is OK as is, if not you can
> > > >> use preprocessor vararg magic to support new and old syntax, sth like:
> > > >>
> > > >> #define _drm_bridge_attach(encoder, bridge, prev, flags, optarg...)
> > > >> __drm_bridge_attach(encoder, bridge, prev, flags)
> > > >>
> > > >> #define drm_bridge_attach(encoder, bridge, prev, optarg...)
> > > >> _drm_bridge_attach(encoder, bridge, prev, ##optarg, 0)
> > > > Good point. I'll try to do this atomically, but if it fails I'll follow
> > > > your suggestion.
> > > >
> > > >> 3. Maybe more convenient would be to just set the flags directly before
> > > >> attachment:
> > > >>
> > > >>     bridge->dont_create_connector = true;
> > > >>
> > > >>     ret = drm_bridge_attach(encoder, bridge, NULL);
> > > >>
> > > >>     This way it will be still expandable, and less changes.
> > > > Bridges that are chained would need to set the dont_create_connector
> > > > flag of the next bridge. It would be a bit ugly, but would make this
> > > > patch smaller. On the other hand we would need to keep the if
> > > > (!create_connector) check in the .attach() handlers, and it would be
> > > > easier to miss it in bridge drivers (current or new) than with an
> > > > explicit argument to the .attach() operation. I would thus have a
> > > > preference for the new argument to .attach(). Especially if it can help
> > > > you with DRM_BRIDGE_FLAG_NO_CHAINING :-)
> > > 
> > > bridge->dont_chain would work as well :)
> > > 
> > > Btw I wonder if it could be possible to disallow creating connectors at all by new bridges - it would speed-up transition.
> > > 
> > > 
> > > Another long term idea. Since bridges can be attached to:
> > > - encoder,
> > > - another bridge,
> > > - crtc (I have one example, but I guess there could be more),
> > > - even before crtc (image postprocessing)
> > > And since bridge output goes to:
> > > - another bridge,
> > > - panel.
> > > 
> > > Wouldn't be better to create drm_source and drm_sink (do not respond with xkcd picture :) ):
> > > - drm_source will be embedded in source device context,
> > > - drm_sink will be embedded in sink device context.
> > > We could make then transitions of bridges to drm_sink with drm_source embeded in its context, and panels to drm_sink.
> > > This way we could drop these crazy constructs:
> > > - if sink is panel then do sth, elsif is bridge then do sth_else,
> > > - if src is bridge then do sth, elsif is encoder ... elsif ....
> > > - helpers of_find_panel_or_bridge,
> > > - drm_panel_bridge,
> > > Also we could implement easily multi input/output bridges/panels/crtcs whatever.
> > > And hpd callbacks you have proposed in another patch would fit better to drm_source.ops.
> > > ...
> > > 
> > > 
> > > Regards
> > > Andrzej
> > > 
> > > 
> > 
> > -- 
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > http://blog.ffwll.ch
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-08 19:32                       ` Laurent Pinchart
  2019-08-09 11:55                         ` Andrzej Hajda
@ 2019-08-14 12:35                         ` Daniel Vetter
  2019-08-19 22:32                           ` Laurent Pinchart
  1 sibling, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 12:35 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Thu, Aug 08, 2019 at 10:32:14PM +0300, Laurent Pinchart wrote:
> Hello,
> 
> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> > On 16.07.2019 11:00, Daniel Vetter wrote:
> > > On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> > >> On 11.07.2019 17:50, Daniel Vetter wrote:
> > >>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> > >>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> > >>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> > >>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> > >>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > >>>>>>>> Hi Laurent,
> > >>>>>>>>
> > >>>>>>>> I like the approach, current practice when almost every bridge should
> > >>>>>>>> optionally implement connector, or alternatively downstream bridge or
> > >>>>>>>> panel is very painful.
> > >>>>>>>
> > >>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> > >>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> > >>>>>>> it's all used in the end. I probably should go and do that, at least to
> > >>>>>>> get a feeling for what your hpd_cb usually does.
> > >>>>>>>
> > >>>>>>>> More comments inlined.
> > >>>>>>>>
> > >>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> > >>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> > >>>>>>>>> data:
> > >>>>>>>>>
> > >>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> > >>>>>>>>>   retrieval operations
> > >>>>>>>>> - Bitmask of supported operations
> > >>>>>>>>
> > >>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> > >>>>>>>> operation's callback?
> > >>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> > >>>>>>>
> > >>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > >>>>>>> add, generally good excuse to not have to think through the design between
> > >>>>>>> different parts of drivers - "just" add another flag.
> > >>>>>>>
> > >>>>>>>>> - Bridge output type
> > >>>>>>>>>
> > >>>>>>>>> Add and document these.
> > >>>>>>>>>
> > >>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> > >>>>>>>>> notification in a way that is as transparent as possible for the
> > >>>>>>>>> bridges.
> > >>>>>>>>
> > >>>>>>>> Documentation of new opses does not explain how it should cooperate with
> > >>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> > >>>>>>>> right? More comments about it later.
> > >>>>>>>>
> > >>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>>>>>>>> ---
> > >>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> > >>>>>>>>>
> > >>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> > >>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >>>>>>>>>   */
> > >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> > >>>>>>>>>  {
> > >>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> > >>>>>>>>> +
> > >>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> > >>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>  	list_del_init(&bridge->list);
> > >>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>> +
> > >>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> > >>>>>>>>>  }
> > >>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> > >>>>>>>>>  
> > >>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>  }
> > >>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >>>>>>>>>  
> > >>>>>>>>> +/**
> > >>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > >>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>> + * @cb: hot-plug detection callback
> > >>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> > >>>>>>>>> + *
> > >>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > >>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> > >>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> > >>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> > >>>>>>>>> + *
> > >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>> + *
> > >>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> > >>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> > >>>>>>>>> + * the bridge.
> > >>>>>>>>> + */
> > >>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> > >>>>>>>> bridge attach:
> > >>>>>>>>
> > >>>>>>>> bridge->hpd_cb = cb;
> > >>>>>>>>
> > >>>>>>>> bridge->hpd_data = data;
> > >>>>>>>>
> > >>>>>>>> ret = drm_bridge_attach(...);
> > >>>>>>>
> > >>>>>>> Yeah I like this more. The other problem here is, what if you need more
> > >>>>>>> than 1 callback registers on the same bridge hdp signal?
> > >>>>>>>
> > >>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> > >>>>>>>> without big sacrifices.
> > >>>>>>>>
> > >>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > >>>>>>>> notifies about sink status change, how it translates to this cb?
> > >>>>>>>>
> > >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>> +			   void *data)
> > >>>>>>>>> +{
> > >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> > >>>>>>>>> +		return;
> > >>>>>>>>> +
> > >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>> +
> > >>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > >>>>>>>>> +		goto unlock;
> > >>>>>>>>> +
> > >>>>>>>>> +	bridge->hpd_cb = cb;
> > >>>>>>>>> +	bridge->hpd_data = data;
> > >>>>>>>>> +
> > >>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +unlock:
> > >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>> +}
> > >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > >>>>>>>>> +
> > >>>>>>>>> +/**
> > >>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > >>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>> + *
> > >>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > >>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > >>>>>>>>> + * function returns the callback will not be called by the bridge when an
> > >>>>>>>>> + * output status change occurs.
> > >>>>>>>>> + *
> > >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>> + */
> > >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > >>>>>>>>> +{
> > >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> > >>>>>>>>> +		return;
> > >>>>>>>>> +
> > >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	bridge->hpd_cb = NULL;
> > >>>>>>>>> +	bridge->hpd_data = NULL;
> > >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>> +}
> > >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > >>>>>>>>> +
> > >>>>>>>>> +/**
> > >>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> > >>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>> + * @status: output connection status
> > >>>>>>>>> + *
> > >>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> > >>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> > >>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > >>>>>>>>> + *
> > >>>>>>>>> + * This function shall be called in a context that can sleep.
> > >>>>>>>>> + */
> > >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>> +			   enum drm_connector_status status)
> > >>>>>>>>> +{
> > >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>> +	if (bridge->hpd_cb)
> > >>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > >>>>>>>
> > >>>>>>> So this isn't quite what I had in mind. Instead something like this:
> > >>>>>>>
> > >>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> > >>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> > >>>>>>> 		if (tmp_bridge == bridge)
> > >>>>>>> 			continue;
> > >>>>>>> 		if (bridge->hpd_notify);
> > >>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > >>>>>>> 	}
> > >>>>>>>
> > >>>>>>> 	encoder = encoder_for_bridge(bridge);
> > >>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> > >>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > >>>>>>>
> > >>>>>>> 	dev = bridge->dev
> > >>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > >>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > >>>>>>>
> > >>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> > >>>>>>> hpd they want/need.
> > >>>>>> As I understand you want to notify every member of the pipeline.
> > >>>>>>
> > >>>>>> I think it should be enough to notify only the source, and then source
> > >>>>>> should decide if/when the hpd should be propagated upstream.
> > >>>>>>
> > >>>>>> It looks more generic for me.
> > >>>>>
> > >>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> > >>>>> the one from Laurent? Kinda confused here.
> > >>>>
> > >>>> Regarding general idea:
> > >>>>
> > >>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> > >>>> source.
> > >>>>
> > >>>> 2. Your is to notify all other bridges and encoder.
> > >>>>
> > >>>> And I prefer 1st approach, why:
> > >>>>
> > >>>> - the source can decide if/when and to who propagate the signal,
> > >>>>
> > >>>> - is more generic, for example if bridge send signal to two
> > >>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> > >>>
> > >>> With Laurent's approach the bridge cannot send the hpd to more than one
> > >>> consumer. There's only 1 callback. So you're example doesn't work.
> > >>
> > >> If there will be two consumers, there will be two bridge attachments,
> > >> thus there will be two notifications, it should work.
> > >
> > > 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> > > callback is registered on the produce bridge, not on the consumer bridge
> > > (or I'm totallly misreading what Laurent does here).
> > 
> > I have assumed that if devices exposes two hardware sink interfaces it
> > will expose two separate bridges - of course it will not work with
> > "bridge chaining" thing, but this is a different story.
> 
> Daniel is right that the current implementation only allows one
> consumer. This is however not a limitation of the API, but of its
> implementation, as I only needed a single consumer. The helpers in this
> series ensure that neither the consumer nor the producer poke in the
> drm_bridge structure to call back to the HPD handler:
> 
> - The consumer calls drm_bridge_hpd_enable() and
>   drm_bridge_hpd_disable(), which could offer a reference-counted
>   behaviour if desired without changes to the consumer.
> 
> - The producer gets configured by .hpd_enable() and .hpd_disable(),
>   which could also easily accommodate reference-counting in the drm
>   bridge core without changes to the producer.
> 
> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
>   easily be extended to support multiple consumers without changes to
>   the producer.
> 
> This is actually my second version of the HPD mechanism. The first
> version was never posted, poked into drm_bridge, and required the
> producer to be aware of the callbacks. After discussing this privately
> with Daniel, I came up with the implementation in this series that,
> while not supporting multiple consumers now, makes it easy to extend
> later without minimal effort.
> 
> Daniel's proposed implementation above looks reasonable to me, provided
> we can iterate over the bridges in an order that don't depend on the
> position of the producer in the chain (should be easy to solve by
> starting at the encoder for instance). It however looks a bit like a
> midlayer to me :-) That's why I have a similar implementation in the
> connector-bridge helper, which could be extended to call
> encoder->helper_private->bridge_hpd_notify() and
> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> drm_bridge_hpd_notify() would on the other hand set the notification
> sequence towards the encoder and driver in stone. Daniel, do you think
> that would be better ?

So the difference between the midlayer and the helper is that the helper
can be ignored. Which the above still can:

- producer can choose to not call that function
- consumer can choose not to have the callback

Now great helpers allow you to ignore only parts of them, so that you can
mix&match. Which again I think with the bridge stuff we're discussing here
is assured.

So the final bit is how opinionated a helper can be, and imo it can be
very opinionated and strict and inflexible. That means it won't be useful
for every possible case, but those can be handled by simply not using the
helper (or that part of the helpers). Examples

- simple display pipe is very opinionated, but trades that in for being
  very useful for really simple displays

- similar with atomic helpers, there's a very strong suggestion that "if
  it doesn't fit, write your own commit_tail()"
 
And I think bridge helpers probably also need fairly opinioated, simply to
make sure that all the bridge drivers work together in a coherent fashion.
If we allow too much flexibility everyone bends the rules a bit, and
nothing fits.

Wrt your question: One option would be to do the same thing like shared
interrupt line handlers. As soon as the first interrupt handler says "I'
ve handled this one" we stop processing. But that might lead to more
confusion about who's responsible for an interrupt.
-Daniel

> 
> I would like to remind everybody that this series isn't the last I will
> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> open to suggestions, and can address problems on top of these patches,
> provided obviously that this series doesn't go in the wrong direction.
> I'm of course also willing to rework this series, but given the amount
> of work we have in the drm_bridge realm, I can't fix everything in one
> go :-)
> 
> > >>>> - it resembles hardware wires :)
> > >>>
> > >>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> > >>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> > >>> interested in that hpd singal. This includes:
> > >>> - Other bridges, e.g. if they provide CEC support.
> > >>> - Other bridges, maybe they need to re-run the HDCP state engine
> > >>> - Overall driver, so it can update the modes/connector status and send the
> > >>>   uevent to the driver.
> > >>> - Overall display pipeline for this specific bridge, maybe you need to
> > >>>   shut down/re-enable the pipe because $reasons.
> > >>>  
> > >>> That's at least my understanding from lots of chats with Laurent about
> > >>> what he wants to do here.
> 
> That's correct, and that's what I was trying to implement :-) The
> notification, in this patch series, goes from the producer bridge to a
> central place (namely the connector, with a helper implementation
> available as part of this series, but custom implementations in display
> drivers are fine if needed) that then dispatches the notification to all
> bridges (through the .lost_hotplug() operation, which we could replace
> by an .hpd_notify() operation) for the first two purposes listed above,
> and then to the overall driver. The only thing I don't support yet is
> dispatching to the display pipeline (item 4 in the list above) as I had
> no need for that, and didn't want to develop an API with no user. This
> would however not be difficult to do when needed, the need is taken into
> account in the proposed implementation.
> 
> > >> I do not know the full picture, but the solution where particular bridge
> > >> notifies everything unconditionally seems to me much less flexible.
> > >>
> > >> If HPD signals is received by the consumer, if there are no obstacles it
> > >> can propagate it further, upstream bridge/encoder or to drm core - it
> > >> will mimic your scenario.
> > >>
> > >> But there are also other scenarios where bridge does not want to
> > >> propagate signal, because for example:
> > >>
> > >> - it wants to wait for other sinks to wake up,
> > >
> > > The other sink can just do that in their hpd callback.
> > >
> > >> - it propagates HPD signal via hardware wire,
> > >
> > > Again, the other sink can just not listen to sw hpd in that case, and use
> > > the wire/hw hpd interrupt.
> > 
> > If it should ignore HPD, why it should receive it at all - it is
> > unnecessary noise. And I am afraid with more complicated pipelines it
> > will be impossible for particular component (bridge/encoder/whatever) to
> > distinguish if HPD notification which came from non-directly connected
> > component should be ignored or not.
> > 
> > >> - first it wants to verify if the sink is valid/compatible/authorized
> > >> device.
> > >
> > > Now you lost me. Why would someone glue incompatible IP into a SoC or
> > > board?
> > 
> > Bridge can have external connectors, and the user can connect there
> > anything.
> > 
> > >> In general HPD is input signal for notify of state changes on particular
> > >> bus, in case of typical video bridge on its output video bus.
> > >>
> > >> In case of bridges they have also input video buses, and they can send
> > >> HPD signal via this bus, but this is indeed different HPD signal, even
> > >> if for most cases they looks similar.
> > >
> > > Ah, I think this is a problem we will eventually have. But it's not
> > > something we're currently solving here at all I think.
> > 
> > Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> > line, so this is not something from far future. And I guess with HPD
> > broadcasting it could be racy/error prone, for example EDID reading can
> > fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> > controller via hw wires also).
> > 
> > >>>> And regarding implementation:
> > >>>>
> > >>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> > >>>>
> > >>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> > >>>>
> > >>>> Your proposition is more straightforward, but if we want to notify only
> > >>>> source we should locate it by parsing notification chain (what about
> > >>>> unchained bridges), or store pointer somewhere during attachment.
> > >>>>
> > >>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> > >>>> similarly to sink as bridge or panel, but fixing it can be done later.
> > >>>
> > >>> Uh I think we're not talking about the same thing really. My understanding
> > >>> is that this callback is if someone (outside of this bridge) is interested
> > >>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> > >>> listener.
> > >>
> > >> Do we have real life examples?
> > >>
> > >> I want to distinguish two situations:
> > >>
> > >> - another device wants to know if input bus of the bridge has changed state,
> > >>
> > >> - another device wants to know if output bus of the bridge has changed
> > >> state.
> > >
> > > Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> > > bridges can exchange state and information about each another. hpd is
> > > about the physical world, i.e. "is there a cable plugged into the port
> > > I'm driving?". We're not going to use fake hpd to update bridge state and
> > > fun stuff like that, we have the atomic_check machinery for this.
> > 
> > My question was if we have real examples that upstream device requires
> > knowledge about state of output line of the bridge?
> > 
> > To be more precise, we have following display pipeline:
> > 
> > A-->B-->C
> > 
> > And C sends HPD to B (ie signal that state of line between B and C
> > changed). Does A really wants to know this information? or it should
> > just need to know if state of line A-->B changed?
> 
> There's one real life example, where A is an HDMI encoder, B is an HDMI
> ESD protector and level shifter, and C is the physical HDMI connector.
> When the HDMI cable is unplugged, the CEC controller part of A needs to
> be notified in order to reset the CEC state machine. One could however
> argue that in that case the A-B link state changes too, but the
> important part is that HPD detection is not performed by A, while A
> needs to be informed of lost hotplug.
> 
> > >>> You seem to have some other idea here.
> > >>>
> > >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>> +}
> > >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > >>>>>>>>> +
> > >>>>>>>>>  #ifdef CONFIG_OF
> > >>>>>>>>>  /**
> > >>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > >>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > >>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> > >>>>>>>>> --- a/include/drm/drm_bridge.h
> > >>>>>>>>> +++ b/include/drm/drm_bridge.h
> > >>>>>>>>> @@ -23,8 +23,9 @@
> > >>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> > >>>>>>>>>  #define __DRM_BRIDGE_H__
> > >>>>>>>>>  
> > >>>>>>>>> -#include <linux/list.h>
> > >>>>>>>>>  #include <linux/ctype.h>
> > >>>>>>>>> +#include <linux/list.h>
> > >>>>>>>>> +#include <linux/mutex.h>
> > >>>>>>>>>  #include <drm/drm_mode_object.h>
> > >>>>>>>>>  #include <drm/drm_modes.h>
> > >>>>>>>>>  
> > >>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >>>>>>>>>  	 */
> > >>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >>>>>>>>>  				    struct drm_atomic_state *state);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @detect:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Check if anything is attached to the bridge output.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> > >>>>>>>>> +	 * considered as always having a component attached to its output.
> > >>>>>>>>> +	 * Bridges that implement this callback shall set the
> > >>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * RETURNS:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @get_modes:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> > >>>>>>>>> +	 * with drm_mode_probed_add().
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> > >>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> > >>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> > >>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * RETURNS:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> > >>>>>>>>> +			 struct drm_connector *connector);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @get_edid:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> > >>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> > >>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> > >>>>>>>>> +	 * the @get_modes callback unimplemented.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The caller of this operation shall first verify the output
> > >>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> > >>>>>>>>> +	 * output.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * RETURNS:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > >>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> > >>>>>>>>> +	 * the returned edid structure with kfree().
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > >>>>>>>>> +				 struct drm_connector *connector);
> > >>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> > >>>>>>>> presence of another one?
> > >>>>>>>>
> > >>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> > >>>>>>>> some helper function to .get_modes cb, which will do the same?
> > >>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > >>>>>>> case, and require that if it has an edid it must fill out connector->info
> > >>>>>>> and connector->edid correctly.
> > >>>>>>>
> > >>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> > >>>>>>> in the connector is up-to-date? With your current callback design that's
> > >>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > >>>>>>> should guarantee that it'll first walk the connectors to update status and
> > >>>>>>> edid/mode list for the final drm_connector. And then instead of just
> > >>>>>>> passing the simple "status", it'll pass the connector, with everything
> > >>>>>>> correctly updated.
> > >>>>>>>
> > >>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> > >>>>>>> edid, which is not so awesome :-)
> > >>>>>>>
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @lost_hotplug:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Notify the bridge of display disconnection.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> > >>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> > >>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> > >>>>>>>>> +	 * HDMI bridges.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_enable:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> > >>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > >>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> > >>>>>>>>> +	 * @hpd_disable.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> > >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_disable:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> > >>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > >>>>>>>>> +	 * connection status occurs.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> > >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >>>>>>>>>  };
> > >>>>>>>>>  
> > >>>>>>>>>  /**
> > >>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >>>>>>>>>  	bool dual_link;
> > >>>>>>>>>  };
> > >>>>>>>>>  
> > >>>>>>>>> +/**
> > >>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > >>>>>>>>> + */
> > >>>>>>>>> +enum drm_bridge_ops {
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > >>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> > >>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > >>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> > >>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > >>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> > >>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> > >>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > >>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> > >>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > >>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> > >>>>>>>>> +};
> > >>>>>>>>> +
> > >>>>>>>>>  /**
> > >>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> > >>>>>>>>>   */
> > >>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> > >>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> > >>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> > >>>>>>>>>  	void *driver_private;
> > >>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> > >>>>>>>>> +	enum drm_bridge_ops ops;
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @type: Type of the connection at the bridge output
> > >>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > >>>>>>>>> +	 * identifies the type of connected display.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	int type;
> > >>>>>>>>> +	/** private: */
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	struct mutex hpd_mutex;
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> > >>>>>>>>> +	 * drm_bridge_hpd_enable().
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > >>>>>>>>> +	 * @hpd_cb.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void *hpd_data;
> > >>>>>>>>>  };
> > >>>>>>>>>  
> > >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> > >>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>  			      struct drm_atomic_state *state);
> > >>>>>>>>>  
> > >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>> +			   void *data);
> > >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>> +			   enum drm_connector_status status);
> > >>>>>>>>> +
> > >>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >>>>>>>>>  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 10:04                               ` Laurent Pinchart
@ 2019-08-14 12:40                                 ` Daniel Vetter
  2019-08-19  8:38                                   ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 12:40 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
> Hi Andrzej,
> 
> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
> > Hi Laurent,
> > 
> > Sorry for late response.
> 
> No worries.
> 
> > On 11.08.2019 00:43, Laurent Pinchart wrote:
> > > On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
> > >> On 08.08.2019 21:32, Laurent Pinchart wrote:
> > >>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> > >>>> On 16.07.2019 11:00, Daniel Vetter wrote:
> > >>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> > >>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> > >>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> > >>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> > >>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> > >>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> > >>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > >>>>>>>>>>>> Hi Laurent,
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> I like the approach, current practice when almost every bridge should
> > >>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> > >>>>>>>>>>>> panel is very painful.
> > >>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> > >>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> > >>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> > >>>>>>>>>>> get a feeling for what your hpd_cb usually does.
> > >>>>>>>>>>>
> > >>>>>>>>>>>> More comments inlined.
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> > >>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> > >>>>>>>>>>>>> data:
> > >>>>>>>>>>>>>
> > >>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> > >>>>>>>>>>>>>   retrieval operations
> > >>>>>>>>>>>>> - Bitmask of supported operations
> > >>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> > >>>>>>>>>>>> operation's callback?
> > >>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> > >>>>>>>>>>>
> > >>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > >>>>>>>>>>> add, generally good excuse to not have to think through the design between
> > >>>>>>>>>>> different parts of drivers - "just" add another flag.
> > >>>>>>>>>>>
> > >>>>>>>>>>>>> - Bridge output type
> > >>>>>>>>>>>>>
> > >>>>>>>>>>>>> Add and document these.
> > >>>>>>>>>>>>>
> > >>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> > >>>>>>>>>>>>> notification in a way that is as transparent as possible for the
> > >>>>>>>>>>>>> bridges.
> > >>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> > >>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> > >>>>>>>>>>>> right? More comments about it later.
> > >>>>>>>>>>>>
> > >>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>>>>>>>>>>>> ---
> > >>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> > >>>>>>>>>>>>>
> > >>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> > >>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >>>>>>>>>>>>>   */
> > >>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> > >>>>>>>>>>>>>  {
> > >>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> > >>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>>>>>  	list_del_init(&bridge->list);
> > >>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> > >>>>>>>>>>>>>  }
> > >>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>>>  }
> > >>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>> +/**
> > >>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > >>>>>>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>>>>>> + * @cb: hot-plug detection callback
> > >>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > >>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> > >>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> > >>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> > >>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> > >>>>>>>>>>>>> + * the bridge.
> > >>>>>>>>>>>>> + */
> > >>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> > >>>>>>>>>>>> bridge attach:
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> bridge->hpd_cb = cb;
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> bridge->hpd_data = data;
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> ret = drm_bridge_attach(...);
> > >>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> > >>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> > >>>>>>>>>>>
> > >>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> > >>>>>>>>>>>> without big sacrifices.
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > >>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
> > >>>>>>>>>>>>
> > >>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>>>>>> +			   void *data)
> > >>>>>>>>>>>>> +{
> > >>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> > >>>>>>>>>>>>> +		return;
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > >>>>>>>>>>>>> +		goto unlock;
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	bridge->hpd_cb = cb;
> > >>>>>>>>>>>>> +	bridge->hpd_data = data;
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +unlock:
> > >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +}
> > >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +/**
> > >>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > >>>>>>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > >>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > >>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> > >>>>>>>>>>>>> + * output status change occurs.
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>>>>>> + */
> > >>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > >>>>>>>>>>>>> +{
> > >>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> > >>>>>>>>>>>>> +		return;
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
> > >>>>>>>>>>>>> +	bridge->hpd_data = NULL;
> > >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +}
> > >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +/**
> > >>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> > >>>>>>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>>>>>> + * @status: output connection status
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> > >>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> > >>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > >>>>>>>>>>>>> + *
> > >>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
> > >>>>>>>>>>>>> + */
> > >>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>>>>>> +			   enum drm_connector_status status)
> > >>>>>>>>>>>>> +{
> > >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +	if (bridge->hpd_cb)
> > >>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > >>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> > >>>>>>>>>>>
> > >>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> > >>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> > >>>>>>>>>>> 		if (tmp_bridge == bridge)
> > >>>>>>>>>>> 			continue;
> > >>>>>>>>>>> 		if (bridge->hpd_notify);
> > >>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > >>>>>>>>>>> 	}
> > >>>>>>>>>>>
> > >>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> > >>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> > >>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > >>>>>>>>>>>
> > >>>>>>>>>>> 	dev = bridge->dev
> > >>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > >>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > >>>>>>>>>>>
> > >>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> > >>>>>>>>>>> hpd they want/need.
> > >>>>>>>>>> As I understand you want to notify every member of the pipeline.
> > >>>>>>>>>>
> > >>>>>>>>>> I think it should be enough to notify only the source, and then source
> > >>>>>>>>>> should decide if/when the hpd should be propagated upstream.
> > >>>>>>>>>>
> > >>>>>>>>>> It looks more generic for me.
> > >>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> > >>>>>>>>> the one from Laurent? Kinda confused here.
> > >>>>>>>> Regarding general idea:
> > >>>>>>>>
> > >>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> > >>>>>>>> source.
> > >>>>>>>>
> > >>>>>>>> 2. Your is to notify all other bridges and encoder.
> > >>>>>>>>
> > >>>>>>>> And I prefer 1st approach, why:
> > >>>>>>>>
> > >>>>>>>> - the source can decide if/when and to who propagate the signal,
> > >>>>>>>>
> > >>>>>>>> - is more generic, for example if bridge send signal to two
> > >>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> > >>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> > >>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
> > >>>>>> If there will be two consumers, there will be two bridge attachments,
> > >>>>>> thus there will be two notifications, it should work.
> > >>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> > >>>>> callback is registered on the produce bridge, not on the consumer bridge
> > >>>>> (or I'm totallly misreading what Laurent does here).
> > >>>> I have assumed that if devices exposes two hardware sink interfaces it
> > >>>> will expose two separate bridges - of course it will not work with
> > >>>> "bridge chaining" thing, but this is a different story.
> > >>> Daniel is right that the current implementation only allows one
> > >>> consumer. This is however not a limitation of the API, but of its
> > >>> implementation, as I only needed a single consumer. The helpers in this
> > >>> series ensure that neither the consumer nor the producer poke in the
> > >>> drm_bridge structure to call back to the HPD handler:
> > >>>
> > >>> - The consumer calls drm_bridge_hpd_enable() and
> > >>>   drm_bridge_hpd_disable(), which could offer a reference-counted
> > >>>   behaviour if desired without changes to the consumer.
> > >>>
> > >>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
> > >>>   which could also easily accommodate reference-counting in the drm
> > >>>   bridge core without changes to the producer.
> > >>>
> > >>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> > >>>   easily be extended to support multiple consumers without changes to
> > >>>   the producer.
> > >>>
> > >>> This is actually my second version of the HPD mechanism. The first
> > >>> version was never posted, poked into drm_bridge, and required the
> > >>> producer to be aware of the callbacks. After discussing this privately
> > >>> with Daniel, I came up with the implementation in this series that,
> > >>> while not supporting multiple consumers now, makes it easy to extend
> > >>> later without minimal effort.
> > >>>
> > >>> Daniel's proposed implementation above looks reasonable to me, provided
> > >>> we can iterate over the bridges in an order that don't depend on the
> > >>> position of the producer in the chain (should be easy to solve by
> > >>> starting at the encoder for instance). It however looks a bit like a
> > >>> midlayer to me :-) That's why I have a similar implementation in the
> > >>> connector-bridge helper, which could be extended to call
> > >>> encoder->helper_private->bridge_hpd_notify() and
> > >>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> > >>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> > >>> drm_bridge_hpd_notify() would on the other hand set the notification
> > >>> sequence towards the encoder and driver in stone. Daniel, do you think
> > >>> that would be better ?
> > >>>
> > >>> I would like to remind everybody that this series isn't the last I will
> > >>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> > >>> open to suggestions, and can address problems on top of these patches,
> > >>> provided obviously that this series doesn't go in the wrong direction.
> > >>> I'm of course also willing to rework this series, but given the amount
> > >>> of work we have in the drm_bridge realm, I can't fix everything in one
> > >>> go :-)
> > >>>
> > >>>>>>>> - it resembles hardware wires :)
> > >>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> > >>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> > >>>>>>> interested in that hpd singal. This includes:
> > >>>>>>> - Other bridges, e.g. if they provide CEC support.
> > >>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> > >>>>>>> - Overall driver, so it can update the modes/connector status and send the
> > >>>>>>>   uevent to the driver.
> > >>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
> > >>>>>>>   shut down/re-enable the pipe because $reasons.
> > >>>>>>>  
> > >>>>>>> That's at least my understanding from lots of chats with Laurent about
> > >>>>>>> what he wants to do here.
> > >>> That's correct, and that's what I was trying to implement :-) The
> > >>> notification, in this patch series, goes from the producer bridge to a
> > >>> central place (namely the connector, with a helper implementation
> > >>> available as part of this series, but custom implementations in display
> > >>> drivers are fine if needed) that then dispatches the notification to all
> > >>> bridges (through the .lost_hotplug() operation, which we could replace
> > >>> by an .hpd_notify() operation) for the first two purposes listed above,
> > >>> and then to the overall driver. The only thing I don't support yet is
> > >>> dispatching to the display pipeline (item 4 in the list above) as I had
> > >>> no need for that, and didn't want to develop an API with no user. This
> > >>> would however not be difficult to do when needed, the need is taken into
> > >>> account in the proposed implementation.
> > >>>
> > >>>>>> I do not know the full picture, but the solution where particular bridge
> > >>>>>> notifies everything unconditionally seems to me much less flexible.
> > >>>>>>
> > >>>>>> If HPD signals is received by the consumer, if there are no obstacles it
> > >>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
> > >>>>>> will mimic your scenario.
> > >>>>>>
> > >>>>>> But there are also other scenarios where bridge does not want to
> > >>>>>> propagate signal, because for example:
> > >>>>>>
> > >>>>>> - it wants to wait for other sinks to wake up,
> > >>>>>>
> > >>>>> The other sink can just do that in their hpd callback.
> > >>>>>
> > >>>>>> - it propagates HPD signal via hardware wire,
> > >>>>> Again, the other sink can just not listen to sw hpd in that case, and use
> > >>>>> the wire/hw hpd interrupt.
> > >>>>>
> > >>>> If it should ignore HPD, why it should receive it at all - it is
> > >>>> unnecessary noise. And I am afraid with more complicated pipelines it
> > >>>> will be impossible for particular component (bridge/encoder/whatever) to
> > >>>> distinguish if HPD notification which came from non-directly connected
> > >>>> component should be ignored or not.
> > >>>>
> > >>>>>> - first it wants to verify if the sink is valid/compatible/authorized
> > >>>>>> device.
> > >>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> > >>>>> board?
> > >>>> Bridge can have external connectors, and the user can connect there
> > >>>> anything.
> > >>>>
> > >>>>>> In general HPD is input signal for notify of state changes on particular
> > >>>>>> bus, in case of typical video bridge on its output video bus.
> > >>>>>>
> > >>>>>> In case of bridges they have also input video buses, and they can send
> > >>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
> > >>>>>> if for most cases they looks similar.
> > >>>>> Ah, I think this is a problem we will eventually have. But it's not
> > >>>>> something we're currently solving here at all I think.
> > >>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> > >>>> line, so this is not something from far future. And I guess with HPD
> > >>>> broadcasting it could be racy/error prone, for example EDID reading can
> > >>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> > >>>> controller via hw wires also).
> > >>>>
> > >>>>>>>> And regarding implementation:
> > >>>>>>>>
> > >>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> > >>>>>>>>
> > >>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> > >>>>>>>>
> > >>>>>>>> Your proposition is more straightforward, but if we want to notify only
> > >>>>>>>> source we should locate it by parsing notification chain (what about
> > >>>>>>>> unchained bridges), or store pointer somewhere during attachment.
> > >>>>>>>>
> > >>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> > >>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> > >>>>>>> Uh I think we're not talking about the same thing really. My understanding
> > >>>>>>> is that this callback is if someone (outside of this bridge) is interested
> > >>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> > >>>>>>> listener.
> > >>>>>> Do we have real life examples?
> > >>>>>>
> > >>>>>> I want to distinguish two situations:
> > >>>>>>
> > >>>>>> - another device wants to know if input bus of the bridge has changed state,
> > >>>>>>
> > >>>>>> - another device wants to know if output bus of the bridge has changed
> > >>>>>> state.
> > >>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> > >>>>> bridges can exchange state and information about each another. hpd is
> > >>>>> about the physical world, i.e. "is there a cable plugged into the port
> > >>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
> > >>>>> fun stuff like that, we have the atomic_check machinery for this.
> > >>>> My question was if we have real examples that upstream device requires
> > >>>> knowledge about state of output line of the bridge?
> > >>>>
> > >>>> To be more precise, we have following display pipeline:
> > >>>>
> > >>>> A-->B-->C
> > >>>>
> > >>>> And C sends HPD to B (ie signal that state of line between B and C
> > >>>> changed). Does A really wants to know this information? or it should
> > >>>> just need to know if state of line A-->B changed?
> > >>> There's one real life example, where A is an HDMI encoder, B is an HDMI
> > >>> ESD protector and level shifter, and C is the physical HDMI connector.
> > >>> When the HDMI cable is unplugged, the CEC controller part of A needs to
> > >>> be notified in order to reset the CEC state machine. One could however
> > >>> argue that in that case the A-B link state changes too, but the
> > >>> important part is that HPD detection is not performed by A, while A
> > >>> needs to be informed of lost hotplug.
> > >> I have no full picture but I guess in this case C sends HPD to B using
> > >> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
> > >> say that B does not participate in HPD transmission/forwarding,
> > > No, in this case A doesn't receive any hardware HPD signal, it requires
> > > HPD notification through software.
> > >
> > >> some shifters with 'advanced power saving' can even perform wake-up of
> > >> upstream pin logic after receiving HPD on downstream, so HPD sent from B
> > >> to A is indeed different than HPD sent from C to B.
> > >>
> > >> Btw, with the above logic of propagation of HPD callback (proposed by
> > >> Daniel) I guess it will work this way:
> > >>
> > >> - A will receive HPD signal via HW,
> > >>
> > >> - then B and C will receive HPD callback via framework.
> > >>
> > >> Am I right?
> > 
> > > It's the other way around.
> > >
> > > In this case the HPD signal from the connector (C) is routed to an input
> > > of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
> > > connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
> > > IRQ and receive the hardware HPD notification. The driver for the HDMI
> > > encoder (A) needs to receive HPD notification in software, through the
> > > framework.
> > 
> > If this is GPIO I wonder why do not query this gpio by encoder directly,
> > rules of ownership of such gpios seems to be grey area, so in such case
> > I would advise to put it in the driver who really needs it.
> > 
> > This way it will be much simpler.
> 
> First to fall, multiple drivers may need to be informed of HPD events
> coming from a GPIO, so we would need to duplicate it in multiple places,
> and I don't think the GPIO framework allows acquiring a GPIO multiple
> times.
> 
> Then, the GPIO is described in DT, and DT doesn't care about which
> driver needs HPD events. DT specifies the GPIO in the node of the device
> it belongs to, this is defined in DT bindings, and must be the same on
> all boards, while depending on the board different devices may need to
> be informed of HPD events.
> 
> For those two reasons HPD GPIO handling and consumption of HPD events
> can't always be grouped in the same driver.
> 
> > Going back to HPD notifications, as I said earlier broadcasting HPD
> > notification unconditionally to every member of the chain with hope that
> > the member will be able to filter-out undesired notification seems to me
> > incorrect - maybe it can solve some problems but is not flexible enough
> > to be usable in other scenarios.
> > 
> > If my arguments do not convince you please just continue with your
> > ideas, we can always add NO_HPD_BROADCAST somewhere :)
> 
> :-) I would like to understand the problems you're referring to though,
> and hopefully solve them. If you could describe one of the scenarios
> where you think this mechanism wouldn't be usable that would help. In
> the meantime I will post a new version of the series with these
> operations kept as-is to get the rest of the patches reviewed.

See my little thing about midlayers, I think midlayers with lots of flags
for everything aren't a good idea. They should be more opinionated about
how things work.

So if there's a case where this broadcasting of various things doesn't
work, let's dig into it.
-Daniel

> 
> > >>>>>>> You seem to have some other idea here.
> > >>>>>>>
> > >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>>>>>> +}
> > >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>>  #ifdef CONFIG_OF
> > >>>>>>>>>>>>>  /**
> > >>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > >>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > >>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> > >>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
> > >>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> > >>>>>>>>>>>>> @@ -23,8 +23,9 @@
> > >>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> > >>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>> -#include <linux/list.h>
> > >>>>>>>>>>>>>  #include <linux/ctype.h>
> > >>>>>>>>>>>>> +#include <linux/list.h>
> > >>>>>>>>>>>>> +#include <linux/mutex.h>
> > >>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
> > >>>>>>>>>>>>>  #include <drm/drm_modes.h>
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >>>>>>>>>>>>>  	 */
> > >>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >>>>>>>>>>>>>  				    struct drm_atomic_state *state);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @detect:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> > >>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
> > >>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> > >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * RETURNS:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @get_modes:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> > >>>>>>>>>>>>> +	 * with drm_mode_probed_add().
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> > >>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> > >>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> > >>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * RETURNS:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> > >>>>>>>>>>>>> +			 struct drm_connector *connector);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @get_edid:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> > >>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> > >>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> > >>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> > >>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> > >>>>>>>>>>>>> +	 * output.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * RETURNS:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > >>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> > >>>>>>>>>>>>> +	 * the returned edid structure with kfree().
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > >>>>>>>>>>>>> +				 struct drm_connector *connector);
> > >>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> > >>>>>>>>>>>> presence of another one?
> > >>>>>>>>>>>>
> > >>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> > >>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> > >>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > >>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> > >>>>>>>>>>> and connector->edid correctly.
> > >>>>>>>>>>>
> > >>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> > >>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
> > >>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > >>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> > >>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> > >>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> > >>>>>>>>>>> correctly updated.
> > >>>>>>>>>>>
> > >>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> > >>>>>>>>>>> edid, which is not so awesome :-)
> > >>>>>>>>>>>
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @lost_hotplug:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> > >>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> > >>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> > >>>>>>>>>>>>> +	 * HDMI bridges.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @hpd_enable:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> > >>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > >>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> > >>>>>>>>>>>>> +	 * @hpd_disable.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> > >>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @hpd_disable:
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> > >>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > >>>>>>>>>>>>> +	 * connection status occurs.
> > >>>>>>>>>>>>> +	 *
> > >>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> > >>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >>>>>>>>>>>>>  };
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>>  /**
> > >>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >>>>>>>>>>>>>  	bool dual_link;
> > >>>>>>>>>>>>>  };
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>> +/**
> > >>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > >>>>>>>>>>>>> + */
> > >>>>>>>>>>>>> +enum drm_bridge_ops {
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > >>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> > >>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > >>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> > >>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > >>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> > >>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> > >>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > >>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> > >>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > >>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> > >>>>>>>>>>>>> +};
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>>  /**
> > >>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> > >>>>>>>>>>>>>   */
> > >>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> > >>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> > >>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> > >>>>>>>>>>>>>  	void *driver_private;
> > >>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> > >>>>>>>>>>>>> +	enum drm_bridge_ops ops;
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> > >>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > >>>>>>>>>>>>> +	 * identifies the type of connected display.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	int type;
> > >>>>>>>>>>>>> +	/** private: */
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	struct mutex hpd_mutex;
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> > >>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > >>>>>>>>>>>>> +	/**
> > >>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > >>>>>>>>>>>>> +	 * @hpd_cb.
> > >>>>>>>>>>>>> +	 */
> > >>>>>>>>>>>>> +	void *hpd_data;
> > >>>>>>>>>>>>>  };
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> > >>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>>>  			      struct drm_atomic_state *state);
> > >>>>>>>>>>>>>  
> > >>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>>>>>> +			   void *data);
> > >>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > >>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>>>>>> +			   enum drm_connector_status status);
> > >>>>>>>>>>>>> +
> > >>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >>>>>>>>>>>>>  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-08 18:19         ` Laurent Pinchart
  2019-08-08 18:36           ` Laurent Pinchart
@ 2019-08-14 12:43           ` Daniel Vetter
  2019-08-14 12:56             ` Laurent Pinchart
  1 sibling, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 12:43 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> Hello Daniel and Andrzej,
> 
> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> > On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > > Hi Laurent,
> > > 
> > > I like the approach, current practice when almost every bridge should
> > > optionally implement connector, or alternatively downstream bridge or
> > > panel is very painful.
> > 
> > Yeah I think this looks mostly reasonable. Some api design comments on top
> > of Andrzej', with the fair warning that I didn't bother to read up on how
> > it's all used in the end. I probably should go and do that, at least to
> > get a feeling for what your hpd_cb usually does.
> > 
> > > More comments inlined.
> > > 
> > > On 07.07.2019 20:18, Laurent Pinchart wrote:
> > > > To support implementation of DRM connectors on top of DRM bridges
> > > > instead of by bridges, the drm_bridge needs to expose new operations and
> > > > data:
> > > >
> > > > - Output detection, hot-plug notification, mode retrieval and EDID
> > > >   retrieval operations
> > > > - Bitmask of supported operations
> > > 
> > > 
> > > Why do we need these bitmask at all? Why cannot we rely on presence of
> > > operation's callback?
> > 
> > Yeah also not a huge fan of these bitmasks. Smells like
> > DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > add, generally good excuse to not have to think through the design between
> > different parts of drivers - "just" add another flag.
> 
> The reason is that a bridge may support an operation (as in implemented
> in the bridge hardware), but that operation may not be supported on a
> particular board. For instance an HDMI encoder may support reading EDID
> when the DDC lines are connected to the encoder, but a board may connect
> the DDC lines to an I2C port of the SoC. We thus need to decouple
> if a particular instance of the device supports the operation (exposed
> by the ops flags) from the function pointers.
> 
> We could of course allocate the drm_bridge_funcs structure dynamically
> for each bridge instance, and fill it with function pointers manually,
> leaving the unused ops always NULL, but that would require making the
> structure writable, which is considered a security issue. That's why I
> decided to keep the drm_bridge_funcs structure as a global static const
> structure, and add an ops bitmask.

Yeah reallocating func structures is not great, I agree ...

Would an -ENXIO at runtime work too? Or do we need to know this
statically. It just feels so silly and redundant to have the flag and the
hook.

> > > > - Bridge output type
> > > >
> > > > Add and document these.
> > > >
> > > > Three new bridge helper functions are also added to handle hot plug
> > > > notification in a way that is as transparent as possible for the
> > > > bridges.
> > > 
> > > Documentation of new opses does not explain how it should cooperate with
> > > bridge chaining, I suppose they should be chained explicitly, am I
> > > right? More comments about it later.
> 
> No, the whole point is that they should not be chained at all. A bridge
> does not have to propagate, for instance, .get_edid() to the next
> bridge. That's one of the core design principles in this series, I want
> to keep the bridges as simple as possible, and move the complexity of
> the boilerplate code that is currently copied all around to helpers. See
> patch "drm: Add helper to create a connector for a chain of bridges" for
> more information about how this is used, with a helper that delegates
> the connector operations to the correct bridge in the chain based on the
> ops reported by each bridge.

Sounds like we have a general discussion of who should be owning the
topology around bridges. I guess there's arguments for both positions ...

Not sure what to do, aside from that I'm not a huge fan of flags
everywhere. But if that's the best we can do, so be it :-/
-Daniel

> 
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > ---
> > > >  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > > >  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > > >  2 files changed, 261 insertions(+), 1 deletion(-)
> > > >
> > > > diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > > > index 519577f363e3..3c2a255df7af 100644
> > > > --- a/drivers/gpu/drm/drm_bridge.c
> > > > +++ b/drivers/gpu/drm/drm_bridge.c
> > > > @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > > >   */
> > > >  void drm_bridge_add(struct drm_bridge *bridge)
> > > >  {
> > > > +	mutex_init(&bridge->hpd_mutex);
> > > > +
> > > >  	mutex_lock(&bridge_lock);
> > > >  	list_add_tail(&bridge->list, &bridge_list);
> > > >  	mutex_unlock(&bridge_lock);
> > > > @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > > >  	mutex_lock(&bridge_lock);
> > > >  	list_del_init(&bridge->list);
> > > >  	mutex_unlock(&bridge_lock);
> > > > +
> > > > +	mutex_destroy(&bridge->hpd_mutex);
> > > >  }
> > > >  EXPORT_SYMBOL(drm_bridge_remove);
> > > >  
> > > > @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > > >  
> > > > +/**
> > > > + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > > > + * @bridge: bridge control structure
> > > > + * @cb: hot-plug detection callback
> > > > + * @data: data to be passed to the hot-plug detection callback
> > > > + *
> > > > + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > > > + * hot plug notification callback. From now on the @cb will be called with
> > > > + * @data when an output status change is detected by the bridge, until hot plug
> > > > + * notification gets disabled with drm_bridge_hpd_disable().
> > > > + *
> > > > + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > > > + * bridge->ops. This function shall not be called when the flag is not set.
> > > > + *
> > > > + * Only one hot plug detection callback can be registered at a time, it is an
> > > > + * error to call this function when hot plug detection is already enabled for
> > > > + * the bridge.
> > > > + */
> > > 
> > > To simplify architecture maybe would be better to enable hpd just on
> > > bridge attach:
> > > 
> > > bridge->hpd_cb = cb;
> > > 
> > > bridge->hpd_data = data;
> > > 
> > > ret = drm_bridge_attach(...);
> > 
> > Yeah I like this more. The other problem here is, what if you need more
> > than 1 callback registers on the same bridge hdp signal?
> 
> That's why I decided to hide hide HPD through helpers,
> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> current implementation is limited to a single listener, only the helpers
> would need to be changed to extend that to multiple listeners.
> 
> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> bridge to disable HPD detection when not used. Doing so keeps the bridge
> simple, it only needs to care about reporting HPD events when they're
> enabled, without caring who (if anyone) is listening, and gets clear
> instructions on whether to enable or disable the HPD hardware (in case
> it can be disabled).
> 
> > > This way we could avoid adding new callbacks hpd_(enable|disable)
> > > without big sacrifices.
> > > 
> > > One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > > notifies about sink status change, how it translates to this cb?
> 
> This is something this series doesn't implement. I don't think it would
> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> here. If you can elaborate on what would be needed, I can implement
> that.
> 
> > > > +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > > > +			   void (*cb)(void *data,
> > > > +				      enum drm_connector_status status),
> > > > +			   void *data)
> > > > +{
> > > > +	if (!bridge || !bridge->funcs->hpd_enable)
> > > > +		return;
> > > > +
> > > > +	mutex_lock(&bridge->hpd_mutex);
> > > > +
> > > > +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > > > +		goto unlock;
> > > > +
> > > > +	bridge->hpd_cb = cb;
> > > > +	bridge->hpd_data = data;
> > > > +
> > > > +	bridge->funcs->hpd_enable(bridge);
> > > > +
> > > > +unlock:
> > > > +	mutex_unlock(&bridge->hpd_mutex);
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > > > +
> > > > +/**
> > > > + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > > > + * @bridge: bridge control structure
> > > > + *
> > > > + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > > > + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > > > + * function returns the callback will not be called by the bridge when an
> > > > + * output status change occurs.
> > > > + *
> > > > + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > > > + * bridge->ops. This function shall not be called when the flag is not set.
> > > > + */
> > > > +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > > > +{
> > > > +	if (!bridge || !bridge->funcs->hpd_disable)
> > > > +		return;
> > > > +
> > > > +	mutex_lock(&bridge->hpd_mutex);
> > > > +	bridge->funcs->hpd_disable(bridge);
> > > > +
> > > > +	bridge->hpd_cb = NULL;
> > > > +	bridge->hpd_data = NULL;
> > > > +	mutex_unlock(&bridge->hpd_mutex);
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > > > +
> > > > +/**
> > > > + * drm_bridge_hpd_notify - notify hot plug detection events
> > > > + * @bridge: bridge control structure
> > > > + * @status: output connection status
> > > > + *
> > > > + * Bridge drivers shall call this function to report hot plug events when they
> > > > + * detect a change in the output status, when hot plug detection has been
> > > > + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > > > + *
> > > > + * This function shall be called in a context that can sleep.
> > > > + */
> > > > +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > > > +			   enum drm_connector_status status)
> > > > +{
> > > > +	mutex_lock(&bridge->hpd_mutex);
> > > > +	if (bridge->hpd_cb)
> > > > +		bridge->hpd_cb(bridge->hpd_data, status);
> > 
> > So this isn't quite what I had in mind. Instead something like this:
> > 
> > 	/* iterates over all bridges in the chain containing @bridge */
> > 	for_each_bridge(tmp_bridge, bridge) {
> > 		if (tmp_bridge == bridge)
> > 			continue;
> > 		if (bridge->hpd_notify);
> > 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > 	}
> > 
> > 	encoder = encoder_for_bridge(bridge);
> > 	if (encoder->helper_private->bridge_hpd_notify)
> > 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > 
> > 	dev = bridge->dev
> > 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > 
> > No register callback needed, no locking needed, everyone gets exactly the
> > hpd they want/need.
> 
> I'll reply to this further down the mail thread, to address additional
> comments.
> 
> > > > +	mutex_unlock(&bridge->hpd_mutex);
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > > > +
> > > >  #ifdef CONFIG_OF
> > > >  /**
> > > >   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > > > diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > > > index 08dc15f93ded..b9445aa5b1ef 100644
> > > > --- a/include/drm/drm_bridge.h
> > > > +++ b/include/drm/drm_bridge.h
> > > > @@ -23,8 +23,9 @@
> > > >  #ifndef __DRM_BRIDGE_H__
> > > >  #define __DRM_BRIDGE_H__
> > > >  
> > > > -#include <linux/list.h>
> > > >  #include <linux/ctype.h>
> > > > +#include <linux/list.h>
> > > > +#include <linux/mutex.h>
> > > >  #include <drm/drm_mode_object.h>
> > > >  #include <drm/drm_modes.h>
> > > >  
> > > > @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > > >  	 */
> > > >  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > > >  				    struct drm_atomic_state *state);
> > > > +
> > > > +	/**
> > > > +	 * @detect:
> > > > +	 *
> > > > +	 * Check if anything is attached to the bridge output.
> > > > +	 *
> > > > +	 * This callback is optional, if not implemented the bridge will be
> > > > +	 * considered as always having a component attached to its output.
> > > > +	 * Bridges that implement this callback shall set the
> > > > +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > > > +	 *
> > > > +	 * RETURNS:
> > > > +	 *
> > > > +	 * drm_connector_status indicating the bridge output status.
> > > > +	 */
> > > > +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > > > +
> > > > +	/**
> > > > +	 * @get_modes:
> > > > +	 *
> > > > +	 * Fill all modes currently valid for the sink into the &drm_connector
> > > > +	 * with drm_mode_probed_add().
> > > > +	 *
> > > > +	 * The @get_modes callback is mostly intended to support non-probable
> > > > +	 * displays such as many fixed panels. Bridges that support reading
> > > > +	 * EDID shall leave @get_modes unimplemented and implement the
> > > > +	 * &drm_bridge_funcs->get_edid callback instead.
> > > > +	 *
> > > > +	 * This callback is optional. Bridges that implement it shall set the
> > > > +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > > > +	 *
> > > > +	 * RETURNS:
> > > > +	 *
> > > > +	 * The number of modes added by calling drm_mode_probed_add().
> > > > +	 */
> > > > +	int (*get_modes)(struct drm_bridge *bridge,
> > > > +			 struct drm_connector *connector);
> > > > +
> > > > +	/**
> > > > +	 * @get_edid:
> > > > +	 *
> > > > +	 * Read and parse the EDID data of the connected display.
> > > > +	 *
> > > > +	 * The @get_edid callback is the preferred way of reporting mode
> > > > +	 * information for a display connected to the bridge output. Bridges
> > > > +	 * that support readind EDID shall implement this callback and leave
> > > > +	 * the @get_modes callback unimplemented.
> > > > +	 *
> > > > +	 * The caller of this operation shall first verify the output
> > > > +	 * connection status and refrain from reading EDID from a disconnected
> > > > +	 * output.
> > > > +	 *
> > > > +	 * This callback is optional. Bridges that implement it shall set the
> > > > +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > > > +	 *
> > > > +	 * RETURNS:
> > > > +	 *
> > > > +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > > > +	 * success, or NULL otherwise. The caller is responsible for freeing
> > > > +	 * the returned edid structure with kfree().
> > > > +	 */
> > > > +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > > > +				 struct drm_connector *connector);
> > > 
> > > It overlaps with get_modes, I guess presence of one ops should disallow
> > > presence of another one?
> > > 
> > > I am not really convinced we need this op at all, cannot we just assign
> > > some helper function to .get_modes cb, which will do the same?
> > 
> > Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > case, and require that if it has an edid it must fill out connector->info
> > and connector->edid correctly.
> 
> I think that's doable, I'll have a look.
> 
> > Btw if a hpd happens, who's responible for making sure the edid/mode list
> > in the connector is up-to-date? With your current callback design that's
> > up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > should guarantee that it'll first walk the connectors to update status and
> > edid/mode list for the final drm_connector. And then instead of just
> > passing the simple "status", it'll pass the connector, with everything
> > correctly updated.
> > 
> > Otherwise everyone interested in that hpd signal will go and re-fetch the
> > edid, which is not so awesome :-)
> 
> With the current design there's a single listener, so it's not a big
> deal :-) Furthermore, the listener is the helper that creates a
> connector on top of a chain of bridges, so it's a pretty good place to
> handle this. See the call to drm_kms_helper_hotplug_event() in
> drm_bridge_connector_hpd_cb().
> 
> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> big of a requirement as a prerequisite for this series (or as part of
> this series). We have hardware that can report HPD with various level of
> details (from "something happened on a connector" to "this particular
> event happened on this particular connector"), and we channel that
> through helpers such as drm_kms_helper_hotplug_event() that lose the
> details and go through a heavy mechanism to refetch everything. I
> understand this is needed in many cases, but I think there's room for
> improvement. This series, in my opinion, doesn't go in the wrong
> direction in that regard, as it eventually calls
> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> on top of it. I'm even willing to work on this, provided I get feedback
> on what is desired.
> 
> > > > +	/**
> > > > +	 * @lost_hotplug:
> > > > +	 *
> > > > +	 * Notify the bridge of display disconnection.
> > > > +	 *
> > > > +	 * This callback is optional, it may be implemented by bridges that
> > > > +	 * need to be notified of display disconnection for internal reasons.
> > > > +	 * One use case is to reset the internal state of CEC controllers for
> > > > +	 * HDMI bridges.
> > > > +	 */
> > > > +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > > > +
> > > > +	/**
> > > > +	 * @hpd_enable:
> > > > +	 *
> > > > +	 * Enable hot plug detection. From now on the bridge shall call
> > > > +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > > > +	 * connection status, until hot plug detection gets disabled with
> > > > +	 * @hpd_disable.
> > > > +	 *
> > > > +	 * This callback is optional and shall only be implemented by bridges
> > > > +	 * that support hot-plug notification without polling. Bridges that
> > > > +	 * implement it shall also implement the @hpd_disable callback and set
> > > > +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > > > +	 */
> > > > +	void (*hpd_enable)(struct drm_bridge *bridge);
> > > > +
> > > > +	/**
> > > > +	 * @hpd_disable:
> > > > +	 *
> > > > +	 * Disable hot plug detection. Once this function returns the bridge
> > > > +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > > > +	 * connection status occurs.
> > > > +	 *
> > > > +	 * This callback is optional and shall only be implemented by bridges
> > > > +	 * that support hot-plug notification without polling. Bridges that
> > > > +	 * implement it shall also implement the @hpd_enable callback and set
> > > > +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > > > +	 */
> > > > +	void (*hpd_disable)(struct drm_bridge *bridge);
> > > >  };
> > > >  
> > > >  /**
> > > > @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > > >  	bool dual_link;
> > > >  };
> > > >  
> > > > +/**
> > > > + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > > > + */
> > > > +enum drm_bridge_ops {
> > > > +	/**
> > > > +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > > > +	 * its output. Bridges that set this flag shall implement the
> > > > +	 * &drm_bridge_funcs->detect callback.
> > > > +	 */
> > > > +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > > > +	/**
> > > > +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > > > +	 * connected to its output. Bridges that set this flag shall implement
> > > > +	 * the &drm_bridge_funcs->get_edid callback.
> > > > +	 */
> > > > +	DRM_BRIDGE_OP_EDID = BIT(1),
> > > > +	/**
> > > > +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > > > +	 * without requiring polling. Bridges that set this flag shall
> > > > +	 * implement the &drm_bridge_funcs->hpd_enable and
> > > > +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > > > +	 */
> > > > +	DRM_BRIDGE_OP_HPD = BIT(2),
> > > > +	/**
> > > > +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > > > +	 * by the display at its output. This does not include readind EDID
> > > > +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > > > +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > > > +	 */
> > > > +	DRM_BRIDGE_OP_MODES = BIT(3),
> > > > +};
> > > > +
> > > >  /**
> > > >   * struct drm_bridge - central DRM bridge control structure
> > > >   */
> > > > @@ -398,6 +535,29 @@ struct drm_bridge {
> > > >  	const struct drm_bridge_funcs *funcs;
> > > >  	/** @driver_private: pointer to the bridge driver's internal context */
> > > >  	void *driver_private;
> > > > +	/** @ops: bitmask of operations supported by the bridge */
> > > > +	enum drm_bridge_ops ops;
> > > > +	/**
> > > > +	 * @type: Type of the connection at the bridge output
> > > > +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > > > +	 * identifies the type of connected display.
> > > > +	 */
> > > > +	int type;
> > > > +	/** private: */
> > > > +	/**
> > > > +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > > > +	 */
> > > > +	struct mutex hpd_mutex;
> > > > +	/**
> > > > +	 * @hpd_cb: Hot plug detection callback, registered with
> > > > +	 * drm_bridge_hpd_enable().
> > > > +	 */
> > > > +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > > > +	/**
> > > > +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > > > +	 * @hpd_cb.
> > > > +	 */
> > > > +	void *hpd_data;
> > > >  };
> > > >  
> > > >  void drm_bridge_add(struct drm_bridge *bridge);
> > > > @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > > >  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > > >  			      struct drm_atomic_state *state);
> > > >  
> > > > +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > > > +			   void (*cb)(void *data,
> > > > +				      enum drm_connector_status status),
> > > > +			   void *data);
> > > > +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > > > +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > > > +			   enum drm_connector_status status);
> > > > +
> > > >  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > > >  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > > >  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 12:43           ` Daniel Vetter
@ 2019-08-14 12:56             ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-14 12:56 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Wed, Aug 14, 2019 at 02:43:21PM +0200, Daniel Vetter wrote:
> On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> > On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> >> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>> Hi Laurent,
> >>> 
> >>> I like the approach, current practice when almost every bridge should
> >>> optionally implement connector, or alternatively downstream bridge or
> >>> panel is very painful.
> >> 
> >> Yeah I think this looks mostly reasonable. Some api design comments on top
> >> of Andrzej', with the fair warning that I didn't bother to read up on how
> >> it's all used in the end. I probably should go and do that, at least to
> >> get a feeling for what your hpd_cb usually does.
> >> 
> >>> More comments inlined.
> >>> 
> >>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>> To support implementation of DRM connectors on top of DRM bridges
> >>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>> data:
> >>>>
> >>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>   retrieval operations
> >>>> - Bitmask of supported operations
> >>> 
> >>> 
> >>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>> operation's callback?
> >> 
> >> Yeah also not a huge fan of these bitmasks. Smells like
> >> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >> add, generally good excuse to not have to think through the design between
> >> different parts of drivers - "just" add another flag.
> > 
> > The reason is that a bridge may support an operation (as in implemented
> > in the bridge hardware), but that operation may not be supported on a
> > particular board. For instance an HDMI encoder may support reading EDID
> > when the DDC lines are connected to the encoder, but a board may connect
> > the DDC lines to an I2C port of the SoC. We thus need to decouple
> > if a particular instance of the device supports the operation (exposed
> > by the ops flags) from the function pointers.
> > 
> > We could of course allocate the drm_bridge_funcs structure dynamically
> > for each bridge instance, and fill it with function pointers manually,
> > leaving the unused ops always NULL, but that would require making the
> > structure writable, which is considered a security issue. That's why I
> > decided to keep the drm_bridge_funcs structure as a global static const
> > structure, and add an ops bitmask.
> 
> Yeah reallocating func structures is not great, I agree ...
> 
> Would an -ENXIO at runtime work too? Or do we need to know this
> statically. It just feels so silly and redundant to have the flag and the
> hook.

That would mean that the operation would need to be called in order to
determine if the bridge offers that particular capability. For some
operations we want to know ahead of time, for instance to determine at
connector creation time if HPD needs to be polled. At that time we don't
necessarily have the required arguments to call the information, and
even if we do, the operation could have side effects that are not
desired.

> >>>> - Bridge output type
> >>>>
> >>>> Add and document these.
> >>>>
> >>>> Three new bridge helper functions are also added to handle hot plug
> >>>> notification in a way that is as transparent as possible for the
> >>>> bridges.
> >>> 
> >>> Documentation of new opses does not explain how it should cooperate with
> >>> bridge chaining, I suppose they should be chained explicitly, am I
> >>> right? More comments about it later.
> > 
> > No, the whole point is that they should not be chained at all. A bridge
> > does not have to propagate, for instance, .get_edid() to the next
> > bridge. That's one of the core design principles in this series, I want
> > to keep the bridges as simple as possible, and move the complexity of
> > the boilerplate code that is currently copied all around to helpers. See
> > patch "drm: Add helper to create a connector for a chain of bridges" for
> > more information about how this is used, with a helper that delegates
> > the connector operations to the correct bridge in the chain based on the
> > ops reported by each bridge.
> 
> Sounds like we have a general discussion of who should be owning the
> topology around bridges. I guess there's arguments for both positions ...
> 
> Not sure what to do, aside from that I'm not a huge fan of flags
> everywhere. But if that's the best we can do, so be it :-/

I understand your concern, and I wasn't very happy to have to introduce
a bitmask in addition to function pointers, but it was the best
middleground I could find. I'll keep your comments in mind in case I can
find a better solution, but so far I believe this is the best option to
keep bridge code simple and to make the API well-defined.

> >>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>> ---
> >>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>
> >>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>> index 519577f363e3..3c2a255df7af 100644
> >>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>   */
> >>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>  {
> >>>> +	mutex_init(&bridge->hpd_mutex);
> >>>> +
> >>>>  	mutex_lock(&bridge_lock);
> >>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>  	mutex_unlock(&bridge_lock);
> >>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>  	mutex_lock(&bridge_lock);
> >>>>  	list_del_init(&bridge->list);
> >>>>  	mutex_unlock(&bridge_lock);
> >>>> +
> >>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>  }
> >>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>  
> >>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>  }
> >>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>  
> >>>> +/**
> >>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>> + * @bridge: bridge control structure
> >>>> + * @cb: hot-plug detection callback
> >>>> + * @data: data to be passed to the hot-plug detection callback
> >>>> + *
> >>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>> + *
> >>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>> + *
> >>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>> + * error to call this function when hot plug detection is already enabled for
> >>>> + * the bridge.
> >>>> + */
> >>> 
> >>> To simplify architecture maybe would be better to enable hpd just on
> >>> bridge attach:
> >>> 
> >>> bridge->hpd_cb = cb;
> >>> 
> >>> bridge->hpd_data = data;
> >>> 
> >>> ret = drm_bridge_attach(...);
> >> 
> >> Yeah I like this more. The other problem here is, what if you need more
> >> than 1 callback registers on the same bridge hdp signal?
> > 
> > That's why I decided to hide hide HPD through helpers,
> > drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> > side, and drm_bridge_hpd_notify() on the event reporter side. While the
> > current implementation is limited to a single listener, only the helpers
> > would need to be changed to extend that to multiple listeners.
> > 
> > Note that the .hpd_enable() and .hpd_disable() operations also allow the
> > bridge to disable HPD detection when not used. Doing so keeps the bridge
> > simple, it only needs to care about reporting HPD events when they're
> > enabled, without caring who (if anyone) is listening, and gets clear
> > instructions on whether to enable or disable the HPD hardware (in case
> > it can be disabled).
> > 
> >>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>> without big sacrifices.
> >>> 
> >>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>> notifies about sink status change, how it translates to this cb?
> > 
> > This is something this series doesn't implement. I don't think it would
> > be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> > here. If you can elaborate on what would be needed, I can implement
> > that.
> > 
> >>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>> +			   void (*cb)(void *data,
> >>>> +				      enum drm_connector_status status),
> >>>> +			   void *data)
> >>>> +{
> >>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>> +		return;
> >>>> +
> >>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>> +
> >>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>> +		goto unlock;
> >>>> +
> >>>> +	bridge->hpd_cb = cb;
> >>>> +	bridge->hpd_data = data;
> >>>> +
> >>>> +	bridge->funcs->hpd_enable(bridge);
> >>>> +
> >>>> +unlock:
> >>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>> +}
> >>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>> +
> >>>> +/**
> >>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>> + * @bridge: bridge control structure
> >>>> + *
> >>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>> + * function returns the callback will not be called by the bridge when an
> >>>> + * output status change occurs.
> >>>> + *
> >>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>> + */
> >>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>> +{
> >>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>> +		return;
> >>>> +
> >>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>> +	bridge->funcs->hpd_disable(bridge);
> >>>> +
> >>>> +	bridge->hpd_cb = NULL;
> >>>> +	bridge->hpd_data = NULL;
> >>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>> +}
> >>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>> +
> >>>> +/**
> >>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>> + * @bridge: bridge control structure
> >>>> + * @status: output connection status
> >>>> + *
> >>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>> + * detect a change in the output status, when hot plug detection has been
> >>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>> + *
> >>>> + * This function shall be called in a context that can sleep.
> >>>> + */
> >>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>> +			   enum drm_connector_status status)
> >>>> +{
> >>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>> +	if (bridge->hpd_cb)
> >>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >> 
> >> So this isn't quite what I had in mind. Instead something like this:
> >> 
> >> 	/* iterates over all bridges in the chain containing @bridge */
> >> 	for_each_bridge(tmp_bridge, bridge) {
> >> 		if (tmp_bridge == bridge)
> >> 			continue;
> >> 		if (bridge->hpd_notify);
> >> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >> 	}
> >> 
> >> 	encoder = encoder_for_bridge(bridge);
> >> 	if (encoder->helper_private->bridge_hpd_notify)
> >> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >> 
> >> 	dev = bridge->dev
> >> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >> 
> >> No register callback needed, no locking needed, everyone gets exactly the
> >> hpd they want/need.
> > 
> > I'll reply to this further down the mail thread, to address additional
> > comments.
> > 
> >>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>> +}
> >>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>> +
> >>>>  #ifdef CONFIG_OF
> >>>>  /**
> >>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>> --- a/include/drm/drm_bridge.h
> >>>> +++ b/include/drm/drm_bridge.h
> >>>> @@ -23,8 +23,9 @@
> >>>>  #ifndef __DRM_BRIDGE_H__
> >>>>  #define __DRM_BRIDGE_H__
> >>>>  
> >>>> -#include <linux/list.h>
> >>>>  #include <linux/ctype.h>
> >>>> +#include <linux/list.h>
> >>>> +#include <linux/mutex.h>
> >>>>  #include <drm/drm_mode_object.h>
> >>>>  #include <drm/drm_modes.h>
> >>>>  
> >>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>  	 */
> >>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>  				    struct drm_atomic_state *state);
> >>>> +
> >>>> +	/**
> >>>> +	 * @detect:
> >>>> +	 *
> >>>> +	 * Check if anything is attached to the bridge output.
> >>>> +	 *
> >>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>> +	 * considered as always having a component attached to its output.
> >>>> +	 * Bridges that implement this callback shall set the
> >>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>> +	 *
> >>>> +	 * RETURNS:
> >>>> +	 *
> >>>> +	 * drm_connector_status indicating the bridge output status.
> >>>> +	 */
> >>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>> +
> >>>> +	/**
> >>>> +	 * @get_modes:
> >>>> +	 *
> >>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>> +	 * with drm_mode_probed_add().
> >>>> +	 *
> >>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>> +	 *
> >>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>> +	 *
> >>>> +	 * RETURNS:
> >>>> +	 *
> >>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>> +	 */
> >>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>> +			 struct drm_connector *connector);
> >>>> +
> >>>> +	/**
> >>>> +	 * @get_edid:
> >>>> +	 *
> >>>> +	 * Read and parse the EDID data of the connected display.
> >>>> +	 *
> >>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>> +	 * information for a display connected to the bridge output. Bridges
> >>>> +	 * that support readind EDID shall implement this callback and leave
> >>>> +	 * the @get_modes callback unimplemented.
> >>>> +	 *
> >>>> +	 * The caller of this operation shall first verify the output
> >>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>> +	 * output.
> >>>> +	 *
> >>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>> +	 *
> >>>> +	 * RETURNS:
> >>>> +	 *
> >>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>> +	 * the returned edid structure with kfree().
> >>>> +	 */
> >>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>> +				 struct drm_connector *connector);
> >>> 
> >>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>> presence of another one?
> >>> 
> >>> I am not really convinced we need this op at all, cannot we just assign
> >>> some helper function to .get_modes cb, which will do the same?
> >> 
> >> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >> case, and require that if it has an edid it must fill out connector->info
> >> and connector->edid correctly.
> > 
> > I think that's doable, I'll have a look.
> > 
> >> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >> in the connector is up-to-date? With your current callback design that's
> >> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >> should guarantee that it'll first walk the connectors to update status and
> >> edid/mode list for the final drm_connector. And then instead of just
> >> passing the simple "status", it'll pass the connector, with everything
> >> correctly updated.
> >> 
> >> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >> edid, which is not so awesome :-)
> > 
> > With the current design there's a single listener, so it's not a big
> > deal :-) Furthermore, the listener is the helper that creates a
> > connector on top of a chain of bridges, so it's a pretty good place to
> > handle this. See the call to drm_kms_helper_hotplug_event() in
> > drm_bridge_connector_hpd_cb().
> > 
> > I'm all for reworking HPD and mode fetching, but I think it's a bit too
> > big of a requirement as a prerequisite for this series (or as part of
> > this series). We have hardware that can report HPD with various level of
> > details (from "something happened on a connector" to "this particular
> > event happened on this particular connector"), and we channel that
> > through helpers such as drm_kms_helper_hotplug_event() that lose the
> > details and go through a heavy mechanism to refetch everything. I
> > understand this is needed in many cases, but I think there's room for
> > improvement. This series, in my opinion, doesn't go in the wrong
> > direction in that regard, as it eventually calls
> > drm_kms_helper_hotplug_event(), so I think improvements would make sense
> > on top of it. I'm even willing to work on this, provided I get feedback
> > on what is desired.
> > 
> >>>> +	/**
> >>>> +	 * @lost_hotplug:
> >>>> +	 *
> >>>> +	 * Notify the bridge of display disconnection.
> >>>> +	 *
> >>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>> +	 * HDMI bridges.
> >>>> +	 */
> >>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>> +
> >>>> +	/**
> >>>> +	 * @hpd_enable:
> >>>> +	 *
> >>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>> +	 * connection status, until hot plug detection gets disabled with
> >>>> +	 * @hpd_disable.
> >>>> +	 *
> >>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>> +	 */
> >>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>> +
> >>>> +	/**
> >>>> +	 * @hpd_disable:
> >>>> +	 *
> >>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>> +	 * connection status occurs.
> >>>> +	 *
> >>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>> +	 */
> >>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>  };
> >>>>  
> >>>>  /**
> >>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>  	bool dual_link;
> >>>>  };
> >>>>  
> >>>> +/**
> >>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>> + */
> >>>> +enum drm_bridge_ops {
> >>>> +	/**
> >>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>> +	 * its output. Bridges that set this flag shall implement the
> >>>> +	 * &drm_bridge_funcs->detect callback.
> >>>> +	 */
> >>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>> +	/**
> >>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>> +	 */
> >>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>> +	/**
> >>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>> +	 */
> >>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>> +	/**
> >>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>> +	 * by the display at its output. This does not include readind EDID
> >>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>> +	 */
> >>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>> +};
> >>>> +
> >>>>  /**
> >>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>   */
> >>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>  	const struct drm_bridge_funcs *funcs;
> >>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>  	void *driver_private;
> >>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>> +	enum drm_bridge_ops ops;
> >>>> +	/**
> >>>> +	 * @type: Type of the connection at the bridge output
> >>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>> +	 * identifies the type of connected display.
> >>>> +	 */
> >>>> +	int type;
> >>>> +	/** private: */
> >>>> +	/**
> >>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>> +	 */
> >>>> +	struct mutex hpd_mutex;
> >>>> +	/**
> >>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>> +	 * drm_bridge_hpd_enable().
> >>>> +	 */
> >>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>> +	/**
> >>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>> +	 * @hpd_cb.
> >>>> +	 */
> >>>> +	void *hpd_data;
> >>>>  };
> >>>>  
> >>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>  			      struct drm_atomic_state *state);
> >>>>  
> >>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>> +			   void (*cb)(void *data,
> >>>> +				      enum drm_connector_status status),
> >>>> +			   void *data);
> >>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>> +			   enum drm_connector_status status);
> >>>> +
> >>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-08 18:36           ` Laurent Pinchart
@ 2019-08-14 13:03             ` Daniel Vetter
  2019-08-14 13:30               ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 13:03 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> > On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> > > On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > >> Hi Laurent,
> > >> 
> > >> I like the approach, current practice when almost every bridge should
> > >> optionally implement connector, or alternatively downstream bridge or
> > >> panel is very painful.
> > > 
> > > Yeah I think this looks mostly reasonable. Some api design comments on top
> > > of Andrzej', with the fair warning that I didn't bother to read up on how
> > > it's all used in the end. I probably should go and do that, at least to
> > > get a feeling for what your hpd_cb usually does.
> > > 
> > >> More comments inlined.
> > >> 
> > >> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>> To support implementation of DRM connectors on top of DRM bridges
> > >>> instead of by bridges, the drm_bridge needs to expose new operations and
> > >>> data:
> > >>>
> > >>> - Output detection, hot-plug notification, mode retrieval and EDID
> > >>>   retrieval operations
> > >>> - Bitmask of supported operations
> > >> 
> > >> 
> > >> Why do we need these bitmask at all? Why cannot we rely on presence of
> > >> operation's callback?
> > > 
> > > Yeah also not a huge fan of these bitmasks. Smells like
> > > DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > > add, generally good excuse to not have to think through the design between
> > > different parts of drivers - "just" add another flag.
> > 
> > The reason is that a bridge may support an operation (as in implemented
> > in the bridge hardware), but that operation may not be supported on a
> > particular board. For instance an HDMI encoder may support reading EDID
> > when the DDC lines are connected to the encoder, but a board may connect
> > the DDC lines to an I2C port of the SoC. We thus need to decouple
> > if a particular instance of the device supports the operation (exposed
> > by the ops flags) from the function pointers.
> > 
> > We could of course allocate the drm_bridge_funcs structure dynamically
> > for each bridge instance, and fill it with function pointers manually,
> > leaving the unused ops always NULL, but that would require making the
> > structure writable, which is considered a security issue. That's why I
> > decided to keep the drm_bridge_funcs structure as a global static const
> > structure, and add an ops bitmask.
> > 
> > >>> - Bridge output type
> > >>>
> > >>> Add and document these.
> > >>>
> > >>> Three new bridge helper functions are also added to handle hot plug
> > >>> notification in a way that is as transparent as possible for the
> > >>> bridges.
> > >> 
> > >> Documentation of new opses does not explain how it should cooperate with
> > >> bridge chaining, I suppose they should be chained explicitly, am I
> > >> right? More comments about it later.
> > 
> > No, the whole point is that they should not be chained at all. A bridge
> > does not have to propagate, for instance, .get_edid() to the next
> > bridge. That's one of the core design principles in this series, I want
> > to keep the bridges as simple as possible, and move the complexity of
> > the boilerplate code that is currently copied all around to helpers. See
> > patch "drm: Add helper to create a connector for a chain of bridges" for
> > more information about how this is used, with a helper that delegates
> > the connector operations to the correct bridge in the chain based on the
> > ops reported by each bridge.
> > 
> > >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>> ---
> > >>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >>>  2 files changed, 261 insertions(+), 1 deletion(-)
> > >>>
> > >>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > >>> index 519577f363e3..3c2a255df7af 100644
> > >>> --- a/drivers/gpu/drm/drm_bridge.c
> > >>> +++ b/drivers/gpu/drm/drm_bridge.c
> > >>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >>>   */
> > >>>  void drm_bridge_add(struct drm_bridge *bridge)
> > >>>  {
> > >>> +	mutex_init(&bridge->hpd_mutex);
> > >>> +
> > >>>  	mutex_lock(&bridge_lock);
> > >>>  	list_add_tail(&bridge->list, &bridge_list);
> > >>>  	mutex_unlock(&bridge_lock);
> > >>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >>>  	mutex_lock(&bridge_lock);
> > >>>  	list_del_init(&bridge->list);
> > >>>  	mutex_unlock(&bridge_lock);
> > >>> +
> > >>> +	mutex_destroy(&bridge->hpd_mutex);
> > >>>  }
> > >>>  EXPORT_SYMBOL(drm_bridge_remove);
> > >>>  
> > >>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>  }
> > >>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >>>  
> > >>> +/**
> > >>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > >>> + * @bridge: bridge control structure
> > >>> + * @cb: hot-plug detection callback
> > >>> + * @data: data to be passed to the hot-plug detection callback
> > >>> + *
> > >>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > >>> + * hot plug notification callback. From now on the @cb will be called with
> > >>> + * @data when an output status change is detected by the bridge, until hot plug
> > >>> + * notification gets disabled with drm_bridge_hpd_disable().
> > >>> + *
> > >>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>> + *
> > >>> + * Only one hot plug detection callback can be registered at a time, it is an
> > >>> + * error to call this function when hot plug detection is already enabled for
> > >>> + * the bridge.
> > >>> + */
> > >> 
> > >> To simplify architecture maybe would be better to enable hpd just on
> > >> bridge attach:
> > >> 
> > >> bridge->hpd_cb = cb;
> > >> 
> > >> bridge->hpd_data = data;
> > >> 
> > >> ret = drm_bridge_attach(...);
> > > 
> > > Yeah I like this more. The other problem here is, what if you need more
> > > than 1 callback registers on the same bridge hdp signal?
> > 
> > That's why I decided to hide hide HPD through helpers,
> > drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> > side, and drm_bridge_hpd_notify() on the event reporter side. While the
> > current implementation is limited to a single listener, only the helpers
> > would need to be changed to extend that to multiple listeners.
> > 
> > Note that the .hpd_enable() and .hpd_disable() operations also allow the
> > bridge to disable HPD detection when not used. Doing so keeps the bridge
> > simple, it only needs to care about reporting HPD events when they're
> > enabled, without caring who (if anyone) is listening, and gets clear
> > instructions on whether to enable or disable the HPD hardware (in case
> > it can be disabled).
> > 
> > >> This way we could avoid adding new callbacks hpd_(enable|disable)
> > >> without big sacrifices.
> > >> 
> > >> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > >> notifies about sink status change, how it translates to this cb?
> > 
> > This is something this series doesn't implement. I don't think it would
> > be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> > here. If you can elaborate on what would be needed, I can implement
> > that.
> > 
> > >>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>> +			   void (*cb)(void *data,
> > >>> +				      enum drm_connector_status status),
> > >>> +			   void *data)
> > >>> +{
> > >>> +	if (!bridge || !bridge->funcs->hpd_enable)
> > >>> +		return;
> > >>> +
> > >>> +	mutex_lock(&bridge->hpd_mutex);
> > >>> +
> > >>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > >>> +		goto unlock;
> > >>> +
> > >>> +	bridge->hpd_cb = cb;
> > >>> +	bridge->hpd_data = data;
> > >>> +
> > >>> +	bridge->funcs->hpd_enable(bridge);
> > >>> +
> > >>> +unlock:
> > >>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>> +}
> > >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > >>> +
> > >>> +/**
> > >>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > >>> + * @bridge: bridge control structure
> > >>> + *
> > >>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > >>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > >>> + * function returns the callback will not be called by the bridge when an
> > >>> + * output status change occurs.
> > >>> + *
> > >>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>> + */
> > >>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > >>> +{
> > >>> +	if (!bridge || !bridge->funcs->hpd_disable)
> > >>> +		return;
> > >>> +
> > >>> +	mutex_lock(&bridge->hpd_mutex);
> > >>> +	bridge->funcs->hpd_disable(bridge);
> > >>> +
> > >>> +	bridge->hpd_cb = NULL;
> > >>> +	bridge->hpd_data = NULL;
> > >>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>> +}
> > >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > >>> +
> > >>> +/**
> > >>> + * drm_bridge_hpd_notify - notify hot plug detection events
> > >>> + * @bridge: bridge control structure
> > >>> + * @status: output connection status
> > >>> + *
> > >>> + * Bridge drivers shall call this function to report hot plug events when they
> > >>> + * detect a change in the output status, when hot plug detection has been
> > >>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > >>> + *
> > >>> + * This function shall be called in a context that can sleep.
> > >>> + */
> > >>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>> +			   enum drm_connector_status status)
> > >>> +{
> > >>> +	mutex_lock(&bridge->hpd_mutex);
> > >>> +	if (bridge->hpd_cb)
> > >>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > > 
> > > So this isn't quite what I had in mind. Instead something like this:
> > > 
> > > 	/* iterates over all bridges in the chain containing @bridge */
> > > 	for_each_bridge(tmp_bridge, bridge) {
> > > 		if (tmp_bridge == bridge)
> > > 			continue;
> > > 		if (bridge->hpd_notify);
> > > 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > > 	}
> > > 
> > > 	encoder = encoder_for_bridge(bridge);
> > > 	if (encoder->helper_private->bridge_hpd_notify)
> > > 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > > 
> > > 	dev = bridge->dev
> > > 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > > 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > > 
> > > No register callback needed, no locking needed, everyone gets exactly the
> > > hpd they want/need.
> > 
> > I'll reply to this further down the mail thread, to address additional
> > comments.
> > 
> > >>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>> +}
> > >>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > >>> +
> > >>>  #ifdef CONFIG_OF
> > >>>  /**
> > >>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > >>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > >>> index 08dc15f93ded..b9445aa5b1ef 100644
> > >>> --- a/include/drm/drm_bridge.h
> > >>> +++ b/include/drm/drm_bridge.h
> > >>> @@ -23,8 +23,9 @@
> > >>>  #ifndef __DRM_BRIDGE_H__
> > >>>  #define __DRM_BRIDGE_H__
> > >>>  
> > >>> -#include <linux/list.h>
> > >>>  #include <linux/ctype.h>
> > >>> +#include <linux/list.h>
> > >>> +#include <linux/mutex.h>
> > >>>  #include <drm/drm_mode_object.h>
> > >>>  #include <drm/drm_modes.h>
> > >>>  
> > >>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >>>  	 */
> > >>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >>>  				    struct drm_atomic_state *state);
> > >>> +
> > >>> +	/**
> > >>> +	 * @detect:
> > >>> +	 *
> > >>> +	 * Check if anything is attached to the bridge output.
> > >>> +	 *
> > >>> +	 * This callback is optional, if not implemented the bridge will be
> > >>> +	 * considered as always having a component attached to its output.
> > >>> +	 * Bridges that implement this callback shall set the
> > >>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > >>> +	 *
> > >>> +	 * RETURNS:
> > >>> +	 *
> > >>> +	 * drm_connector_status indicating the bridge output status.
> > >>> +	 */
> > >>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > >>> +
> > >>> +	/**
> > >>> +	 * @get_modes:
> > >>> +	 *
> > >>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> > >>> +	 * with drm_mode_probed_add().
> > >>> +	 *
> > >>> +	 * The @get_modes callback is mostly intended to support non-probable
> > >>> +	 * displays such as many fixed panels. Bridges that support reading
> > >>> +	 * EDID shall leave @get_modes unimplemented and implement the
> > >>> +	 * &drm_bridge_funcs->get_edid callback instead.
> > >>> +	 *
> > >>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > >>> +	 *
> > >>> +	 * RETURNS:
> > >>> +	 *
> > >>> +	 * The number of modes added by calling drm_mode_probed_add().
> > >>> +	 */
> > >>> +	int (*get_modes)(struct drm_bridge *bridge,
> > >>> +			 struct drm_connector *connector);
> > >>> +
> > >>> +	/**
> > >>> +	 * @get_edid:
> > >>> +	 *
> > >>> +	 * Read and parse the EDID data of the connected display.
> > >>> +	 *
> > >>> +	 * The @get_edid callback is the preferred way of reporting mode
> > >>> +	 * information for a display connected to the bridge output. Bridges
> > >>> +	 * that support readind EDID shall implement this callback and leave
> > >>> +	 * the @get_modes callback unimplemented.
> > >>> +	 *
> > >>> +	 * The caller of this operation shall first verify the output
> > >>> +	 * connection status and refrain from reading EDID from a disconnected
> > >>> +	 * output.
> > >>> +	 *
> > >>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > >>> +	 *
> > >>> +	 * RETURNS:
> > >>> +	 *
> > >>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > >>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> > >>> +	 * the returned edid structure with kfree().
> > >>> +	 */
> > >>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > >>> +				 struct drm_connector *connector);
> > >> 
> > >> It overlaps with get_modes, I guess presence of one ops should disallow
> > >> presence of another one?
> > >> 
> > >> I am not really convinced we need this op at all, cannot we just assign
> > >> some helper function to .get_modes cb, which will do the same?
> > > 
> > > Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > > case, and require that if it has an edid it must fill out connector->info
> > > and connector->edid correctly.
> > 
> > I think that's doable, I'll have a look.
> 
> So I had a look, and while this is doable, it would essentially mean
> that all bridges that retrieve modes from EDID would have to roll out
> their own version of the following code:
> 
> static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> 					       struct drm_bridge *bridge)
> {
> 	enum drm_connector_status status;
> 	struct edid *edid;
> 	int n;
> 
> 	status = drm_bridge_connector_detect(connector, false);
> 	if (status != connector_status_connected)
> 		goto no_edid;
> 
> 	edid = bridge->funcs->get_edid(bridge, connector);
> 	if (!edid || !drm_edid_is_valid(edid)) {
> 		kfree(edid);
> 		goto no_edid;
> 	}
> 
> 	drm_connector_update_edid_property(connector, edid);
> 	n = drm_add_edid_modes(connector, edid);
> 
> 	kfree(edid);
> 	return n;
> 
> no_edid:
> 	drm_connector_update_edid_property(connector, NULL);
> 	return 0;
> }
> 
> Is this desired ?

We store the edid, and we store a lot of decoded information in
drm_connector->display_info. Can't they just look there? Re-fetching the
edid definitely sounds like the wrong thing to do.

We might run into some ordering issue here I guess with hotplugs and who's
fetching the edid and everything like that.

Also maybe I'm missing the point here, and thinking too much of
->get_modes on the connector. But then I'm not clear on why the bridge
needs the connector, and why it instead can't just return the edid it can
read and let the caller/core figure out everything else?
-Daniel

> 
> > > Btw if a hpd happens, who's responible for making sure the edid/mode list
> > > in the connector is up-to-date? With your current callback design that's
> > > up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > > should guarantee that it'll first walk the connectors to update status and
> > > edid/mode list for the final drm_connector. And then instead of just
> > > passing the simple "status", it'll pass the connector, with everything
> > > correctly updated.
> > > 
> > > Otherwise everyone interested in that hpd signal will go and re-fetch the
> > > edid, which is not so awesome :-)
> > 
> > With the current design there's a single listener, so it's not a big
> > deal :-) Furthermore, the listener is the helper that creates a
> > connector on top of a chain of bridges, so it's a pretty good place to
> > handle this. See the call to drm_kms_helper_hotplug_event() in
> > drm_bridge_connector_hpd_cb().
> > 
> > I'm all for reworking HPD and mode fetching, but I think it's a bit too
> > big of a requirement as a prerequisite for this series (or as part of
> > this series). We have hardware that can report HPD with various level of
> > details (from "something happened on a connector" to "this particular
> > event happened on this particular connector"), and we channel that
> > through helpers such as drm_kms_helper_hotplug_event() that lose the
> > details and go through a heavy mechanism to refetch everything. I
> > understand this is needed in many cases, but I think there's room for
> > improvement. This series, in my opinion, doesn't go in the wrong
> > direction in that regard, as it eventually calls
> > drm_kms_helper_hotplug_event(), so I think improvements would make sense
> > on top of it. I'm even willing to work on this, provided I get feedback
> > on what is desired.
> > 
> > >>> +	/**
> > >>> +	 * @lost_hotplug:
> > >>> +	 *
> > >>> +	 * Notify the bridge of display disconnection.
> > >>> +	 *
> > >>> +	 * This callback is optional, it may be implemented by bridges that
> > >>> +	 * need to be notified of display disconnection for internal reasons.
> > >>> +	 * One use case is to reset the internal state of CEC controllers for
> > >>> +	 * HDMI bridges.
> > >>> +	 */
> > >>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > >>> +
> > >>> +	/**
> > >>> +	 * @hpd_enable:
> > >>> +	 *
> > >>> +	 * Enable hot plug detection. From now on the bridge shall call
> > >>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > >>> +	 * connection status, until hot plug detection gets disabled with
> > >>> +	 * @hpd_disable.
> > >>> +	 *
> > >>> +	 * This callback is optional and shall only be implemented by bridges
> > >>> +	 * that support hot-plug notification without polling. Bridges that
> > >>> +	 * implement it shall also implement the @hpd_disable callback and set
> > >>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>> +	 */
> > >>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> > >>> +
> > >>> +	/**
> > >>> +	 * @hpd_disable:
> > >>> +	 *
> > >>> +	 * Disable hot plug detection. Once this function returns the bridge
> > >>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > >>> +	 * connection status occurs.
> > >>> +	 *
> > >>> +	 * This callback is optional and shall only be implemented by bridges
> > >>> +	 * that support hot-plug notification without polling. Bridges that
> > >>> +	 * implement it shall also implement the @hpd_enable callback and set
> > >>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>> +	 */
> > >>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >>>  };
> > >>>  
> > >>>  /**
> > >>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >>>  	bool dual_link;
> > >>>  };
> > >>>  
> > >>> +/**
> > >>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > >>> + */
> > >>> +enum drm_bridge_ops {
> > >>> +	/**
> > >>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > >>> +	 * its output. Bridges that set this flag shall implement the
> > >>> +	 * &drm_bridge_funcs->detect callback.
> > >>> +	 */
> > >>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > >>> +	/**
> > >>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > >>> +	 * connected to its output. Bridges that set this flag shall implement
> > >>> +	 * the &drm_bridge_funcs->get_edid callback.
> > >>> +	 */
> > >>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> > >>> +	/**
> > >>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > >>> +	 * without requiring polling. Bridges that set this flag shall
> > >>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> > >>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > >>> +	 */
> > >>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> > >>> +	/**
> > >>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > >>> +	 * by the display at its output. This does not include readind EDID
> > >>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > >>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > >>> +	 */
> > >>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> > >>> +};
> > >>> +
> > >>>  /**
> > >>>   * struct drm_bridge - central DRM bridge control structure
> > >>>   */
> > >>> @@ -398,6 +535,29 @@ struct drm_bridge {
> > >>>  	const struct drm_bridge_funcs *funcs;
> > >>>  	/** @driver_private: pointer to the bridge driver's internal context */
> > >>>  	void *driver_private;
> > >>> +	/** @ops: bitmask of operations supported by the bridge */
> > >>> +	enum drm_bridge_ops ops;
> > >>> +	/**
> > >>> +	 * @type: Type of the connection at the bridge output
> > >>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > >>> +	 * identifies the type of connected display.
> > >>> +	 */
> > >>> +	int type;
> > >>> +	/** private: */
> > >>> +	/**
> > >>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > >>> +	 */
> > >>> +	struct mutex hpd_mutex;
> > >>> +	/**
> > >>> +	 * @hpd_cb: Hot plug detection callback, registered with
> > >>> +	 * drm_bridge_hpd_enable().
> > >>> +	 */
> > >>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > >>> +	/**
> > >>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > >>> +	 * @hpd_cb.
> > >>> +	 */
> > >>> +	void *hpd_data;
> > >>>  };
> > >>>  
> > >>>  void drm_bridge_add(struct drm_bridge *bridge);
> > >>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>  			      struct drm_atomic_state *state);
> > >>>  
> > >>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>> +			   void (*cb)(void *data,
> > >>> +				      enum drm_connector_status status),
> > >>> +			   void *data);
> > >>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > >>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>> +			   enum drm_connector_status status);
> > >>> +
> > >>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >>>  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 13:03             ` Daniel Vetter
@ 2019-08-14 13:30               ` Laurent Pinchart
  2019-08-14 17:02                 ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-14 13:30 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Wed, Aug 14, 2019 at 03:03:29PM +0200, Daniel Vetter wrote:
> On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> > On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> >> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> >>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>> Hi Laurent,
> >>>> 
> >>>> I like the approach, current practice when almost every bridge should
> >>>> optionally implement connector, or alternatively downstream bridge or
> >>>> panel is very painful.
> >>> 
> >>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>> it's all used in the end. I probably should go and do that, at least to
> >>> get a feeling for what your hpd_cb usually does.
> >>> 
> >>>> More comments inlined.
> >>>> 
> >>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>> data:
> >>>>>
> >>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>   retrieval operations
> >>>>> - Bitmask of supported operations
> >>>> 
> >>>> 
> >>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>> operation's callback?
> >>> 
> >>> Yeah also not a huge fan of these bitmasks. Smells like
> >>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>> add, generally good excuse to not have to think through the design between
> >>> different parts of drivers - "just" add another flag.
> >> 
> >> The reason is that a bridge may support an operation (as in implemented
> >> in the bridge hardware), but that operation may not be supported on a
> >> particular board. For instance an HDMI encoder may support reading EDID
> >> when the DDC lines are connected to the encoder, but a board may connect
> >> the DDC lines to an I2C port of the SoC. We thus need to decouple
> >> if a particular instance of the device supports the operation (exposed
> >> by the ops flags) from the function pointers.
> >> 
> >> We could of course allocate the drm_bridge_funcs structure dynamically
> >> for each bridge instance, and fill it with function pointers manually,
> >> leaving the unused ops always NULL, but that would require making the
> >> structure writable, which is considered a security issue. That's why I
> >> decided to keep the drm_bridge_funcs structure as a global static const
> >> structure, and add an ops bitmask.
> >> 
> >>>>> - Bridge output type
> >>>>>
> >>>>> Add and document these.
> >>>>>
> >>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>> notification in a way that is as transparent as possible for the
> >>>>> bridges.
> >>>> 
> >>>> Documentation of new opses does not explain how it should cooperate with
> >>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>> right? More comments about it later.
> >> 
> >> No, the whole point is that they should not be chained at all. A bridge
> >> does not have to propagate, for instance, .get_edid() to the next
> >> bridge. That's one of the core design principles in this series, I want
> >> to keep the bridges as simple as possible, and move the complexity of
> >> the boilerplate code that is currently copied all around to helpers. See
> >> patch "drm: Add helper to create a connector for a chain of bridges" for
> >> more information about how this is used, with a helper that delegates
> >> the connector operations to the correct bridge in the chain based on the
> >> ops reported by each bridge.
> >> 
> >>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>> ---
> >>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>
> >>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>   */
> >>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>  {
> >>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>> +
> >>>>>  	mutex_lock(&bridge_lock);
> >>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>  	mutex_unlock(&bridge_lock);
> >>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>  	mutex_lock(&bridge_lock);
> >>>>>  	list_del_init(&bridge->list);
> >>>>>  	mutex_unlock(&bridge_lock);
> >>>>> +
> >>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>  }
> >>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>  
> >>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>  }
> >>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>  
> >>>>> +/**
> >>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>> + * @bridge: bridge control structure
> >>>>> + * @cb: hot-plug detection callback
> >>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>> + *
> >>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>> + *
> >>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>> + *
> >>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>> + * the bridge.
> >>>>> + */
> >>>> 
> >>>> To simplify architecture maybe would be better to enable hpd just on
> >>>> bridge attach:
> >>>> 
> >>>> bridge->hpd_cb = cb;
> >>>> 
> >>>> bridge->hpd_data = data;
> >>>> 
> >>>> ret = drm_bridge_attach(...);
> >>> 
> >>> Yeah I like this more. The other problem here is, what if you need more
> >>> than 1 callback registers on the same bridge hdp signal?
> >> 
> >> That's why I decided to hide hide HPD through helpers,
> >> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> >> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> >> current implementation is limited to a single listener, only the helpers
> >> would need to be changed to extend that to multiple listeners.
> >> 
> >> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> >> bridge to disable HPD detection when not used. Doing so keeps the bridge
> >> simple, it only needs to care about reporting HPD events when they're
> >> enabled, without caring who (if anyone) is listening, and gets clear
> >> instructions on whether to enable or disable the HPD hardware (in case
> >> it can be disabled).
> >> 
> >>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>> without big sacrifices.
> >>>> 
> >>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>> notifies about sink status change, how it translates to this cb?
> >> 
> >> This is something this series doesn't implement. I don't think it would
> >> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> >> here. If you can elaborate on what would be needed, I can implement
> >> that.
> >> 
> >>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>> +			   void (*cb)(void *data,
> >>>>> +				      enum drm_connector_status status),
> >>>>> +			   void *data)
> >>>>> +{
> >>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>> +		return;
> >>>>> +
> >>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>> +
> >>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>> +		goto unlock;
> >>>>> +
> >>>>> +	bridge->hpd_cb = cb;
> >>>>> +	bridge->hpd_data = data;
> >>>>> +
> >>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>> +
> >>>>> +unlock:
> >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>> +
> >>>>> +/**
> >>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>> + * @bridge: bridge control structure
> >>>>> + *
> >>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>> + * function returns the callback will not be called by the bridge when an
> >>>>> + * output status change occurs.
> >>>>> + *
> >>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>> + */
> >>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>> +{
> >>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>> +		return;
> >>>>> +
> >>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>> +
> >>>>> +	bridge->hpd_cb = NULL;
> >>>>> +	bridge->hpd_data = NULL;
> >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>> +
> >>>>> +/**
> >>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>> + * @bridge: bridge control structure
> >>>>> + * @status: output connection status
> >>>>> + *
> >>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>> + *
> >>>>> + * This function shall be called in a context that can sleep.
> >>>>> + */
> >>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>> +			   enum drm_connector_status status)
> >>>>> +{
> >>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>> +	if (bridge->hpd_cb)
> >>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>> 
> >>> So this isn't quite what I had in mind. Instead something like this:
> >>> 
> >>> 	/* iterates over all bridges in the chain containing @bridge */
> >>> 	for_each_bridge(tmp_bridge, bridge) {
> >>> 		if (tmp_bridge == bridge)
> >>> 			continue;
> >>> 		if (bridge->hpd_notify);
> >>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>> 	}
> >>> 
> >>> 	encoder = encoder_for_bridge(bridge);
> >>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>> 
> >>> 	dev = bridge->dev
> >>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>> 
> >>> No register callback needed, no locking needed, everyone gets exactly the
> >>> hpd they want/need.
> >> 
> >> I'll reply to this further down the mail thread, to address additional
> >> comments.
> >> 
> >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>> +
> >>>>>  #ifdef CONFIG_OF
> >>>>>  /**
> >>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>> --- a/include/drm/drm_bridge.h
> >>>>> +++ b/include/drm/drm_bridge.h
> >>>>> @@ -23,8 +23,9 @@
> >>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>  #define __DRM_BRIDGE_H__
> >>>>>  
> >>>>> -#include <linux/list.h>
> >>>>>  #include <linux/ctype.h>
> >>>>> +#include <linux/list.h>
> >>>>> +#include <linux/mutex.h>
> >>>>>  #include <drm/drm_mode_object.h>
> >>>>>  #include <drm/drm_modes.h>
> >>>>>  
> >>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>  	 */
> >>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>  				    struct drm_atomic_state *state);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @detect:
> >>>>> +	 *
> >>>>> +	 * Check if anything is attached to the bridge output.
> >>>>> +	 *
> >>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>> +	 * considered as always having a component attached to its output.
> >>>>> +	 * Bridges that implement this callback shall set the
> >>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>> +	 *
> >>>>> +	 * RETURNS:
> >>>>> +	 *
> >>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>> +	 */
> >>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @get_modes:
> >>>>> +	 *
> >>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>> +	 * with drm_mode_probed_add().
> >>>>> +	 *
> >>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>> +	 *
> >>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>> +	 *
> >>>>> +	 * RETURNS:
> >>>>> +	 *
> >>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>> +	 */
> >>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>> +			 struct drm_connector *connector);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @get_edid:
> >>>>> +	 *
> >>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>> +	 *
> >>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>> +	 * the @get_modes callback unimplemented.
> >>>>> +	 *
> >>>>> +	 * The caller of this operation shall first verify the output
> >>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>> +	 * output.
> >>>>> +	 *
> >>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>> +	 *
> >>>>> +	 * RETURNS:
> >>>>> +	 *
> >>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>> +	 * the returned edid structure with kfree().
> >>>>> +	 */
> >>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>> +				 struct drm_connector *connector);
> >>>> 
> >>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>> presence of another one?
> >>>> 
> >>>> I am not really convinced we need this op at all, cannot we just assign
> >>>> some helper function to .get_modes cb, which will do the same?
> >>> 
> >>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>> case, and require that if it has an edid it must fill out connector->info
> >>> and connector->edid correctly.
> >> 
> >> I think that's doable, I'll have a look.
> > 
> > So I had a look, and while this is doable, it would essentially mean
> > that all bridges that retrieve modes from EDID would have to roll out
> > their own version of the following code:
> > 
> > static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> > 					       struct drm_bridge *bridge)
> > {
> > 	enum drm_connector_status status;
> > 	struct edid *edid;
> > 	int n;
> > 
> > 	status = drm_bridge_connector_detect(connector, false);
> > 	if (status != connector_status_connected)
> > 		goto no_edid;
> > 
> > 	edid = bridge->funcs->get_edid(bridge, connector);
> > 	if (!edid || !drm_edid_is_valid(edid)) {
> > 		kfree(edid);
> > 		goto no_edid;
> > 	}
> > 
> > 	drm_connector_update_edid_property(connector, edid);
> > 	n = drm_add_edid_modes(connector, edid);
> > 
> > 	kfree(edid);
> > 	return n;
> > 
> > no_edid:
> > 	drm_connector_update_edid_property(connector, NULL);
> > 	return 0;
> > }
> > 
> > Is this desired ?
> 
> We store the edid, and we store a lot of decoded information in
> drm_connector->display_info. Can't they just look there? Re-fetching the
> edid definitely sounds like the wrong thing to do.
> 
> We might run into some ordering issue here I guess with hotplugs and who's
> fetching the edid and everything like that.

That's exactly what I was about to answer after reading your first
paragraph :-) I believe caching EDID is a good idea, but my familiarity
with hotplug-related issues is limited to a handful of systems, and I'm
sure I'm missing some common problems. If you can tell me how you think
this should be done, I can give it a try.

> Also maybe I'm missing the point here, and thinking too much of
> ->get_modes on the connector. But then I'm not clear on why the bridge
> needs the connector, and why it instead can't just return the edid it can
> read and let the caller/core figure out everything else?

That's exactly what the .get_edid() operation that you asked me to
remove did... :-) You didn't like the fact that it duplicated the
.get_modes() logic. Should I add it back, and clearly document
.get_modes() as a fallback used only when the connector doesn't use EDID
?

> >>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>> in the connector is up-to-date? With your current callback design that's
> >>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>> should guarantee that it'll first walk the connectors to update status and
> >>> edid/mode list for the final drm_connector. And then instead of just
> >>> passing the simple "status", it'll pass the connector, with everything
> >>> correctly updated.
> >>> 
> >>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>> edid, which is not so awesome :-)
> >> 
> >> With the current design there's a single listener, so it's not a big
> >> deal :-) Furthermore, the listener is the helper that creates a
> >> connector on top of a chain of bridges, so it's a pretty good place to
> >> handle this. See the call to drm_kms_helper_hotplug_event() in
> >> drm_bridge_connector_hpd_cb().
> >> 
> >> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> >> big of a requirement as a prerequisite for this series (or as part of
> >> this series). We have hardware that can report HPD with various level of
> >> details (from "something happened on a connector" to "this particular
> >> event happened on this particular connector"), and we channel that
> >> through helpers such as drm_kms_helper_hotplug_event() that lose the
> >> details and go through a heavy mechanism to refetch everything. I
> >> understand this is needed in many cases, but I think there's room for
> >> improvement. This series, in my opinion, doesn't go in the wrong
> >> direction in that regard, as it eventually calls
> >> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> >> on top of it. I'm even willing to work on this, provided I get feedback
> >> on what is desired.
> >> 
> >>>>> +	/**
> >>>>> +	 * @lost_hotplug:
> >>>>> +	 *
> >>>>> +	 * Notify the bridge of display disconnection.
> >>>>> +	 *
> >>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>> +	 * HDMI bridges.
> >>>>> +	 */
> >>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @hpd_enable:
> >>>>> +	 *
> >>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>> +	 * @hpd_disable.
> >>>>> +	 *
> >>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>> +	 */
> >>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>> +
> >>>>> +	/**
> >>>>> +	 * @hpd_disable:
> >>>>> +	 *
> >>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>> +	 * connection status occurs.
> >>>>> +	 *
> >>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>> +	 */
> >>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>  };
> >>>>>  
> >>>>>  /**
> >>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>  	bool dual_link;
> >>>>>  };
> >>>>>  
> >>>>> +/**
> >>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>> + */
> >>>>> +enum drm_bridge_ops {
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>> +	/**
> >>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>> +	 */
> >>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>> +};
> >>>>> +
> >>>>>  /**
> >>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>   */
> >>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>  	void *driver_private;
> >>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>> +	enum drm_bridge_ops ops;
> >>>>> +	/**
> >>>>> +	 * @type: Type of the connection at the bridge output
> >>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>> +	 * identifies the type of connected display.
> >>>>> +	 */
> >>>>> +	int type;
> >>>>> +	/** private: */
> >>>>> +	/**
> >>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>> +	 */
> >>>>> +	struct mutex hpd_mutex;
> >>>>> +	/**
> >>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>> +	 * drm_bridge_hpd_enable().
> >>>>> +	 */
> >>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>> +	/**
> >>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>> +	 * @hpd_cb.
> >>>>> +	 */
> >>>>> +	void *hpd_data;
> >>>>>  };
> >>>>>  
> >>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>  			      struct drm_atomic_state *state);
> >>>>>  
> >>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>> +			   void (*cb)(void *data,
> >>>>> +				      enum drm_connector_status status),
> >>>>> +			   void *data);
> >>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>> +			   enum drm_connector_status status);
> >>>>> +
> >>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges
  2019-08-08 19:48       ` Laurent Pinchart
@ 2019-08-14 15:01         ` Daniel Vetter
  2019-08-19 22:16           ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 15:01 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Laurent,

On Thu, Aug 08, 2019 at 10:48:43PM +0300, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Thu, Jul 18, 2019 at 07:01:03PM +0200, Daniel Vetter wrote:
> > On Sun, Jul 07, 2019 at 09:19:02PM +0300, Laurent Pinchart wrote:
> > > Most bridge drivers create a DRM connector to model the connector at the
> > > output of the bridge. This model is historical and has worked pretty
> > > well so far, but causes several issues:
> > > 
> > > - It prevents supporting more complex display pipelines where DRM
> > > connector operations are split over multiple components. For instance a
> > > pipeline with a bridge connected to the DDC signals to read EDID data,
> > > and another one connected to the HPD signal to detect connection and
> > > disconnection, will not be possible to support through this model.
> > > 
> > > - It requires every bridge driver to implement similar connector
> > > handling code, resulting in code duplication.
> > > 
> > > - It assumes that a bridge will either be wired to a connector or to
> > > another bridge, but doesn't support bridges that can be used in both
> > > positions very well (although there is some ad-hoc support for this in
> > > the analogix_dp bridge driver).
> > > 
> > > In order to solve these issues, ownership of the connector needs to be
> > > moved to the display controller driver.
> > > 
> > > To avoid code duplication in display controller drivers, add a new
> > > helper to create and manage a DRM connector backed by a chain of
> > > bridges. All connector operations are delegating to the appropriate
> > > bridge in the chain.
> > > 
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > ---
> > >  drivers/gpu/drm/Makefile               |   3 +-
> > >  drivers/gpu/drm/drm_bridge_connector.c | 385 +++++++++++++++++++++++++
> > >  include/drm/drm_bridge_connector.h     |  18 ++
> > >  3 files changed, 405 insertions(+), 1 deletion(-)
> > >  create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
> > >  create mode 100644 include/drm/drm_bridge_connector.h
> > > 
> > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > index 9f0d2ee35794..1b74653c9db9 100644
> > > --- a/drivers/gpu/drm/Makefile
> > > +++ b/drivers/gpu/drm/Makefile
> > > @@ -37,7 +37,8 @@ drm_vram_helper-y := drm_gem_vram_helper.o \
> > >  		     drm_vram_mm_helper.o
> > >  obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
> > >  
> > > -drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper.o \
> > > +drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
> > > +		drm_dsc.o drm_probe_helper.o \
> > >  		drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
> > >  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
> > >  		drm_simple_kms_helper.o drm_modeset_helper.o \
> > > diff --git a/drivers/gpu/drm/drm_bridge_connector.c b/drivers/gpu/drm/drm_bridge_connector.c
> > > new file mode 100644
> > > index 000000000000..09f2d6bfb561
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/drm_bridge_connector.c
> > > @@ -0,0 +1,385 @@
> > > +// SPDX-License-Identifier: GPL-2.0+
> > > +/*
> > > + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > + */
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/module.h>
> > > +#include <linux/slab.h>
> > > +
> > > +#include <drm/drm_atomic_state_helper.h>
> > > +#include <drm/drm_bridge.h>
> > > +#include <drm/drm_bridge_connector.h>
> > > +#include <drm/drm_connector.h>
> > > +#include <drm/drm_device.h>
> > > +#include <drm/drm_edid.h>
> > > +#include <drm/drm_modeset_helper_vtables.h>
> > > +#include <drm/drm_probe_helper.h>
> > > +
> > > +/**
> > > + * DOC: overview
> > > + *
> > > + * The DRM bridge connector helper object provides a DRM connector
> > > + * implementation that wraps a chain of &struct drm_bridge. The connector
> > > + * operations are fully implemented based on the operations of the bridges in
> > > + * the chain, and don't require any intervention from the display controller
> > > + * driver at runtime.
> > > + *
> > > + * To use the helper, display controller drivers create a bridge connector with
> > > + * a call to drm_bridge_connector_init(). This associates the newly created
> > > + * connector with the chain of bridges passed to the function and registers it
> > > + * with the DRM device. At that point the connector becomes fully usable, no
> > > + * further operation is needed.
> > > + *
> > > + * The DRM bridge connector operations are implemented based on the operations
> > > + * provided by the bridges in the chain. Each connector operation is delegated
> > > + * to the bridge closest to the connector (at the end of the chain) that
> > > + * provides the relevant functionality.
> > > + *
> > > + * To make use of this helper, all bridges in the chain shall report bridge
> > > + * operation flags (&drm_bridge->ops) and bridge output type
> > > + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> > > + */
> > > +
> > > +/**
> > > + * struct drm_bridge_connector - A connector backed by a chain of bridges
> > > + */
> > > +struct drm_bridge_connector {
> > > +	/**
> > > +	 * @base: The base DRM connector
> > > +	 */
> > > +	struct drm_connector base;
> > > +	/**
> > > +	 * @bridge:
> > > +	 *
> > > +	 * The first bridge in the chain (connected to the output of the CRTC).
> > > +	 */
> > > +	struct drm_bridge *bridge;
> > > +	/**
> > > +	 * @bridge_edid:
> > > +	 *
> > > +	 * The last bridge in the chain (closest to the connector) that provides
> > > +	 * EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
> > > +	 */
> > > +	struct drm_bridge *bridge_edid;
> > > +	/**
> > > +	 * @bridge_hpd:
> > > +	 *
> > > +	 * The last bridge in the chain (closest to the connector) that provides
> > > +	 * hot-plug detection notification, if any (see &DRM_BRIDGE_OP_HPD).
> > > +	 */
> > > +	struct drm_bridge *bridge_hpd;
> > > +	/**
> > > +	 * @bridge_detect:
> > > +	 *
> > > +	 * The last bridge in the chain (closest to the connector) that provides
> > > +	 * connector detection, if any (see &DRM_BRIDGE_OP_DETECT).
> > > +	 */
> > > +	struct drm_bridge *bridge_detect;
> > > +	/**
> > > +	 * @bridge_detect:
> > > +	 *
> > > +	 * The last bridge in the chain (closest to the connector) that provides
> > > +	 * connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
> > > +	 */
> > > +	struct drm_bridge *bridge_modes;
> > > +	/**
> > > +	 * @hdmi_mode: Valid for HDMI connectors only.
> > > +	 */
> > > +	bool hdmi_mode;
> > 
> > This should probably be in drm_display_info somewhere, not here?
> 
> Yes, and it's unused in this patch, I've just noticed that. Field
> dropped.
> 
> > Wrt the overall design ... why do we need a new struct? If we assume (at
> > least for now) that we only allow one encoder for such a bridge chain
> > (currently still true), then you can always go from the connector to it's
> > only possibel encoder. And from there to the bridge chain.
> > 
> > Furthermore all the special bridge pointers here can just be found at
> > runtime by walking the bridge links. And none of these paths are hot
> > enough to make this a problem.
> > 
> > With that your drm_bridge_connector here would become just a pile of
> > functions as default implementations for connectors. Making it more
> > modular and more helper-y and easier to transition gradually.
> 
> The main purpose of this structure is indeed to cache the bridge
> pointers, which could be recalculated at runtime. I agree there's no
> real hot path, but caching them still feels nice :-)

We've treated anything in atomic as not a hot-path, preferring clean code
over fast. Except if someone can proof otherwise, which very few ever
bother to even try :-)

> How do you go from the connector to its encoder though ? The
> drm_connector encoder field is valid for non-atomic drivers only, and
> the encoder_ids field is marked as not to be accessed directly. Should I
> use drm_connector_for_each_possible_encoder() and pick the first encoder
> ?

pick_single_encoder_for_connector. Was once exported even ... I think for
bridge we can just hard-code the assumption that there's only one
connector for a bridge chain.

For more fancy topologies this ofc all breaks down, but maybe we can
postpone solving that problem ...

> 
> > > +};
> > > +
> > > +#define to_drm_bridge_connector(x) \
> > > +	container_of(x, struct drm_bridge_connector, base)
> > > +
> > > +/* -----------------------------------------------------------------------------
> > > + * Bridge Connector Hot-Plug Handling
> > > + */
> > > +
> > > +static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
> > > +					    enum drm_connector_status status)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +	struct drm_bridge *bridge;
> > > +
> > > +	if (status != connector_status_disconnected)
> > > +		return;
> > > +
> > > +	/*
> > > +	 * Notify all bridges in the pipeline of disconnection. This is required
> > > +	 * to let the HDMI encoders reset their internal state related to
> > > +	 * connection status, such as the CEC address.
> > > +	 */
> > > +	for (bridge = bridge_connector->bridge; bridge; bridge = bridge->next) {
> > > +		if (bridge->funcs->lost_hotplug)
> > > +			bridge->funcs->lost_hotplug(bridge);
> > 
> > So looking at this you pretty much implement my idea for hdp handling
> > already, except you're calling it ->lost_hotplug and not ->notify_hpd.
> 
> Renamed already in my private tree, will be in v2 :-)
> 
> > Plus you require some callback registration. Essentially my design (that I
> > explained in my reply to your bridge patch) would just make
> > drm_bridge_connector_hpd_cb() the one and only hpd_cb, and punt all hpd
> > handling to bridge drivers like you do here.
> > 
> > Ofc that leaves us with "who's calling drm_kms_helper_hotplug_event()",
> > and that's what the new hdp_notify on the encoder and the global
> > drm_mode_config_helper_funcs would be for.
> 
> Let's discuss that in the replies to the other patch, as the discussion
> is already longer there. I'm not opposed to your proposal, but I've
> asked a few questions to clarify it.
> 
> > > +	}
> > > +}
> > > +
> > > +static void drm_bridge_connector_hpd_cb(void *cb_data,
> > > +					enum drm_connector_status status)
> > > +{
> > > +	struct drm_bridge_connector *drm_bridge_connector = cb_data;
> > > +	struct drm_connector *connector = &drm_bridge_connector->base;
> > > +	struct drm_device *dev = connector->dev;
> > > +	enum drm_connector_status old_status;
> > > +
> > > +	mutex_lock(&dev->mode_config.mutex);
> > > +	old_status = connector->status;
> > > +	connector->status = status;
> > > +	mutex_unlock(&dev->mode_config.mutex);
> > > +
> > > +	if (old_status == status)
> > > +		return;
> > > +
> > > +	drm_bridge_connector_hpd_notify(connector, status);
> > > +
> > > +	drm_kms_helper_hotplug_event(dev);
> > > +}
> > > +
> > > +/**
> > > + * drm_bridge_connector_enable_hpd - Enable hot-plug detection for the connector
> > > + * @connector: The DRM bridge connector
> > > + *
> > > + * This function enables hot-plug detection for the given bridge connector.
> > > + * This is typically used by display drivers in their resume handler.
> > > + */
> > > +void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> > > +
> > > +	if (hpd)
> > > +		drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
> > > +				      bridge_connector);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_bridge_connector_enable_hpd);
> > > +
> > > +/**
> > > + * drm_bridge_connector_disable_hpd - Disable hot-plug detection for the
> > > + * connector
> > > + * @connector: The DRM bridge connector
> > > + *
> > > + * This function disables hot-plug detection for the given bridge connector.
> > > + * This is typically used by display drivers in their suspend handler.
> > > + */
> > > +void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> > > +
> > > +	if (hpd)
> > > +		drm_bridge_hpd_disable(hpd);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_bridge_connector_disable_hpd);
> > > +
> > > +/* -----------------------------------------------------------------------------
> > > + * Bridge Connector Functions
> > > + */
> > > +
> > > +static enum drm_connector_status
> > > +drm_bridge_connector_detect(struct drm_connector *connector, bool force)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +	struct drm_bridge *detect = bridge_connector->bridge_detect;
> > > +	enum drm_connector_status status;
> > > +
> > > +	if (detect) {
> > > +		status = detect->funcs->detect(detect);
> > > +
> > > +		drm_bridge_connector_hpd_notify(connector, status);
> > > +	} else {
> > > +		switch (connector->connector_type) {
> > > +		case DRM_MODE_CONNECTOR_DPI:
> > > +		case DRM_MODE_CONNECTOR_LVDS:
> > > +		case DRM_MODE_CONNECTOR_DSI:
> > > +			status = connector_status_connected;
> > > +			break;
> > > +		default:
> > > +			status = connector_status_unknown;
> > > +			break;
> > > +		}
> > > +	}
> > > +
> > > +	return status;
> > > +}
> > > +
> > > +static void drm_bridge_connector_destroy(struct drm_connector *connector)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +
> > > +	if (bridge_connector->bridge_hpd) {
> > > +		struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> > > +
> > > +		drm_bridge_hpd_disable(hpd);
> > > +	}
> > > +
> > > +	drm_connector_unregister(connector);
> > > +	drm_connector_cleanup(connector);
> > > +
> > > +	kfree(bridge_connector);
> > > +}
> > > +
> > > +static const struct drm_connector_funcs drm_bridge_connector_funcs = {
> > > +	.reset = drm_atomic_helper_connector_reset,
> > > +	.detect = drm_bridge_connector_detect,
> > 
> > For that smooht helper library feeling I think we should export _detect
> > and get_modes at least.
> 
> Already, even without a user ? Or should we wait until someone needs
> them ?

I figured mostly to have an excuse for the kerneldoc ...

Cheers, Daniel

> > > +	.fill_modes = drm_helper_probe_single_connector_modes,
> > > +	.destroy = drm_bridge_connector_destroy,
> > > +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> > > +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> > > +};
> > > +
> > > +/* -----------------------------------------------------------------------------
> > > + * Bridge Connector Helper Functions
> > > + */
> > > +
> > > +#define MAX_EDID  512
> > > +
> > > +static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> > > +					       struct drm_bridge *bridge)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +	enum drm_connector_status status;
> > > +	struct edid *edid;
> > > +	int n;
> > > +
> > > +	status = drm_bridge_connector_detect(connector, false);
> > > +	if (status != connector_status_connected)
> > > +		goto no_edid;
> > > +
> > > +	edid = bridge->funcs->get_edid(bridge, connector);
> > > +	if (!edid || !drm_edid_is_valid(edid)) {
> > > +		kfree(edid);
> > > +		goto no_edid;
> > > +	}
> > > +
> > > +	drm_connector_update_edid_property(connector, edid);
> > > +	n = drm_add_edid_modes(connector, edid);
> > > +
> > > +	bridge_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
> > > +
> > > +	kfree(edid);
> > > +	return n;
> > > +
> > > +no_edid:
> > > +	drm_connector_update_edid_property(connector, NULL);
> > > +	return 0;
> > > +}
> > > +
> > > +static int drm_bridge_connector_get_modes(struct drm_connector *connector)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector =
> > > +		to_drm_bridge_connector(connector);
> > > +	struct drm_bridge *bridge;
> > > +
> > > +	/*
> > > +	 * If display exposes EDID, then we parse that in the normal way to
> > > +	 * build table of supported modes.
> > > +	 */
> > > +	bridge = bridge_connector->bridge_edid;
> > > +	if (bridge)
> > > +		return drm_bridge_connector_get_modes_edid(connector, bridge);
> > > +
> > > +	/*
> > > +	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
> > > +	 * resolution panel or an analog TV output), query it.
> > > +	 */
> > > +	bridge = bridge_connector->bridge_modes;
> > > +	if (bridge)
> > > +		return bridge->funcs->get_modes(bridge, connector);
> > > +
> > > +	/*
> > > +	 * We can't retrieve modes, which can happen for instance for a DVI or
> > > +	 * VGA output with the DDC bus unconnected. The KMS core will add the
> > > +	 * default modes.
> > > +	 */
> > > +	return 0;
> > > +}
> > > +
> > > +static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
> > > +	.get_modes = drm_bridge_connector_get_modes,
> > > +	/* No need for .mode_valid(), the bridges are checked by the core. */
> > > +};
> > > +
> > > +/* -----------------------------------------------------------------------------
> > > + * Bridge Connector Initialisation
> > > + */
> > > +
> > > +/**
> > > + * drm_bridge_connector_init - Initialise a connector for a chain of bridges
> > > + * @drm: the DRM device
> > > + * @bridge: the bridge closest to the CRTC output
> > > + *
> > > + * Allocate, initialise and register a &drm_bridge_connector with the @drm
> > > + * device. The connector is associated with a chain of bridges that starts at
> > > + * the CRTC output with @bridge. All bridges in the chain shall report bridge
> > > + * operation flags (&drm_bridge->ops) and bridge output type
> > > + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> > > + *
> > > + * Returns a pointer to the new connector on success, or a negative error
> > > + * pointer otherwise.
> > > + */
> > > +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> > > +						struct drm_bridge *bridge)
> > > +{
> > > +	struct drm_bridge_connector *bridge_connector;
> > > +	struct drm_connector *connector;
> > > +	int connector_type;
> > > +
> > > +	bridge_connector = kzalloc(sizeof(*bridge_connector), GFP_KERNEL);
> > > +	if (!bridge_connector)
> > > +		return ERR_PTR(-ENOMEM);
> > > +
> > > +	bridge_connector->bridge = bridge;
> > > +
> > > +	/*
> > > +	 * Initialise connector status handling. First locate the furthest
> > > +	 * bridges in the pipeline that support HPD and output detection. Then
> > > +	 * initialise the connector polling mode, using HPD if available and
> > > +	 * falling back to polling if supported. If neither HPD nor output
> > > +	 * detection are available, we don't support hotplug detection at all.
> > > +	 */
> > > +	connector_type = DRM_MODE_CONNECTOR_Unknown;
> > > +	for ( ; bridge; bridge = bridge->next) {
> > > +		if (bridge->ops & DRM_BRIDGE_OP_EDID)
> > > +			bridge_connector->bridge_edid = bridge;
> > > +		if (bridge->ops & DRM_BRIDGE_OP_HPD)
> > > +			bridge_connector->bridge_hpd = bridge;
> > > +		if (bridge->ops & DRM_BRIDGE_OP_DETECT)
> > > +			bridge_connector->bridge_detect = bridge;
> > > +		if (bridge->ops & DRM_BRIDGE_OP_MODES)
> > > +			bridge_connector->bridge_modes = bridge;
> > > +
> > > +		if (!bridge->next)
> > > +			connector_type = bridge->type;
> > > +	}
> > > +
> > > +	if (connector_type == DRM_MODE_CONNECTOR_Unknown) {
> > > +		kfree(bridge_connector);
> > > +		return ERR_PTR(-EINVAL);
> > > +	}
> > > +
> > > +	/*
> > > +	 * TODO: Handle interlace_allowed, doublescan_allowed, stereo_allowed
> > > +	 * and ycbcr_420_allowed.
> > > +	 */
> > > +	connector = &bridge_connector->base;
> > > +	drm_connector_init(drm, connector, &drm_bridge_connector_funcs,
> > > +			   connector_type);
> > > +	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
> > > +
> > > +	if (bridge_connector->bridge_hpd)
> > > +		connector->polled = DRM_CONNECTOR_POLL_HPD;
> > > +	else if (bridge_connector->bridge_detect)
> > > +		connector->polled = DRM_CONNECTOR_POLL_CONNECT
> > > +				  | DRM_CONNECTOR_POLL_DISCONNECT;
> > > +
> > > +	return connector;
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_bridge_connector_init);
> > > diff --git a/include/drm/drm_bridge_connector.h b/include/drm/drm_bridge_connector.h
> > > new file mode 100644
> > > index 000000000000..ec33b44954b8
> > > --- /dev/null
> > > +++ b/include/drm/drm_bridge_connector.h
> > > @@ -0,0 +1,18 @@
> > > +/* SPDX-License-Identifier: GPL-2.0+ */
> > > +/*
> > > + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > + */
> > > +
> > > +#ifndef __DRM_BRIDGE_CONNECTOR_H__
> > > +#define __DRM_BRIDGE_CONNECTOR_H__
> > > +
> > > +struct drm_bridge;
> > > +struct drm_connector;
> > > +struct drm_device;
> > > +
> > > +void drm_bridge_connector_enable_hpd(struct drm_connector *connector);
> > > +void drm_bridge_connector_disable_hpd(struct drm_connector *connector);
> > > +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> > > +						struct drm_bridge *bridge);
> > > +
> > > +#endif /* __DRM_BRIDGE_CONNECTOR_H__ */
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 13:30               ` Laurent Pinchart
@ 2019-08-14 17:02                 ` Daniel Vetter
  2019-08-16 23:30                   ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-14 17:02 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Wed, Aug 14, 2019 at 04:30:57PM +0300, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Wed, Aug 14, 2019 at 03:03:29PM +0200, Daniel Vetter wrote:
> > On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> > > On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> > >> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> > >>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > >>>> Hi Laurent,
> > >>>> 
> > >>>> I like the approach, current practice when almost every bridge should
> > >>>> optionally implement connector, or alternatively downstream bridge or
> > >>>> panel is very painful.
> > >>> 
> > >>> Yeah I think this looks mostly reasonable. Some api design comments on top
> > >>> of Andrzej', with the fair warning that I didn't bother to read up on how
> > >>> it's all used in the end. I probably should go and do that, at least to
> > >>> get a feeling for what your hpd_cb usually does.
> > >>> 
> > >>>> More comments inlined.
> > >>>> 
> > >>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>>>> To support implementation of DRM connectors on top of DRM bridges
> > >>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> > >>>>> data:
> > >>>>>
> > >>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> > >>>>>   retrieval operations
> > >>>>> - Bitmask of supported operations
> > >>>> 
> > >>>> 
> > >>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> > >>>> operation's callback?
> > >>> 
> > >>> Yeah also not a huge fan of these bitmasks. Smells like
> > >>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > >>> add, generally good excuse to not have to think through the design between
> > >>> different parts of drivers - "just" add another flag.
> > >> 
> > >> The reason is that a bridge may support an operation (as in implemented
> > >> in the bridge hardware), but that operation may not be supported on a
> > >> particular board. For instance an HDMI encoder may support reading EDID
> > >> when the DDC lines are connected to the encoder, but a board may connect
> > >> the DDC lines to an I2C port of the SoC. We thus need to decouple
> > >> if a particular instance of the device supports the operation (exposed
> > >> by the ops flags) from the function pointers.
> > >> 
> > >> We could of course allocate the drm_bridge_funcs structure dynamically
> > >> for each bridge instance, and fill it with function pointers manually,
> > >> leaving the unused ops always NULL, but that would require making the
> > >> structure writable, which is considered a security issue. That's why I
> > >> decided to keep the drm_bridge_funcs structure as a global static const
> > >> structure, and add an ops bitmask.
> > >> 
> > >>>>> - Bridge output type
> > >>>>>
> > >>>>> Add and document these.
> > >>>>>
> > >>>>> Three new bridge helper functions are also added to handle hot plug
> > >>>>> notification in a way that is as transparent as possible for the
> > >>>>> bridges.
> > >>>> 
> > >>>> Documentation of new opses does not explain how it should cooperate with
> > >>>> bridge chaining, I suppose they should be chained explicitly, am I
> > >>>> right? More comments about it later.
> > >> 
> > >> No, the whole point is that they should not be chained at all. A bridge
> > >> does not have to propagate, for instance, .get_edid() to the next
> > >> bridge. That's one of the core design principles in this series, I want
> > >> to keep the bridges as simple as possible, and move the complexity of
> > >> the boilerplate code that is currently copied all around to helpers. See
> > >> patch "drm: Add helper to create a connector for a chain of bridges" for
> > >> more information about how this is used, with a helper that delegates
> > >> the connector operations to the correct bridge in the chain based on the
> > >> ops reported by each bridge.
> > >> 
> > >>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>>>> ---
> > >>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> > >>>>>
> > >>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > >>>>> index 519577f363e3..3c2a255df7af 100644
> > >>>>> --- a/drivers/gpu/drm/drm_bridge.c
> > >>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> > >>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >>>>>   */
> > >>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> > >>>>>  {
> > >>>>> +	mutex_init(&bridge->hpd_mutex);
> > >>>>> +
> > >>>>>  	mutex_lock(&bridge_lock);
> > >>>>>  	list_add_tail(&bridge->list, &bridge_list);
> > >>>>>  	mutex_unlock(&bridge_lock);
> > >>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >>>>>  	mutex_lock(&bridge_lock);
> > >>>>>  	list_del_init(&bridge->list);
> > >>>>>  	mutex_unlock(&bridge_lock);
> > >>>>> +
> > >>>>> +	mutex_destroy(&bridge->hpd_mutex);
> > >>>>>  }
> > >>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> > >>>>>  
> > >>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>  }
> > >>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >>>>>  
> > >>>>> +/**
> > >>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > >>>>> + * @bridge: bridge control structure
> > >>>>> + * @cb: hot-plug detection callback
> > >>>>> + * @data: data to be passed to the hot-plug detection callback
> > >>>>> + *
> > >>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > >>>>> + * hot plug notification callback. From now on the @cb will be called with
> > >>>>> + * @data when an output status change is detected by the bridge, until hot plug
> > >>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> > >>>>> + *
> > >>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>> + *
> > >>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> > >>>>> + * error to call this function when hot plug detection is already enabled for
> > >>>>> + * the bridge.
> > >>>>> + */
> > >>>> 
> > >>>> To simplify architecture maybe would be better to enable hpd just on
> > >>>> bridge attach:
> > >>>> 
> > >>>> bridge->hpd_cb = cb;
> > >>>> 
> > >>>> bridge->hpd_data = data;
> > >>>> 
> > >>>> ret = drm_bridge_attach(...);
> > >>> 
> > >>> Yeah I like this more. The other problem here is, what if you need more
> > >>> than 1 callback registers on the same bridge hdp signal?
> > >> 
> > >> That's why I decided to hide hide HPD through helpers,
> > >> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> > >> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> > >> current implementation is limited to a single listener, only the helpers
> > >> would need to be changed to extend that to multiple listeners.
> > >> 
> > >> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> > >> bridge to disable HPD detection when not used. Doing so keeps the bridge
> > >> simple, it only needs to care about reporting HPD events when they're
> > >> enabled, without caring who (if anyone) is listening, and gets clear
> > >> instructions on whether to enable or disable the HPD hardware (in case
> > >> it can be disabled).
> > >> 
> > >>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> > >>>> without big sacrifices.
> > >>>> 
> > >>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > >>>> notifies about sink status change, how it translates to this cb?
> > >> 
> > >> This is something this series doesn't implement. I don't think it would
> > >> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> > >> here. If you can elaborate on what would be needed, I can implement
> > >> that.
> > >> 
> > >>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>> +			   void (*cb)(void *data,
> > >>>>> +				      enum drm_connector_status status),
> > >>>>> +			   void *data)
> > >>>>> +{
> > >>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> > >>>>> +		return;
> > >>>>> +
> > >>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>> +
> > >>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > >>>>> +		goto unlock;
> > >>>>> +
> > >>>>> +	bridge->hpd_cb = cb;
> > >>>>> +	bridge->hpd_data = data;
> > >>>>> +
> > >>>>> +	bridge->funcs->hpd_enable(bridge);
> > >>>>> +
> > >>>>> +unlock:
> > >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>> +}
> > >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > >>>>> +
> > >>>>> +/**
> > >>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > >>>>> + * @bridge: bridge control structure
> > >>>>> + *
> > >>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > >>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > >>>>> + * function returns the callback will not be called by the bridge when an
> > >>>>> + * output status change occurs.
> > >>>>> + *
> > >>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>> + */
> > >>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > >>>>> +{
> > >>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> > >>>>> +		return;
> > >>>>> +
> > >>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>> +	bridge->funcs->hpd_disable(bridge);
> > >>>>> +
> > >>>>> +	bridge->hpd_cb = NULL;
> > >>>>> +	bridge->hpd_data = NULL;
> > >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>> +}
> > >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > >>>>> +
> > >>>>> +/**
> > >>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> > >>>>> + * @bridge: bridge control structure
> > >>>>> + * @status: output connection status
> > >>>>> + *
> > >>>>> + * Bridge drivers shall call this function to report hot plug events when they
> > >>>>> + * detect a change in the output status, when hot plug detection has been
> > >>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > >>>>> + *
> > >>>>> + * This function shall be called in a context that can sleep.
> > >>>>> + */
> > >>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>> +			   enum drm_connector_status status)
> > >>>>> +{
> > >>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>> +	if (bridge->hpd_cb)
> > >>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > >>> 
> > >>> So this isn't quite what I had in mind. Instead something like this:
> > >>> 
> > >>> 	/* iterates over all bridges in the chain containing @bridge */
> > >>> 	for_each_bridge(tmp_bridge, bridge) {
> > >>> 		if (tmp_bridge == bridge)
> > >>> 			continue;
> > >>> 		if (bridge->hpd_notify);
> > >>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > >>> 	}
> > >>> 
> > >>> 	encoder = encoder_for_bridge(bridge);
> > >>> 	if (encoder->helper_private->bridge_hpd_notify)
> > >>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > >>> 
> > >>> 	dev = bridge->dev
> > >>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > >>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > >>> 
> > >>> No register callback needed, no locking needed, everyone gets exactly the
> > >>> hpd they want/need.
> > >> 
> > >> I'll reply to this further down the mail thread, to address additional
> > >> comments.
> > >> 
> > >>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>> +}
> > >>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > >>>>> +
> > >>>>>  #ifdef CONFIG_OF
> > >>>>>  /**
> > >>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > >>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > >>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> > >>>>> --- a/include/drm/drm_bridge.h
> > >>>>> +++ b/include/drm/drm_bridge.h
> > >>>>> @@ -23,8 +23,9 @@
> > >>>>>  #ifndef __DRM_BRIDGE_H__
> > >>>>>  #define __DRM_BRIDGE_H__
> > >>>>>  
> > >>>>> -#include <linux/list.h>
> > >>>>>  #include <linux/ctype.h>
> > >>>>> +#include <linux/list.h>
> > >>>>> +#include <linux/mutex.h>
> > >>>>>  #include <drm/drm_mode_object.h>
> > >>>>>  #include <drm/drm_modes.h>
> > >>>>>  
> > >>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >>>>>  	 */
> > >>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >>>>>  				    struct drm_atomic_state *state);
> > >>>>> +
> > >>>>> +	/**
> > >>>>> +	 * @detect:
> > >>>>> +	 *
> > >>>>> +	 * Check if anything is attached to the bridge output.
> > >>>>> +	 *
> > >>>>> +	 * This callback is optional, if not implemented the bridge will be
> > >>>>> +	 * considered as always having a component attached to its output.
> > >>>>> +	 * Bridges that implement this callback shall set the
> > >>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > >>>>> +	 *
> > >>>>> +	 * RETURNS:
> > >>>>> +	 *
> > >>>>> +	 * drm_connector_status indicating the bridge output status.
> > >>>>> +	 */
> > >>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > >>>>> +
> > >>>>> +	/**
> > >>>>> +	 * @get_modes:
> > >>>>> +	 *
> > >>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> > >>>>> +	 * with drm_mode_probed_add().
> > >>>>> +	 *
> > >>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> > >>>>> +	 * displays such as many fixed panels. Bridges that support reading
> > >>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> > >>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> > >>>>> +	 *
> > >>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > >>>>> +	 *
> > >>>>> +	 * RETURNS:
> > >>>>> +	 *
> > >>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> > >>>>> +	 */
> > >>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> > >>>>> +			 struct drm_connector *connector);
> > >>>>> +
> > >>>>> +	/**
> > >>>>> +	 * @get_edid:
> > >>>>> +	 *
> > >>>>> +	 * Read and parse the EDID data of the connected display.
> > >>>>> +	 *
> > >>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> > >>>>> +	 * information for a display connected to the bridge output. Bridges
> > >>>>> +	 * that support readind EDID shall implement this callback and leave
> > >>>>> +	 * the @get_modes callback unimplemented.
> > >>>>> +	 *
> > >>>>> +	 * The caller of this operation shall first verify the output
> > >>>>> +	 * connection status and refrain from reading EDID from a disconnected
> > >>>>> +	 * output.
> > >>>>> +	 *
> > >>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > >>>>> +	 *
> > >>>>> +	 * RETURNS:
> > >>>>> +	 *
> > >>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > >>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> > >>>>> +	 * the returned edid structure with kfree().
> > >>>>> +	 */
> > >>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > >>>>> +				 struct drm_connector *connector);
> > >>>> 
> > >>>> It overlaps with get_modes, I guess presence of one ops should disallow
> > >>>> presence of another one?
> > >>>> 
> > >>>> I am not really convinced we need this op at all, cannot we just assign
> > >>>> some helper function to .get_modes cb, which will do the same?
> > >>> 
> > >>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > >>> case, and require that if it has an edid it must fill out connector->info
> > >>> and connector->edid correctly.
> > >> 
> > >> I think that's doable, I'll have a look.
> > > 
> > > So I had a look, and while this is doable, it would essentially mean
> > > that all bridges that retrieve modes from EDID would have to roll out
> > > their own version of the following code:
> > > 
> > > static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> > > 					       struct drm_bridge *bridge)
> > > {
> > > 	enum drm_connector_status status;
> > > 	struct edid *edid;
> > > 	int n;
> > > 
> > > 	status = drm_bridge_connector_detect(connector, false);
> > > 	if (status != connector_status_connected)
> > > 		goto no_edid;
> > > 
> > > 	edid = bridge->funcs->get_edid(bridge, connector);
> > > 	if (!edid || !drm_edid_is_valid(edid)) {
> > > 		kfree(edid);
> > > 		goto no_edid;
> > > 	}
> > > 
> > > 	drm_connector_update_edid_property(connector, edid);
> > > 	n = drm_add_edid_modes(connector, edid);
> > > 
> > > 	kfree(edid);
> > > 	return n;
> > > 
> > > no_edid:
> > > 	drm_connector_update_edid_property(connector, NULL);
> > > 	return 0;
> > > }
> > > 
> > > Is this desired ?
> > 
> > We store the edid, and we store a lot of decoded information in
> > drm_connector->display_info. Can't they just look there? Re-fetching the
> > edid definitely sounds like the wrong thing to do.
> > 
> > We might run into some ordering issue here I guess with hotplugs and who's
> > fetching the edid and everything like that.
> 
> That's exactly what I was about to answer after reading your first
> paragraph :-) I believe caching EDID is a good idea, but my familiarity
> with hotplug-related issues is limited to a handful of systems, and I'm
> sure I'm missing some common problems. If you can tell me how you think
> this should be done, I can give it a try.

I think all you need to do is make sure that when handling a hpd, the edid
is fetched first. Before other parts of the bridge try to reconfigure
themselves ...

> > Also maybe I'm missing the point here, and thinking too much of
> > ->get_modes on the connector. But then I'm not clear on why the bridge
> > needs the connector, and why it instead can't just return the edid it can
> > read and let the caller/core figure out everything else?
> 
> That's exactly what the .get_edid() operation that you asked me to
> remove did... :-) You didn't like the fact that it duplicated the
> .get_modes() logic. Should I add it back, and clearly document
> .get_modes() as a fallback used only when the connector doesn't use EDID
> ?

I guess I'm making a bit a fool of myself here. What I meant is that if we
do want to keep ->get_edid, then why do you need to pass the connector?
Just return the edid blob, and let the caller parse it, and stuff all
relevant data into drm_connector. Just thinking along the lines of your
goal of making the bridge drivers as dumb as possible.

Same thing for ->get_modes would be neat too, but we don't have a nice
datastructure for this. We'd need to pass both a list_head and a pointer
to the drm_display_info I think.

That would decouple bridges even more from connector, which I think is
somewhere on your goal list ...
-Daniel

> 
> > >>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> > >>> in the connector is up-to-date? With your current callback design that's
> > >>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > >>> should guarantee that it'll first walk the connectors to update status and
> > >>> edid/mode list for the final drm_connector. And then instead of just
> > >>> passing the simple "status", it'll pass the connector, with everything
> > >>> correctly updated.
> > >>> 
> > >>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> > >>> edid, which is not so awesome :-)
> > >> 
> > >> With the current design there's a single listener, so it's not a big
> > >> deal :-) Furthermore, the listener is the helper that creates a
> > >> connector on top of a chain of bridges, so it's a pretty good place to
> > >> handle this. See the call to drm_kms_helper_hotplug_event() in
> > >> drm_bridge_connector_hpd_cb().
> > >> 
> > >> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> > >> big of a requirement as a prerequisite for this series (or as part of
> > >> this series). We have hardware that can report HPD with various level of
> > >> details (from "something happened on a connector" to "this particular
> > >> event happened on this particular connector"), and we channel that
> > >> through helpers such as drm_kms_helper_hotplug_event() that lose the
> > >> details and go through a heavy mechanism to refetch everything. I
> > >> understand this is needed in many cases, but I think there's room for
> > >> improvement. This series, in my opinion, doesn't go in the wrong
> > >> direction in that regard, as it eventually calls
> > >> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> > >> on top of it. I'm even willing to work on this, provided I get feedback
> > >> on what is desired.
> > >> 
> > >>>>> +	/**
> > >>>>> +	 * @lost_hotplug:
> > >>>>> +	 *
> > >>>>> +	 * Notify the bridge of display disconnection.
> > >>>>> +	 *
> > >>>>> +	 * This callback is optional, it may be implemented by bridges that
> > >>>>> +	 * need to be notified of display disconnection for internal reasons.
> > >>>>> +	 * One use case is to reset the internal state of CEC controllers for
> > >>>>> +	 * HDMI bridges.
> > >>>>> +	 */
> > >>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > >>>>> +
> > >>>>> +	/**
> > >>>>> +	 * @hpd_enable:
> > >>>>> +	 *
> > >>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> > >>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > >>>>> +	 * connection status, until hot plug detection gets disabled with
> > >>>>> +	 * @hpd_disable.
> > >>>>> +	 *
> > >>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> > >>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>> +	 */
> > >>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> > >>>>> +
> > >>>>> +	/**
> > >>>>> +	 * @hpd_disable:
> > >>>>> +	 *
> > >>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> > >>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > >>>>> +	 * connection status occurs.
> > >>>>> +	 *
> > >>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> > >>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>> +	 */
> > >>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >>>>>  };
> > >>>>>  
> > >>>>>  /**
> > >>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >>>>>  	bool dual_link;
> > >>>>>  };
> > >>>>>  
> > >>>>> +/**
> > >>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > >>>>> + */
> > >>>>> +enum drm_bridge_ops {
> > >>>>> +	/**
> > >>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > >>>>> +	 * its output. Bridges that set this flag shall implement the
> > >>>>> +	 * &drm_bridge_funcs->detect callback.
> > >>>>> +	 */
> > >>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > >>>>> +	/**
> > >>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > >>>>> +	 * connected to its output. Bridges that set this flag shall implement
> > >>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> > >>>>> +	 */
> > >>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> > >>>>> +	/**
> > >>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > >>>>> +	 * without requiring polling. Bridges that set this flag shall
> > >>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> > >>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > >>>>> +	 */
> > >>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> > >>>>> +	/**
> > >>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > >>>>> +	 * by the display at its output. This does not include readind EDID
> > >>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > >>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > >>>>> +	 */
> > >>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> > >>>>> +};
> > >>>>> +
> > >>>>>  /**
> > >>>>>   * struct drm_bridge - central DRM bridge control structure
> > >>>>>   */
> > >>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> > >>>>>  	const struct drm_bridge_funcs *funcs;
> > >>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> > >>>>>  	void *driver_private;
> > >>>>> +	/** @ops: bitmask of operations supported by the bridge */
> > >>>>> +	enum drm_bridge_ops ops;
> > >>>>> +	/**
> > >>>>> +	 * @type: Type of the connection at the bridge output
> > >>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > >>>>> +	 * identifies the type of connected display.
> > >>>>> +	 */
> > >>>>> +	int type;
> > >>>>> +	/** private: */
> > >>>>> +	/**
> > >>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > >>>>> +	 */
> > >>>>> +	struct mutex hpd_mutex;
> > >>>>> +	/**
> > >>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> > >>>>> +	 * drm_bridge_hpd_enable().
> > >>>>> +	 */
> > >>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > >>>>> +	/**
> > >>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > >>>>> +	 * @hpd_cb.
> > >>>>> +	 */
> > >>>>> +	void *hpd_data;
> > >>>>>  };
> > >>>>>  
> > >>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> > >>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>  			      struct drm_atomic_state *state);
> > >>>>>  
> > >>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>> +			   void (*cb)(void *data,
> > >>>>> +				      enum drm_connector_status status),
> > >>>>> +			   void *data);
> > >>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > >>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>> +			   enum drm_connector_status status);
> > >>>>> +
> > >>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >>>>>  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 17:02                 ` Daniel Vetter
@ 2019-08-16 23:30                   ` Laurent Pinchart
  2019-08-17  0:14                     ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-16 23:30 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Daniel,

On Wed, Aug 14, 2019 at 07:02:29PM +0200, Daniel Vetter wrote:
> On Wed, Aug 14, 2019 at 04:30:57PM +0300, Laurent Pinchart wrote:
> > On Wed, Aug 14, 2019 at 03:03:29PM +0200, Daniel Vetter wrote:
> >> On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> >>> On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> >>>> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> >>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>> Hi Laurent,
> >>>>>> 
> >>>>>> I like the approach, current practice when almost every bridge should
> >>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>> panel is very painful.
> >>>>> 
> >>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>> get a feeling for what your hpd_cb usually does.
> >>>>> 
> >>>>>> More comments inlined.
> >>>>>> 
> >>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>> data:
> >>>>>>>
> >>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>   retrieval operations
> >>>>>>> - Bitmask of supported operations
> >>>>>> 
> >>>>>> 
> >>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>> operation's callback?
> >>>>> 
> >>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>> add, generally good excuse to not have to think through the design between
> >>>>> different parts of drivers - "just" add another flag.
> >>>> 
> >>>> The reason is that a bridge may support an operation (as in implemented
> >>>> in the bridge hardware), but that operation may not be supported on a
> >>>> particular board. For instance an HDMI encoder may support reading EDID
> >>>> when the DDC lines are connected to the encoder, but a board may connect
> >>>> the DDC lines to an I2C port of the SoC. We thus need to decouple
> >>>> if a particular instance of the device supports the operation (exposed
> >>>> by the ops flags) from the function pointers.
> >>>> 
> >>>> We could of course allocate the drm_bridge_funcs structure dynamically
> >>>> for each bridge instance, and fill it with function pointers manually,
> >>>> leaving the unused ops always NULL, but that would require making the
> >>>> structure writable, which is considered a security issue. That's why I
> >>>> decided to keep the drm_bridge_funcs structure as a global static const
> >>>> structure, and add an ops bitmask.
> >>>> 
> >>>>>>> - Bridge output type
> >>>>>>>
> >>>>>>> Add and document these.
> >>>>>>>
> >>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>> bridges.
> >>>>>> 
> >>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>> right? More comments about it later.
> >>>> 
> >>>> No, the whole point is that they should not be chained at all. A bridge
> >>>> does not have to propagate, for instance, .get_edid() to the next
> >>>> bridge. That's one of the core design principles in this series, I want
> >>>> to keep the bridges as simple as possible, and move the complexity of
> >>>> the boilerplate code that is currently copied all around to helpers. See
> >>>> patch "drm: Add helper to create a connector for a chain of bridges" for
> >>>> more information about how this is used, with a helper that delegates
> >>>> the connector operations to the correct bridge in the chain based on the
> >>>> ops reported by each bridge.
> >>>> 
> >>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>> ---
> >>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>
> >>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>   */
> >>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>  {
> >>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>> +
> >>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>> +
> >>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>  }
> >>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>  
> >>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>  }
> >>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>  
> >>>>>>> +/**
> >>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>> + * @bridge: bridge control structure
> >>>>>>> + * @cb: hot-plug detection callback
> >>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>> + *
> >>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>> + *
> >>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>> + *
> >>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>> + * the bridge.
> >>>>>>> + */
> >>>>>> 
> >>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>> bridge attach:
> >>>>>> 
> >>>>>> bridge->hpd_cb = cb;
> >>>>>> 
> >>>>>> bridge->hpd_data = data;
> >>>>>> 
> >>>>>> ret = drm_bridge_attach(...);
> >>>>> 
> >>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>> than 1 callback registers on the same bridge hdp signal?
> >>>> 
> >>>> That's why I decided to hide hide HPD through helpers,
> >>>> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> >>>> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> >>>> current implementation is limited to a single listener, only the helpers
> >>>> would need to be changed to extend that to multiple listeners.
> >>>> 
> >>>> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> >>>> bridge to disable HPD detection when not used. Doing so keeps the bridge
> >>>> simple, it only needs to care about reporting HPD events when they're
> >>>> enabled, without caring who (if anyone) is listening, and gets clear
> >>>> instructions on whether to enable or disable the HPD hardware (in case
> >>>> it can be disabled).
> >>>> 
> >>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>> without big sacrifices.
> >>>>>> 
> >>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>> notifies about sink status change, how it translates to this cb?
> >>>> 
> >>>> This is something this series doesn't implement. I don't think it would
> >>>> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> >>>> here. If you can elaborate on what would be needed, I can implement
> >>>> that.
> >>>> 
> >>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>> +			   void (*cb)(void *data,
> >>>>>>> +				      enum drm_connector_status status),
> >>>>>>> +			   void *data)
> >>>>>>> +{
> >>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>> +		return;
> >>>>>>> +
> >>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>> +
> >>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>> +		goto unlock;
> >>>>>>> +
> >>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>> +	bridge->hpd_data = data;
> >>>>>>> +
> >>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>> +
> >>>>>>> +unlock:
> >>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>> +
> >>>>>>> +/**
> >>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>> + * @bridge: bridge control structure
> >>>>>>> + *
> >>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>> + * output status change occurs.
> >>>>>>> + *
> >>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>> + */
> >>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>> +{
> >>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>> +		return;
> >>>>>>> +
> >>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>> +
> >>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>> +
> >>>>>>> +/**
> >>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>> + * @bridge: bridge control structure
> >>>>>>> + * @status: output connection status
> >>>>>>> + *
> >>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>> + *
> >>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>> + */
> >>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>> +			   enum drm_connector_status status)
> >>>>>>> +{
> >>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>> +	if (bridge->hpd_cb)
> >>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>> 
> >>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>> 
> >>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>> 		if (tmp_bridge == bridge)
> >>>>> 			continue;
> >>>>> 		if (bridge->hpd_notify);
> >>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>> 	}
> >>>>> 
> >>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>> 
> >>>>> 	dev = bridge->dev
> >>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>> 
> >>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>> hpd they want/need.
> >>>> 
> >>>> I'll reply to this further down the mail thread, to address additional
> >>>> comments.
> >>>> 
> >>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>> +
> >>>>>>>  #ifdef CONFIG_OF
> >>>>>>>  /**
> >>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>  
> >>>>>>> -#include <linux/list.h>
> >>>>>>>  #include <linux/ctype.h>
> >>>>>>> +#include <linux/list.h>
> >>>>>>> +#include <linux/mutex.h>
> >>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>  
> >>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>  	 */
> >>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @detect:
> >>>>>>> +	 *
> >>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>> +	 *
> >>>>>>> +	 * RETURNS:
> >>>>>>> +	 *
> >>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>> +	 */
> >>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @get_modes:
> >>>>>>> +	 *
> >>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>> +	 *
> >>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>> +	 *
> >>>>>>> +	 * RETURNS:
> >>>>>>> +	 *
> >>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>> +	 */
> >>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>> +			 struct drm_connector *connector);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @get_edid:
> >>>>>>> +	 *
> >>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>> +	 *
> >>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>> +	 *
> >>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>> +	 * output.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>> +	 *
> >>>>>>> +	 * RETURNS:
> >>>>>>> +	 *
> >>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>> +	 */
> >>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>> +				 struct drm_connector *connector);
> >>>>>> 
> >>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>> presence of another one?
> >>>>>> 
> >>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>> 
> >>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>> and connector->edid correctly.
> >>>> 
> >>>> I think that's doable, I'll have a look.
> >>> 
> >>> So I had a look, and while this is doable, it would essentially mean
> >>> that all bridges that retrieve modes from EDID would have to roll out
> >>> their own version of the following code:
> >>> 
> >>> static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> >>> 					       struct drm_bridge *bridge)
> >>> {
> >>> 	enum drm_connector_status status;
> >>> 	struct edid *edid;
> >>> 	int n;
> >>> 
> >>> 	status = drm_bridge_connector_detect(connector, false);
> >>> 	if (status != connector_status_connected)
> >>> 		goto no_edid;
> >>> 
> >>> 	edid = bridge->funcs->get_edid(bridge, connector);
> >>> 	if (!edid || !drm_edid_is_valid(edid)) {
> >>> 		kfree(edid);
> >>> 		goto no_edid;
> >>> 	}
> >>> 
> >>> 	drm_connector_update_edid_property(connector, edid);
> >>> 	n = drm_add_edid_modes(connector, edid);
> >>> 
> >>> 	kfree(edid);
> >>> 	return n;
> >>> 
> >>> no_edid:
> >>> 	drm_connector_update_edid_property(connector, NULL);
> >>> 	return 0;
> >>> }
> >>> 
> >>> Is this desired ?
> >> 
> >> We store the edid, and we store a lot of decoded information in
> >> drm_connector->display_info. Can't they just look there? Re-fetching the
> >> edid definitely sounds like the wrong thing to do.
> >> 
> >> We might run into some ordering issue here I guess with hotplugs and who's
> >> fetching the edid and everything like that.
> > 
> > That's exactly what I was about to answer after reading your first
> > paragraph :-) I believe caching EDID is a good idea, but my familiarity
> > with hotplug-related issues is limited to a handful of systems, and I'm
> > sure I'm missing some common problems. If you can tell me how you think
> > this should be done, I can give it a try.
> 
> I think all you need to do is make sure that when handling a hpd, the edid
> is fetched first. Before other parts of the bridge try to reconfigure
> themselves ...
> 
> >> Also maybe I'm missing the point here, and thinking too much of
> >> ->get_modes on the connector. But then I'm not clear on why the bridge
> >> needs the connector, and why it instead can't just return the edid it can
> >> read and let the caller/core figure out everything else?
> > 
> > That's exactly what the .get_edid() operation that you asked me to
> > remove did... :-) You didn't like the fact that it duplicated the
> > .get_modes() logic. Should I add it back, and clearly document
> > .get_modes() as a fallback used only when the connector doesn't use EDID
> > ?
> 
> I guess I'm making a bit a fool of myself here. What I meant is that if we
> do want to keep ->get_edid, then why do you need to pass the connector?
> Just return the edid blob, and let the caller parse it, and stuff all
> relevant data into drm_connector. Just thinking along the lines of your
> goal of making the bridge drivers as dumb as possible.
> 
> Same thing for ->get_modes would be neat too, but we don't have a nice
> datastructure for this. We'd need to pass both a list_head and a pointer
> to the drm_display_info I think.
> 
> That would decouple bridges even more from connector, which I think is
> somewhere on your goal list ...

So I had a look at that. We would need to remove the connector argument
from drm_do_get_edid(). The connector currently stores a few fields
related to EDID parsing:

        /**
         * @null_edid_counter: track sinks that give us all zeros for the EDID.
         * Needed to workaround some HW bugs where we get all 0s
         */
        int null_edid_counter;

        /** @bad_edid_counter: track sinks that give us an EDID with invalid checksum */
        unsigned bad_edid_counter;

        /**
         * @edid_corrupt: Indicates whether the last read EDID was corrupt. Used
         * in Displayport compliance testing - Displayport Link CTS Core 1.2
         * rev1.1 4.2.2.6
         */
        bool edid_corrupt;

We would need to decouple that from drm_connector. One option would be
to create a new drm_edid structure that would store those three fields,
as well as a struct edid, and return it from drm_do_get_edid() instead
of the raw struct edid. Would you prefer a different solution ? Do you
think that's a prerequisite to for this patch ?

> >>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>> in the connector is up-to-date? With your current callback design that's
> >>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>> correctly updated.
> >>>>> 
> >>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>> edid, which is not so awesome :-)
> >>>> 
> >>>> With the current design there's a single listener, so it's not a big
> >>>> deal :-) Furthermore, the listener is the helper that creates a
> >>>> connector on top of a chain of bridges, so it's a pretty good place to
> >>>> handle this. See the call to drm_kms_helper_hotplug_event() in
> >>>> drm_bridge_connector_hpd_cb().
> >>>> 
> >>>> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> >>>> big of a requirement as a prerequisite for this series (or as part of
> >>>> this series). We have hardware that can report HPD with various level of
> >>>> details (from "something happened on a connector" to "this particular
> >>>> event happened on this particular connector"), and we channel that
> >>>> through helpers such as drm_kms_helper_hotplug_event() that lose the
> >>>> details and go through a heavy mechanism to refetch everything. I
> >>>> understand this is needed in many cases, but I think there's room for
> >>>> improvement. This series, in my opinion, doesn't go in the wrong
> >>>> direction in that regard, as it eventually calls
> >>>> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> >>>> on top of it. I'm even willing to work on this, provided I get feedback
> >>>> on what is desired.
> >>>> 
> >>>>>>> +	/**
> >>>>>>> +	 * @lost_hotplug:
> >>>>>>> +	 *
> >>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>> +	 * HDMI bridges.
> >>>>>>> +	 */
> >>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_enable:
> >>>>>>> +	 *
> >>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>> +	 * @hpd_disable.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>> +	 */
> >>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>> +
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_disable:
> >>>>>>> +	 *
> >>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>> +	 * connection status occurs.
> >>>>>>> +	 *
> >>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>> +	 */
> >>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>  };
> >>>>>>>  
> >>>>>>>  /**
> >>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>  	bool dual_link;
> >>>>>>>  };
> >>>>>>>  
> >>>>>>> +/**
> >>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>> + */
> >>>>>>> +enum drm_bridge_ops {
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>> +	/**
> >>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>> +	 */
> >>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>> +};
> >>>>>>> +
> >>>>>>>  /**
> >>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>   */
> >>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>  	void *driver_private;
> >>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>> +	/**
> >>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>> +	 * identifies the type of connected display.
> >>>>>>> +	 */
> >>>>>>> +	int type;
> >>>>>>> +	/** private: */
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>> +	 */
> >>>>>>> +	struct mutex hpd_mutex;
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>> +	 */
> >>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>> +	/**
> >>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>> +	 * @hpd_cb.
> >>>>>>> +	 */
> >>>>>>> +	void *hpd_data;
> >>>>>>>  };
> >>>>>>>  
> >>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>  
> >>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>> +			   void (*cb)(void *data,
> >>>>>>> +				      enum drm_connector_status status),
> >>>>>>> +			   void *data);
> >>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>> +			   enum drm_connector_status status);
> >>>>>>> +
> >>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-16 23:30                   ` Laurent Pinchart
@ 2019-08-17  0:14                     ` Laurent Pinchart
  2019-08-19  2:37                       ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-17  0:14 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Daniel,

On Sat, Aug 17, 2019 at 02:30:08AM +0300, Laurent Pinchart wrote:
> On Wed, Aug 14, 2019 at 07:02:29PM +0200, Daniel Vetter wrote:
> > On Wed, Aug 14, 2019 at 04:30:57PM +0300, Laurent Pinchart wrote:
> >> On Wed, Aug 14, 2019 at 03:03:29PM +0200, Daniel Vetter wrote:
> >>> On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> >>>> On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> >>>>> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> >>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>> Hi Laurent,
> >>>>>>> 
> >>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>> panel is very painful.
> >>>>>> 
> >>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>> 
> >>>>>>> More comments inlined.
> >>>>>>> 
> >>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>> data:
> >>>>>>>>
> >>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>   retrieval operations
> >>>>>>>> - Bitmask of supported operations
> >>>>>>> 
> >>>>>>> 
> >>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>> operation's callback?
> >>>>>> 
> >>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>> add, generally good excuse to not have to think through the design between
> >>>>>> different parts of drivers - "just" add another flag.
> >>>>> 
> >>>>> The reason is that a bridge may support an operation (as in implemented
> >>>>> in the bridge hardware), but that operation may not be supported on a
> >>>>> particular board. For instance an HDMI encoder may support reading EDID
> >>>>> when the DDC lines are connected to the encoder, but a board may connect
> >>>>> the DDC lines to an I2C port of the SoC. We thus need to decouple
> >>>>> if a particular instance of the device supports the operation (exposed
> >>>>> by the ops flags) from the function pointers.
> >>>>> 
> >>>>> We could of course allocate the drm_bridge_funcs structure dynamically
> >>>>> for each bridge instance, and fill it with function pointers manually,
> >>>>> leaving the unused ops always NULL, but that would require making the
> >>>>> structure writable, which is considered a security issue. That's why I
> >>>>> decided to keep the drm_bridge_funcs structure as a global static const
> >>>>> structure, and add an ops bitmask.
> >>>>> 
> >>>>>>>> - Bridge output type
> >>>>>>>>
> >>>>>>>> Add and document these.
> >>>>>>>>
> >>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>> bridges.
> >>>>>>> 
> >>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>> right? More comments about it later.
> >>>>> 
> >>>>> No, the whole point is that they should not be chained at all. A bridge
> >>>>> does not have to propagate, for instance, .get_edid() to the next
> >>>>> bridge. That's one of the core design principles in this series, I want
> >>>>> to keep the bridges as simple as possible, and move the complexity of
> >>>>> the boilerplate code that is currently copied all around to helpers. See
> >>>>> patch "drm: Add helper to create a connector for a chain of bridges" for
> >>>>> more information about how this is used, with a helper that delegates
> >>>>> the connector operations to the correct bridge in the chain based on the
> >>>>> ops reported by each bridge.
> >>>>> 
> >>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>> ---
> >>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>
> >>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>   */
> >>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>  {
> >>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>> +
> >>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>> +
> >>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>  }
> >>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>  
> >>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>  }
> >>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>  
> >>>>>>>> +/**
> >>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>> + * @bridge: bridge control structure
> >>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>> + *
> >>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>> + *
> >>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>> + *
> >>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>> + * the bridge.
> >>>>>>>> + */
> >>>>>>> 
> >>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>> bridge attach:
> >>>>>>> 
> >>>>>>> bridge->hpd_cb = cb;
> >>>>>>> 
> >>>>>>> bridge->hpd_data = data;
> >>>>>>> 
> >>>>>>> ret = drm_bridge_attach(...);
> >>>>>> 
> >>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>> 
> >>>>> That's why I decided to hide hide HPD through helpers,
> >>>>> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> >>>>> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> >>>>> current implementation is limited to a single listener, only the helpers
> >>>>> would need to be changed to extend that to multiple listeners.
> >>>>> 
> >>>>> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> >>>>> bridge to disable HPD detection when not used. Doing so keeps the bridge
> >>>>> simple, it only needs to care about reporting HPD events when they're
> >>>>> enabled, without caring who (if anyone) is listening, and gets clear
> >>>>> instructions on whether to enable or disable the HPD hardware (in case
> >>>>> it can be disabled).
> >>>>> 
> >>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>> without big sacrifices.
> >>>>>>> 
> >>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>> 
> >>>>> This is something this series doesn't implement. I don't think it would
> >>>>> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> >>>>> here. If you can elaborate on what would be needed, I can implement
> >>>>> that.
> >>>>> 
> >>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>> +			   void *data)
> >>>>>>>> +{
> >>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>> +		return;
> >>>>>>>> +
> >>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>> +
> >>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>> +		goto unlock;
> >>>>>>>> +
> >>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>> +
> >>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>> +
> >>>>>>>> +unlock:
> >>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>> +}
> >>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>> +
> >>>>>>>> +/**
> >>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>> + * @bridge: bridge control structure
> >>>>>>>> + *
> >>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>> + * output status change occurs.
> >>>>>>>> + *
> >>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>> + */
> >>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>> +{
> >>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>> +		return;
> >>>>>>>> +
> >>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>> +
> >>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>> +}
> >>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>> +
> >>>>>>>> +/**
> >>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>> + * @bridge: bridge control structure
> >>>>>>>> + * @status: output connection status
> >>>>>>>> + *
> >>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>> + *
> >>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>> + */
> >>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>> +{
> >>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>> 
> >>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>> 
> >>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>> 		if (tmp_bridge == bridge)
> >>>>>> 			continue;
> >>>>>> 		if (bridge->hpd_notify);
> >>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>> 	}
> >>>>>> 
> >>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>> 
> >>>>>> 	dev = bridge->dev
> >>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>> 
> >>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>> hpd they want/need.
> >>>>> 
> >>>>> I'll reply to this further down the mail thread, to address additional
> >>>>> comments.
> >>>>> 
> >>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>> +}
> >>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>> +
> >>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>  /**
> >>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>  
> >>>>>>>> -#include <linux/list.h>
> >>>>>>>>  #include <linux/ctype.h>
> >>>>>>>> +#include <linux/list.h>
> >>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>  
> >>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>  	 */
> >>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>> +
> >>>>>>>> +	/**
> >>>>>>>> +	 * @detect:
> >>>>>>>> +	 *
> >>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>> +	 *
> >>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>> +	 *
> >>>>>>>> +	 * RETURNS:
> >>>>>>>> +	 *
> >>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>> +	 */
> >>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>> +
> >>>>>>>> +	/**
> >>>>>>>> +	 * @get_modes:
> >>>>>>>> +	 *
> >>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>> +	 *
> >>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>> +	 *
> >>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>> +	 *
> >>>>>>>> +	 * RETURNS:
> >>>>>>>> +	 *
> >>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>> +	 */
> >>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>> +
> >>>>>>>> +	/**
> >>>>>>>> +	 * @get_edid:
> >>>>>>>> +	 *
> >>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>> +	 *
> >>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>> +	 *
> >>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>> +	 * output.
> >>>>>>>> +	 *
> >>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>> +	 *
> >>>>>>>> +	 * RETURNS:
> >>>>>>>> +	 *
> >>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>> +	 */
> >>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>> +				 struct drm_connector *connector);
> >>>>>>> 
> >>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>> presence of another one?
> >>>>>>> 
> >>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>> 
> >>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>> and connector->edid correctly.
> >>>>> 
> >>>>> I think that's doable, I'll have a look.
> >>>> 
> >>>> So I had a look, and while this is doable, it would essentially mean
> >>>> that all bridges that retrieve modes from EDID would have to roll out
> >>>> their own version of the following code:
> >>>> 
> >>>> static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> >>>> 					       struct drm_bridge *bridge)
> >>>> {
> >>>> 	enum drm_connector_status status;
> >>>> 	struct edid *edid;
> >>>> 	int n;
> >>>> 
> >>>> 	status = drm_bridge_connector_detect(connector, false);
> >>>> 	if (status != connector_status_connected)
> >>>> 		goto no_edid;
> >>>> 
> >>>> 	edid = bridge->funcs->get_edid(bridge, connector);
> >>>> 	if (!edid || !drm_edid_is_valid(edid)) {
> >>>> 		kfree(edid);
> >>>> 		goto no_edid;
> >>>> 	}
> >>>> 
> >>>> 	drm_connector_update_edid_property(connector, edid);
> >>>> 	n = drm_add_edid_modes(connector, edid);
> >>>> 
> >>>> 	kfree(edid);
> >>>> 	return n;
> >>>> 
> >>>> no_edid:
> >>>> 	drm_connector_update_edid_property(connector, NULL);
> >>>> 	return 0;
> >>>> }
> >>>> 
> >>>> Is this desired ?
> >>> 
> >>> We store the edid, and we store a lot of decoded information in
> >>> drm_connector->display_info. Can't they just look there? Re-fetching the
> >>> edid definitely sounds like the wrong thing to do.
> >>> 
> >>> We might run into some ordering issue here I guess with hotplugs and who's
> >>> fetching the edid and everything like that.
> >> 
> >> That's exactly what I was about to answer after reading your first
> >> paragraph :-) I believe caching EDID is a good idea, but my familiarity
> >> with hotplug-related issues is limited to a handful of systems, and I'm
> >> sure I'm missing some common problems. If you can tell me how you think
> >> this should be done, I can give it a try.
> > 
> > I think all you need to do is make sure that when handling a hpd, the edid
> > is fetched first. Before other parts of the bridge try to reconfigure
> > themselves ...
> > 
> >>> Also maybe I'm missing the point here, and thinking too much of
> >>> ->get_modes on the connector. But then I'm not clear on why the bridge
> >>> needs the connector, and why it instead can't just return the edid it can
> >>> read and let the caller/core figure out everything else?
> >> 
> >> That's exactly what the .get_edid() operation that you asked me to
> >> remove did... :-) You didn't like the fact that it duplicated the
> >> .get_modes() logic. Should I add it back, and clearly document
> >> .get_modes() as a fallback used only when the connector doesn't use EDID
> >> ?
> > 
> > I guess I'm making a bit a fool of myself here. What I meant is that if we
> > do want to keep ->get_edid, then why do you need to pass the connector?
> > Just return the edid blob, and let the caller parse it, and stuff all
> > relevant data into drm_connector. Just thinking along the lines of your
> > goal of making the bridge drivers as dumb as possible.
> > 
> > Same thing for ->get_modes would be neat too, but we don't have a nice
> > datastructure for this. We'd need to pass both a list_head and a pointer
> > to the drm_display_info I think.
> > 
> > That would decouple bridges even more from connector, which I think is
> > somewhere on your goal list ...
> 
> So I had a look at that. We would need to remove the connector argument
> from drm_do_get_edid(). The connector currently stores a few fields
> related to EDID parsing:
> 
>         /**
>          * @null_edid_counter: track sinks that give us all zeros for the EDID.
>          * Needed to workaround some HW bugs where we get all 0s
>          */
>         int null_edid_counter;
> 
>         /** @bad_edid_counter: track sinks that give us an EDID with invalid checksum */
>         unsigned bad_edid_counter;
> 
>         /**
>          * @edid_corrupt: Indicates whether the last read EDID was corrupt. Used
>          * in Displayport compliance testing - Displayport Link CTS Core 1.2
>          * rev1.1 4.2.2.6
>          */
>         bool edid_corrupt;
> 
> We would need to decouple that from drm_connector. One option would be
> to create a new drm_edid structure that would store those three fields,
> as well as a struct edid, and return it from drm_do_get_edid() instead
> of the raw struct edid. Would you prefer a different solution ? Do you
> think that's a prerequisite to for this patch ?

The more I look at EDID parsing, the more it feels that we should
redesign the whole HPD and EDID get handling, and the more it becomes
out of scope for this patch series :-S EDID retrieval and extraction of
information from EDID is intertwined, for instance drm_get_edid()
performs the following:

struct edid *drm_get_edid(struct drm_connector *connector,
			  struct i2c_adapter *adapter)
{
	struct edid *edid;

	if (connector->force == DRM_FORCE_OFF)
		return NULL;

	if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
		return NULL;

	edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
	if (edid)
		drm_get_displayid(connector, edid);
	return edid;
}

I don't think the drm_get_displayid() call belongs there. Moving it to
the numerous callers of drm_get_edid() doesn't seem a good idea. Ideally
it should be done at the same time as populating the modes from EDID,
but I'm pretty sure that would break things, with not all EDID retrieval
resulting in modes updates.

A few drivers call drm_do_get_edid() directly, in order to provide a
custom EDID block read function, and they skip connector->force handling
and drm_get_displayid() as a result. In most case I assume that's a bug
that just went unnoticed.

Decoupling EDID read from drm_connector for bridges in a proper way seem
like a huge piece of work to me, and I really can't make it a
prerequisite for this patch.

> >>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>> correctly updated.
> >>>>>> 
> >>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>> edid, which is not so awesome :-)
> >>>>> 
> >>>>> With the current design there's a single listener, so it's not a big
> >>>>> deal :-) Furthermore, the listener is the helper that creates a
> >>>>> connector on top of a chain of bridges, so it's a pretty good place to
> >>>>> handle this. See the call to drm_kms_helper_hotplug_event() in
> >>>>> drm_bridge_connector_hpd_cb().
> >>>>> 
> >>>>> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> >>>>> big of a requirement as a prerequisite for this series (or as part of
> >>>>> this series). We have hardware that can report HPD with various level of
> >>>>> details (from "something happened on a connector" to "this particular
> >>>>> event happened on this particular connector"), and we channel that
> >>>>> through helpers such as drm_kms_helper_hotplug_event() that lose the
> >>>>> details and go through a heavy mechanism to refetch everything. I
> >>>>> understand this is needed in many cases, but I think there's room for
> >>>>> improvement. This series, in my opinion, doesn't go in the wrong
> >>>>> direction in that regard, as it eventually calls
> >>>>> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> >>>>> on top of it. I'm even willing to work on this, provided I get feedback
> >>>>> on what is desired.
> >>>>> 
> >>>>>>>> +	/**
> >>>>>>>> +	 * @lost_hotplug:
> >>>>>>>> +	 *
> >>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>> +	 *
> >>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>> +	 * HDMI bridges.
> >>>>>>>> +	 */
> >>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>> +
> >>>>>>>> +	/**
> >>>>>>>> +	 * @hpd_enable:
> >>>>>>>> +	 *
> >>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>> +	 * @hpd_disable.
> >>>>>>>> +	 *
> >>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>> +	 */
> >>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>> +
> >>>>>>>> +	/**
> >>>>>>>> +	 * @hpd_disable:
> >>>>>>>> +	 *
> >>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>> +	 * connection status occurs.
> >>>>>>>> +	 *
> >>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>> +	 */
> >>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>  };
> >>>>>>>>  
> >>>>>>>>  /**
> >>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>  	bool dual_link;
> >>>>>>>>  };
> >>>>>>>>  
> >>>>>>>> +/**
> >>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>> + */
> >>>>>>>> +enum drm_bridge_ops {
> >>>>>>>> +	/**
> >>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>> +	 */
> >>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>> +	/**
> >>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>> +	 */
> >>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>> +	/**
> >>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>> +	 */
> >>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>> +	/**
> >>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>> +	 */
> >>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>> +};
> >>>>>>>> +
> >>>>>>>>  /**
> >>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>   */
> >>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>  	void *driver_private;
> >>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>> +	/**
> >>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>> +	 */
> >>>>>>>> +	int type;
> >>>>>>>> +	/** private: */
> >>>>>>>> +	/**
> >>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>> +	 */
> >>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>> +	/**
> >>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>> +	 */
> >>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>> +	/**
> >>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>> +	 * @hpd_cb.
> >>>>>>>> +	 */
> >>>>>>>> +	void *hpd_data;
> >>>>>>>>  };
> >>>>>>>>  
> >>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>  
> >>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>> +			   void *data);
> >>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>> +
> >>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-17  0:14                     ` Laurent Pinchart
@ 2019-08-19  2:37                       ` Laurent Pinchart
  2019-08-20  8:41                         ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-19  2:37 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Tomi Valkeinen, Sean Paul, Sebastian Reichel, dri-devel

Hi Daniel,

On Sat, Aug 17, 2019 at 03:14:56AM +0300, Laurent Pinchart wrote:
> On Sat, Aug 17, 2019 at 02:30:08AM +0300, Laurent Pinchart wrote:
> > On Wed, Aug 14, 2019 at 07:02:29PM +0200, Daniel Vetter wrote:
> >> On Wed, Aug 14, 2019 at 04:30:57PM +0300, Laurent Pinchart wrote:
> >>> On Wed, Aug 14, 2019 at 03:03:29PM +0200, Daniel Vetter wrote:
> >>>> On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> >>>>> On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> >>>>>> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> >>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>> Hi Laurent,
> >>>>>>>> 
> >>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>> panel is very painful.
> >>>>>>> 
> >>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>> 
> >>>>>>>> More comments inlined.
> >>>>>>>> 
> >>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>> data:
> >>>>>>>>>
> >>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>   retrieval operations
> >>>>>>>>> - Bitmask of supported operations
> >>>>>>>> 
> >>>>>>>> 
> >>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>> operation's callback?
> >>>>>>> 
> >>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>> different parts of drivers - "just" add another flag.
> >>>>>> 
> >>>>>> The reason is that a bridge may support an operation (as in implemented
> >>>>>> in the bridge hardware), but that operation may not be supported on a
> >>>>>> particular board. For instance an HDMI encoder may support reading EDID
> >>>>>> when the DDC lines are connected to the encoder, but a board may connect
> >>>>>> the DDC lines to an I2C port of the SoC. We thus need to decouple
> >>>>>> if a particular instance of the device supports the operation (exposed
> >>>>>> by the ops flags) from the function pointers.
> >>>>>> 
> >>>>>> We could of course allocate the drm_bridge_funcs structure dynamically
> >>>>>> for each bridge instance, and fill it with function pointers manually,
> >>>>>> leaving the unused ops always NULL, but that would require making the
> >>>>>> structure writable, which is considered a security issue. That's why I
> >>>>>> decided to keep the drm_bridge_funcs structure as a global static const
> >>>>>> structure, and add an ops bitmask.
> >>>>>> 
> >>>>>>>>> - Bridge output type
> >>>>>>>>>
> >>>>>>>>> Add and document these.
> >>>>>>>>>
> >>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>> bridges.
> >>>>>>>> 
> >>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>> right? More comments about it later.
> >>>>>> 
> >>>>>> No, the whole point is that they should not be chained at all. A bridge
> >>>>>> does not have to propagate, for instance, .get_edid() to the next
> >>>>>> bridge. That's one of the core design principles in this series, I want
> >>>>>> to keep the bridges as simple as possible, and move the complexity of
> >>>>>> the boilerplate code that is currently copied all around to helpers. See
> >>>>>> patch "drm: Add helper to create a connector for a chain of bridges" for
> >>>>>> more information about how this is used, with a helper that delegates
> >>>>>> the connector operations to the correct bridge in the chain based on the
> >>>>>> ops reported by each bridge.
> >>>>>> 
> >>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>> ---
> >>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>
> >>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>   */
> >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>  {
> >>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>> +
> >>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>> +
> >>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>  }
> >>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>  
> >>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>  }
> >>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>  
> >>>>>>>>> +/**
> >>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>> + *
> >>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>> + *
> >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>> + *
> >>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>> + * the bridge.
> >>>>>>>>> + */
> >>>>>>>> 
> >>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>> bridge attach:
> >>>>>>>> 
> >>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>> 
> >>>>>>>> bridge->hpd_data = data;
> >>>>>>>> 
> >>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>> 
> >>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>> 
> >>>>>> That's why I decided to hide hide HPD through helpers,
> >>>>>> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> >>>>>> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> >>>>>> current implementation is limited to a single listener, only the helpers
> >>>>>> would need to be changed to extend that to multiple listeners.
> >>>>>> 
> >>>>>> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> >>>>>> bridge to disable HPD detection when not used. Doing so keeps the bridge
> >>>>>> simple, it only needs to care about reporting HPD events when they're
> >>>>>> enabled, without caring who (if anyone) is listening, and gets clear
> >>>>>> instructions on whether to enable or disable the HPD hardware (in case
> >>>>>> it can be disabled).
> >>>>>> 
> >>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>> without big sacrifices.
> >>>>>>>> 
> >>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>> 
> >>>>>> This is something this series doesn't implement. I don't think it would
> >>>>>> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> >>>>>> here. If you can elaborate on what would be needed, I can implement
> >>>>>> that.
> >>>>>> 
> >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>> +			   void *data)
> >>>>>>>>> +{
> >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>> +		return;
> >>>>>>>>> +
> >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>> +
> >>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>> +		goto unlock;
> >>>>>>>>> +
> >>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>> +
> >>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>> +
> >>>>>>>>> +unlock:
> >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>> +}
> >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>> +
> >>>>>>>>> +/**
> >>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>> + *
> >>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>> + * output status change occurs.
> >>>>>>>>> + *
> >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>> + */
> >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>> +{
> >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>> +		return;
> >>>>>>>>> +
> >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>> +
> >>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>> +}
> >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>> +
> >>>>>>>>> +/**
> >>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>> + * @status: output connection status
> >>>>>>>>> + *
> >>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>> + *
> >>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>> + */
> >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>> +{
> >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>> 
> >>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>> 
> >>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>> 			continue;
> >>>>>>> 		if (bridge->hpd_notify);
> >>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>> 	}
> >>>>>>> 
> >>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>> 
> >>>>>>> 	dev = bridge->dev
> >>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>> 
> >>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>> hpd they want/need.
> >>>>>> 
> >>>>>> I'll reply to this further down the mail thread, to address additional
> >>>>>> comments.
> >>>>>> 
> >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>> +}
> >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>> +
> >>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>  /**
> >>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>  
> >>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>> +#include <linux/list.h>
> >>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>  
> >>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>  	 */
> >>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @detect:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * RETURNS:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>> +	 */
> >>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @get_modes:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * RETURNS:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>> +	 */
> >>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @get_edid:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>> +	 * output.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * RETURNS:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>> +	 */
> >>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>> 
> >>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>> presence of another one?
> >>>>>>>> 
> >>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>> 
> >>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>> and connector->edid correctly.
> >>>>>> 
> >>>>>> I think that's doable, I'll have a look.
> >>>>> 
> >>>>> So I had a look, and while this is doable, it would essentially mean
> >>>>> that all bridges that retrieve modes from EDID would have to roll out
> >>>>> their own version of the following code:
> >>>>> 
> >>>>> static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> >>>>> 					       struct drm_bridge *bridge)
> >>>>> {
> >>>>> 	enum drm_connector_status status;
> >>>>> 	struct edid *edid;
> >>>>> 	int n;
> >>>>> 
> >>>>> 	status = drm_bridge_connector_detect(connector, false);
> >>>>> 	if (status != connector_status_connected)
> >>>>> 		goto no_edid;
> >>>>> 
> >>>>> 	edid = bridge->funcs->get_edid(bridge, connector);
> >>>>> 	if (!edid || !drm_edid_is_valid(edid)) {
> >>>>> 		kfree(edid);
> >>>>> 		goto no_edid;
> >>>>> 	}
> >>>>> 
> >>>>> 	drm_connector_update_edid_property(connector, edid);
> >>>>> 	n = drm_add_edid_modes(connector, edid);
> >>>>> 
> >>>>> 	kfree(edid);
> >>>>> 	return n;
> >>>>> 
> >>>>> no_edid:
> >>>>> 	drm_connector_update_edid_property(connector, NULL);
> >>>>> 	return 0;
> >>>>> }
> >>>>> 
> >>>>> Is this desired ?
> >>>> 
> >>>> We store the edid, and we store a lot of decoded information in
> >>>> drm_connector->display_info. Can't they just look there? Re-fetching the
> >>>> edid definitely sounds like the wrong thing to do.
> >>>> 
> >>>> We might run into some ordering issue here I guess with hotplugs and who's
> >>>> fetching the edid and everything like that.
> >>> 
> >>> That's exactly what I was about to answer after reading your first
> >>> paragraph :-) I believe caching EDID is a good idea, but my familiarity
> >>> with hotplug-related issues is limited to a handful of systems, and I'm
> >>> sure I'm missing some common problems. If you can tell me how you think
> >>> this should be done, I can give it a try.
> >> 
> >> I think all you need to do is make sure that when handling a hpd, the edid
> >> is fetched first. Before other parts of the bridge try to reconfigure
> >> themselves ...
> >> 
> >>>> Also maybe I'm missing the point here, and thinking too much of
> >>>> ->get_modes on the connector. But then I'm not clear on why the bridge
> >>>> needs the connector, and why it instead can't just return the edid it can
> >>>> read and let the caller/core figure out everything else?
> >>> 
> >>> That's exactly what the .get_edid() operation that you asked me to
> >>> remove did... :-) You didn't like the fact that it duplicated the
> >>> .get_modes() logic. Should I add it back, and clearly document
> >>> .get_modes() as a fallback used only when the connector doesn't use EDID
> >>> ?
> >> 
> >> I guess I'm making a bit a fool of myself here. What I meant is that if we
> >> do want to keep ->get_edid, then why do you need to pass the connector?
> >> Just return the edid blob, and let the caller parse it, and stuff all
> >> relevant data into drm_connector. Just thinking along the lines of your
> >> goal of making the bridge drivers as dumb as possible.
> >> 
> >> Same thing for ->get_modes would be neat too, but we don't have a nice
> >> datastructure for this. We'd need to pass both a list_head and a pointer
> >> to the drm_display_info I think.
> >> 
> >> That would decouple bridges even more from connector, which I think is
> >> somewhere on your goal list ...
> > 
> > So I had a look at that. We would need to remove the connector argument
> > from drm_do_get_edid(). The connector currently stores a few fields
> > related to EDID parsing:
> > 
> >         /**
> >          * @null_edid_counter: track sinks that give us all zeros for the EDID.
> >          * Needed to workaround some HW bugs where we get all 0s
> >          */
> >         int null_edid_counter;
> > 
> >         /** @bad_edid_counter: track sinks that give us an EDID with invalid checksum */
> >         unsigned bad_edid_counter;
> > 
> >         /**
> >          * @edid_corrupt: Indicates whether the last read EDID was corrupt. Used
> >          * in Displayport compliance testing - Displayport Link CTS Core 1.2
> >          * rev1.1 4.2.2.6
> >          */
> >         bool edid_corrupt;
> > 
> > We would need to decouple that from drm_connector. One option would be
> > to create a new drm_edid structure that would store those three fields,
> > as well as a struct edid, and return it from drm_do_get_edid() instead
> > of the raw struct edid. Would you prefer a different solution ? Do you
> > think that's a prerequisite to for this patch ?
> 
> The more I look at EDID parsing, the more it feels that we should
> redesign the whole HPD and EDID get handling, and the more it becomes
> out of scope for this patch series :-S EDID retrieval and extraction of
> information from EDID is intertwined, for instance drm_get_edid()
> performs the following:
> 
> struct edid *drm_get_edid(struct drm_connector *connector,
> 			  struct i2c_adapter *adapter)
> {
> 	struct edid *edid;
> 
> 	if (connector->force == DRM_FORCE_OFF)
> 		return NULL;
> 
> 	if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
> 		return NULL;
> 
> 	edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
> 	if (edid)
> 		drm_get_displayid(connector, edid);
> 	return edid;
> }
> 
> I don't think the drm_get_displayid() call belongs there. Moving it to
> the numerous callers of drm_get_edid() doesn't seem a good idea. Ideally
> it should be done at the same time as populating the modes from EDID,
> but I'm pretty sure that would break things, with not all EDID retrieval
> resulting in modes updates.
> 
> A few drivers call drm_do_get_edid() directly, in order to provide a
> custom EDID block read function, and they skip connector->force handling
> and drm_get_displayid() as a result. In most case I assume that's a bug
> that just went unnoticed.
> 
> Decoupling EDID read from drm_connector for bridges in a proper way seem
> like a huge piece of work to me, and I really can't make it a
> prerequisite for this patch.

I still gave it a try, and it resulted in

	git://linuxtv.org/pinchartl/media.git omapdrm/edid

Could you have a look at the last five patches in the branch ?

drm/edid: Reorganise the DisplayID parsing code
drm/edid: Move functions to avoid forward declaration
drm/edid: Move DisplayID tile parsing to drm_connector.c
drm/edid: Honour connector->force in drm_do_get_edid()
[WIP] drm/edid: Decouple EDID retrieval from connector

While the first four patches probably make sense and could be merged
independently of the last one, it's really the fifth patch that makes
decoupling of .get_edid() from drm_connector possible. And it's the
patch I'm the least happy with in this whole series :-S As written in
it's commit message, is it worth it ?

> >>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>> correctly updated.
> >>>>>>> 
> >>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>> edid, which is not so awesome :-)
> >>>>>> 
> >>>>>> With the current design there's a single listener, so it's not a big
> >>>>>> deal :-) Furthermore, the listener is the helper that creates a
> >>>>>> connector on top of a chain of bridges, so it's a pretty good place to
> >>>>>> handle this. See the call to drm_kms_helper_hotplug_event() in
> >>>>>> drm_bridge_connector_hpd_cb().
> >>>>>> 
> >>>>>> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> >>>>>> big of a requirement as a prerequisite for this series (or as part of
> >>>>>> this series). We have hardware that can report HPD with various level of
> >>>>>> details (from "something happened on a connector" to "this particular
> >>>>>> event happened on this particular connector"), and we channel that
> >>>>>> through helpers such as drm_kms_helper_hotplug_event() that lose the
> >>>>>> details and go through a heavy mechanism to refetch everything. I
> >>>>>> understand this is needed in many cases, but I think there's room for
> >>>>>> improvement. This series, in my opinion, doesn't go in the wrong
> >>>>>> direction in that regard, as it eventually calls
> >>>>>> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> >>>>>> on top of it. I'm even willing to work on this, provided I get feedback
> >>>>>> on what is desired.
> >>>>>> 
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>> +
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>> +	 * connection status occurs.
> >>>>>>>>> +	 *
> >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>  };
> >>>>>>>>>  
> >>>>>>>>>  /**
> >>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>  	bool dual_link;
> >>>>>>>>>  };
> >>>>>>>>>  
> >>>>>>>>> +/**
> >>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>> + */
> >>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>> +	 */
> >>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>>  /**
> >>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>   */
> >>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>  	void *driver_private;
> >>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>> +	 */
> >>>>>>>>> +	int type;
> >>>>>>>>> +	/** private: */
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>> +	 */
> >>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>> +	 */
> >>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>> +	/**
> >>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>> +	 */
> >>>>>>>>> +	void *hpd_data;
> >>>>>>>>>  };
> >>>>>>>>>  
> >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>  
> >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>> +			   void *data);
> >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>> +
> >>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 12:40                                 ` Daniel Vetter
@ 2019-08-19  8:38                                   ` Andrzej Hajda
  2019-08-19 22:45                                     ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-08-19  8:38 UTC (permalink / raw)
  To: Daniel Vetter, Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On 14.08.2019 14:40, Daniel Vetter wrote:
> On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
>> Hi Andrzej,
>>
>> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
>>> Hi Laurent,
>>>
>>> Sorry for late response.
>> No worries.
>>
>>> On 11.08.2019 00:43, Laurent Pinchart wrote:
>>>> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
>>>>> On 08.08.2019 21:32, Laurent Pinchart wrote:
>>>>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
>>>>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
>>>>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
>>>>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
>>>>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>>>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>>>> Hi Laurent,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
>>>>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>>>>>>>>>>> panel is very painful.
>>>>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> More comments inlined.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>>>>>>>>>>> data:
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>>>>>>>>>>   retrieval operations
>>>>>>>>>>>>>>>> - Bitmask of supported operations
>>>>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>>>>>>>>>>> operation's callback?
>>>>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>>>>>>>>>>> add, generally good excuse to not have to think through the design between
>>>>>>>>>>>>>> different parts of drivers - "just" add another flag.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> - Bridge output type
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Add and document these.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>>>>>>>>>>> bridges.
>>>>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>>>>>>>>>>> right? More comments about it later.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>>>>>>>>>>   */
>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>  {
>>>>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>  }
>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>  }
>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>> + * @cb: hot-plug detection callback
>>>>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>>>>>>>>>>> + * the bridge.
>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>>>>>>>>>>> bridge attach:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> bridge->hpd_cb = cb;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> bridge->hpd_data = data;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> ret = drm_bridge_attach(...);
>>>>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>>>>>>>>>>> without big sacrifices.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>>>>> +			   void *data)
>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>>>>>>>>>>> +		goto unlock;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>>>>>>>>>>> +	bridge->hpd_data = data;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +unlock:
>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>>>>>>>>>>> + * output status change occurs.
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>> + * @status: output connection status
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>> +			   enum drm_connector_status status)
>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +	if (bridge->hpd_cb)
>>>>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>>>>>>>>>>> 		if (tmp_bridge == bridge)
>>>>>>>>>>>>>> 			continue;
>>>>>>>>>>>>>> 		if (bridge->hpd_notify);
>>>>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>>>>>>>>>>> 	}
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> 	dev = bridge->dev
>>>>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>>>>>>>>>>> hpd they want/need.
>>>>>>>>>>>>> As I understand you want to notify every member of the pipeline.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I think it should be enough to notify only the source, and then source
>>>>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
>>>>>>>>>>>>>
>>>>>>>>>>>>> It looks more generic for me.
>>>>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>>>>>>>>>>> the one from Laurent? Kinda confused here.
>>>>>>>>>>> Regarding general idea:
>>>>>>>>>>>
>>>>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
>>>>>>>>>>> source.
>>>>>>>>>>>
>>>>>>>>>>> 2. Your is to notify all other bridges and encoder.
>>>>>>>>>>>
>>>>>>>>>>> And I prefer 1st approach, why:
>>>>>>>>>>>
>>>>>>>>>>> - the source can decide if/when and to who propagate the signal,
>>>>>>>>>>>
>>>>>>>>>>> - is more generic, for example if bridge send signal to two
>>>>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
>>>>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
>>>>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
>>>>>>>>> If there will be two consumers, there will be two bridge attachments,
>>>>>>>>> thus there will be two notifications, it should work.
>>>>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
>>>>>>>> callback is registered on the produce bridge, not on the consumer bridge
>>>>>>>> (or I'm totallly misreading what Laurent does here).
>>>>>>> I have assumed that if devices exposes two hardware sink interfaces it
>>>>>>> will expose two separate bridges - of course it will not work with
>>>>>>> "bridge chaining" thing, but this is a different story.
>>>>>> Daniel is right that the current implementation only allows one
>>>>>> consumer. This is however not a limitation of the API, but of its
>>>>>> implementation, as I only needed a single consumer. The helpers in this
>>>>>> series ensure that neither the consumer nor the producer poke in the
>>>>>> drm_bridge structure to call back to the HPD handler:
>>>>>>
>>>>>> - The consumer calls drm_bridge_hpd_enable() and
>>>>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
>>>>>>   behaviour if desired without changes to the consumer.
>>>>>>
>>>>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
>>>>>>   which could also easily accommodate reference-counting in the drm
>>>>>>   bridge core without changes to the producer.
>>>>>>
>>>>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
>>>>>>   easily be extended to support multiple consumers without changes to
>>>>>>   the producer.
>>>>>>
>>>>>> This is actually my second version of the HPD mechanism. The first
>>>>>> version was never posted, poked into drm_bridge, and required the
>>>>>> producer to be aware of the callbacks. After discussing this privately
>>>>>> with Daniel, I came up with the implementation in this series that,
>>>>>> while not supporting multiple consumers now, makes it easy to extend
>>>>>> later without minimal effort.
>>>>>>
>>>>>> Daniel's proposed implementation above looks reasonable to me, provided
>>>>>> we can iterate over the bridges in an order that don't depend on the
>>>>>> position of the producer in the chain (should be easy to solve by
>>>>>> starting at the encoder for instance). It however looks a bit like a
>>>>>> midlayer to me :-) That's why I have a similar implementation in the
>>>>>> connector-bridge helper, which could be extended to call
>>>>>> encoder->helper_private->bridge_hpd_notify() and
>>>>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
>>>>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
>>>>>> drm_bridge_hpd_notify() would on the other hand set the notification
>>>>>> sequence towards the encoder and driver in stone. Daniel, do you think
>>>>>> that would be better ?
>>>>>>
>>>>>> I would like to remind everybody that this series isn't the last I will
>>>>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
>>>>>> open to suggestions, and can address problems on top of these patches,
>>>>>> provided obviously that this series doesn't go in the wrong direction.
>>>>>> I'm of course also willing to rework this series, but given the amount
>>>>>> of work we have in the drm_bridge realm, I can't fix everything in one
>>>>>> go :-)
>>>>>>
>>>>>>>>>>> - it resembles hardware wires :)
>>>>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
>>>>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
>>>>>>>>>> interested in that hpd singal. This includes:
>>>>>>>>>> - Other bridges, e.g. if they provide CEC support.
>>>>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
>>>>>>>>>> - Overall driver, so it can update the modes/connector status and send the
>>>>>>>>>>   uevent to the driver.
>>>>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
>>>>>>>>>>   shut down/re-enable the pipe because $reasons.
>>>>>>>>>>  
>>>>>>>>>> That's at least my understanding from lots of chats with Laurent about
>>>>>>>>>> what he wants to do here.
>>>>>> That's correct, and that's what I was trying to implement :-) The
>>>>>> notification, in this patch series, goes from the producer bridge to a
>>>>>> central place (namely the connector, with a helper implementation
>>>>>> available as part of this series, but custom implementations in display
>>>>>> drivers are fine if needed) that then dispatches the notification to all
>>>>>> bridges (through the .lost_hotplug() operation, which we could replace
>>>>>> by an .hpd_notify() operation) for the first two purposes listed above,
>>>>>> and then to the overall driver. The only thing I don't support yet is
>>>>>> dispatching to the display pipeline (item 4 in the list above) as I had
>>>>>> no need for that, and didn't want to develop an API with no user. This
>>>>>> would however not be difficult to do when needed, the need is taken into
>>>>>> account in the proposed implementation.
>>>>>>
>>>>>>>>> I do not know the full picture, but the solution where particular bridge
>>>>>>>>> notifies everything unconditionally seems to me much less flexible.
>>>>>>>>>
>>>>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
>>>>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
>>>>>>>>> will mimic your scenario.
>>>>>>>>>
>>>>>>>>> But there are also other scenarios where bridge does not want to
>>>>>>>>> propagate signal, because for example:
>>>>>>>>>
>>>>>>>>> - it wants to wait for other sinks to wake up,
>>>>>>>>>
>>>>>>>> The other sink can just do that in their hpd callback.
>>>>>>>>
>>>>>>>>> - it propagates HPD signal via hardware wire,
>>>>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
>>>>>>>> the wire/hw hpd interrupt.
>>>>>>>>
>>>>>>> If it should ignore HPD, why it should receive it at all - it is
>>>>>>> unnecessary noise. And I am afraid with more complicated pipelines it
>>>>>>> will be impossible for particular component (bridge/encoder/whatever) to
>>>>>>> distinguish if HPD notification which came from non-directly connected
>>>>>>> component should be ignored or not.
>>>>>>>
>>>>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
>>>>>>>>> device.
>>>>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
>>>>>>>> board?
>>>>>>> Bridge can have external connectors, and the user can connect there
>>>>>>> anything.
>>>>>>>
>>>>>>>>> In general HPD is input signal for notify of state changes on particular
>>>>>>>>> bus, in case of typical video bridge on its output video bus.
>>>>>>>>>
>>>>>>>>> In case of bridges they have also input video buses, and they can send
>>>>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
>>>>>>>>> if for most cases they looks similar.
>>>>>>>> Ah, I think this is a problem we will eventually have. But it's not
>>>>>>>> something we're currently solving here at all I think.
>>>>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
>>>>>>> line, so this is not something from far future. And I guess with HPD
>>>>>>> broadcasting it could be racy/error prone, for example EDID reading can
>>>>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
>>>>>>> controller via hw wires also).
>>>>>>>
>>>>>>>>>>> And regarding implementation:
>>>>>>>>>>>
>>>>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>>>>>>>>>>
>>>>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>>>>>>>>>>
>>>>>>>>>>> Your proposition is more straightforward, but if we want to notify only
>>>>>>>>>>> source we should locate it by parsing notification chain (what about
>>>>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
>>>>>>>>>>>
>>>>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
>>>>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
>>>>>>>>>> Uh I think we're not talking about the same thing really. My understanding
>>>>>>>>>> is that this callback is if someone (outside of this bridge) is interested
>>>>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
>>>>>>>>>> listener.
>>>>>>>>> Do we have real life examples?
>>>>>>>>>
>>>>>>>>> I want to distinguish two situations:
>>>>>>>>>
>>>>>>>>> - another device wants to know if input bus of the bridge has changed state,
>>>>>>>>>
>>>>>>>>> - another device wants to know if output bus of the bridge has changed
>>>>>>>>> state.
>>>>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
>>>>>>>> bridges can exchange state and information about each another. hpd is
>>>>>>>> about the physical world, i.e. "is there a cable plugged into the port
>>>>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
>>>>>>>> fun stuff like that, we have the atomic_check machinery for this.
>>>>>>> My question was if we have real examples that upstream device requires
>>>>>>> knowledge about state of output line of the bridge?
>>>>>>>
>>>>>>> To be more precise, we have following display pipeline:
>>>>>>>
>>>>>>> A-->B-->C
>>>>>>>
>>>>>>> And C sends HPD to B (ie signal that state of line between B and C
>>>>>>> changed). Does A really wants to know this information? or it should
>>>>>>> just need to know if state of line A-->B changed?
>>>>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
>>>>>> ESD protector and level shifter, and C is the physical HDMI connector.
>>>>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
>>>>>> be notified in order to reset the CEC state machine. One could however
>>>>>> argue that in that case the A-B link state changes too, but the
>>>>>> important part is that HPD detection is not performed by A, while A
>>>>>> needs to be informed of lost hotplug.
>>>>> I have no full picture but I guess in this case C sends HPD to B using
>>>>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
>>>>> say that B does not participate in HPD transmission/forwarding,
>>>> No, in this case A doesn't receive any hardware HPD signal, it requires
>>>> HPD notification through software.
>>>>
>>>>> some shifters with 'advanced power saving' can even perform wake-up of
>>>>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
>>>>> to A is indeed different than HPD sent from C to B.
>>>>>
>>>>> Btw, with the above logic of propagation of HPD callback (proposed by
>>>>> Daniel) I guess it will work this way:
>>>>>
>>>>> - A will receive HPD signal via HW,
>>>>>
>>>>> - then B and C will receive HPD callback via framework.
>>>>>
>>>>> Am I right?
>>>> It's the other way around.
>>>>
>>>> In this case the HPD signal from the connector (C) is routed to an input
>>>> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
>>>> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
>>>> IRQ and receive the hardware HPD notification. The driver for the HDMI
>>>> encoder (A) needs to receive HPD notification in software, through the
>>>> framework.
>>> If this is GPIO I wonder why do not query this gpio by encoder directly,
>>> rules of ownership of such gpios seems to be grey area, so in such case
>>> I would advise to put it in the driver who really needs it.
>>>
>>> This way it will be much simpler.
>> First to fall, multiple drivers may need to be informed of HPD events
>> coming from a GPIO, so we would need to duplicate it in multiple places,
>> and I don't think the GPIO framework allows acquiring a GPIO multiple
>> times.
>>
>> Then, the GPIO is described in DT, and DT doesn't care about which
>> driver needs HPD events. DT specifies the GPIO in the node of the device
>> it belongs to, this is defined in DT bindings, and must be the same on
>> all boards, while depending on the board different devices may need to
>> be informed of HPD events.
>>
>> For those two reasons HPD GPIO handling and consumption of HPD events
>> can't always be grouped in the same driver.
>>
>>> Going back to HPD notifications, as I said earlier broadcasting HPD
>>> notification unconditionally to every member of the chain with hope that
>>> the member will be able to filter-out undesired notification seems to me
>>> incorrect - maybe it can solve some problems but is not flexible enough
>>> to be usable in other scenarios.
>>>
>>> If my arguments do not convince you please just continue with your
>>> ideas, we can always add NO_HPD_BROADCAST somewhere :)
>> :-) I would like to understand the problems you're referring to though,
>> and hopefully solve them. If you could describe one of the scenarios
>> where you think this mechanism wouldn't be usable that would help. In
>> the meantime I will post a new version of the series with these
>> operations kept as-is to get the rest of the patches reviewed.
> See my little thing about midlayers, I think midlayers with lots of flags
> for everything aren't a good idea. They should be more opinionated about
> how things work.
>
> So if there's a case where this broadcasting of various things doesn't
> work, let's dig into it.
> -Daniel


OK, almost real life example:

A -> B -> C

A - RGB/HDMI converter,

B - HDMI/MHL converter,

C - uUSB controller (MUIC).


C - detects presence of MHL sink and routes MHL lines to B in such case.

B - has no hardware logic to detect HPD, but it's firmware can read EDID
from downstream component via HW lines and it has hardware lines to
upstream component to send EDID,

A - can read EDID from B via hardware lines, but does not have hardware HPD.


So how it should work (according to specification):

1. C detects MHL sink.

2. C switches his mux to route lines to B.

3. C sends HPD notification to B.

4. B powers on, its firmware reads EDID from downstream lines (possibly
adjusting it) and makes it available to upstream component A.

5. B sends HPD notification to A.


I do not know how it could work with HPD broadcasting.

I guess C should be HPD provider, but in case of HPD broadcasting A and
B would receive notification in the same time, as a result A would start
reading EDID too early - fail.


Regards

Andrzej


>
>>>>>>>>>> You seem to have some other idea here.
>>>>>>>>>>
>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>  #ifdef CONFIG_OF
>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>> -#include <linux/list.h>
>>>>>>>>>>>>>>>>  #include <linux/ctype.h>
>>>>>>>>>>>>>>>> +#include <linux/list.h>
>>>>>>>>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>>>>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>>>>>>>>>>  	 */
>>>>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @detect:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @get_modes:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>> +			 struct drm_connector *connector);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @get_edid:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>>>>>>>>>>> +	 * output.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>> +				 struct drm_connector *connector);
>>>>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>>>>>>>>>>> presence of another one?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>>>>>>>>>>> and connector->edid correctly.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
>>>>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>>>>>>>>>>> correctly updated.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>>>>>>>>>>> edid, which is not so awesome :-)
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @lost_hotplug:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>>>>>>>>>>> +	 * HDMI bridges.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @hpd_enable:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>>>>>>>>>>> +	 * @hpd_disable.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @hpd_disable:
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>>>>>>>>>>> +	 * connection status occurs.
>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>>>>>>>>>>  	bool dual_link;
>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>> +enum drm_bridge_ops {
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>>>>>>>>>>   */
>>>>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>>>>>>>>>>  	void *driver_private;
>>>>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>>>>>>>>>>> +	 * identifies the type of connected display.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	int type;
>>>>>>>>>>>>>>>> +	/** private: */
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	struct mutex hpd_mutex;
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>>>>>>>>>>> +	 * @hpd_cb.
>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>> +	void *hpd_data;
>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>>>>> +			   void *data);
>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>> +			   enum drm_connector_status status);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>>>>>>>>>>  					u32 connector_type);
>> -- 
>> Regards,
>>
>> Laurent Pinchart


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges
  2019-08-14 15:01         ` Daniel Vetter
@ 2019-08-19 22:16           ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-19 22:16 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Wed, Aug 14, 2019 at 05:01:31PM +0200, Daniel Vetter wrote:
> On Thu, Aug 08, 2019 at 10:48:43PM +0300, Laurent Pinchart wrote:
> > On Thu, Jul 18, 2019 at 07:01:03PM +0200, Daniel Vetter wrote:
> >> On Sun, Jul 07, 2019 at 09:19:02PM +0300, Laurent Pinchart wrote:
> >>> Most bridge drivers create a DRM connector to model the connector at the
> >>> output of the bridge. This model is historical and has worked pretty
> >>> well so far, but causes several issues:
> >>> 
> >>> - It prevents supporting more complex display pipelines where DRM
> >>> connector operations are split over multiple components. For instance a
> >>> pipeline with a bridge connected to the DDC signals to read EDID data,
> >>> and another one connected to the HPD signal to detect connection and
> >>> disconnection, will not be possible to support through this model.
> >>> 
> >>> - It requires every bridge driver to implement similar connector
> >>> handling code, resulting in code duplication.
> >>> 
> >>> - It assumes that a bridge will either be wired to a connector or to
> >>> another bridge, but doesn't support bridges that can be used in both
> >>> positions very well (although there is some ad-hoc support for this in
> >>> the analogix_dp bridge driver).
> >>> 
> >>> In order to solve these issues, ownership of the connector needs to be
> >>> moved to the display controller driver.
> >>> 
> >>> To avoid code duplication in display controller drivers, add a new
> >>> helper to create and manage a DRM connector backed by a chain of
> >>> bridges. All connector operations are delegating to the appropriate
> >>> bridge in the chain.
> >>> 
> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> ---
> >>>  drivers/gpu/drm/Makefile               |   3 +-
> >>>  drivers/gpu/drm/drm_bridge_connector.c | 385 +++++++++++++++++++++++++
> >>>  include/drm/drm_bridge_connector.h     |  18 ++
> >>>  3 files changed, 405 insertions(+), 1 deletion(-)
> >>>  create mode 100644 drivers/gpu/drm/drm_bridge_connector.c
> >>>  create mode 100644 include/drm/drm_bridge_connector.h
> >>> 
> >>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> >>> index 9f0d2ee35794..1b74653c9db9 100644
> >>> --- a/drivers/gpu/drm/Makefile
> >>> +++ b/drivers/gpu/drm/Makefile
> >>> @@ -37,7 +37,8 @@ drm_vram_helper-y := drm_gem_vram_helper.o \
> >>>  		     drm_vram_mm_helper.o
> >>>  obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
> >>>  
> >>> -drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper.o \
> >>> +drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
> >>> +		drm_dsc.o drm_probe_helper.o \
> >>>  		drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
> >>>  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
> >>>  		drm_simple_kms_helper.o drm_modeset_helper.o \
> >>> diff --git a/drivers/gpu/drm/drm_bridge_connector.c b/drivers/gpu/drm/drm_bridge_connector.c
> >>> new file mode 100644
> >>> index 000000000000..09f2d6bfb561
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/drm_bridge_connector.c
> >>> @@ -0,0 +1,385 @@
> >>> +// SPDX-License-Identifier: GPL-2.0+
> >>> +/*
> >>> + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> + */
> >>> +
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/slab.h>
> >>> +
> >>> +#include <drm/drm_atomic_state_helper.h>
> >>> +#include <drm/drm_bridge.h>
> >>> +#include <drm/drm_bridge_connector.h>
> >>> +#include <drm/drm_connector.h>
> >>> +#include <drm/drm_device.h>
> >>> +#include <drm/drm_edid.h>
> >>> +#include <drm/drm_modeset_helper_vtables.h>
> >>> +#include <drm/drm_probe_helper.h>
> >>> +
> >>> +/**
> >>> + * DOC: overview
> >>> + *
> >>> + * The DRM bridge connector helper object provides a DRM connector
> >>> + * implementation that wraps a chain of &struct drm_bridge. The connector
> >>> + * operations are fully implemented based on the operations of the bridges in
> >>> + * the chain, and don't require any intervention from the display controller
> >>> + * driver at runtime.
> >>> + *
> >>> + * To use the helper, display controller drivers create a bridge connector with
> >>> + * a call to drm_bridge_connector_init(). This associates the newly created
> >>> + * connector with the chain of bridges passed to the function and registers it
> >>> + * with the DRM device. At that point the connector becomes fully usable, no
> >>> + * further operation is needed.
> >>> + *
> >>> + * The DRM bridge connector operations are implemented based on the operations
> >>> + * provided by the bridges in the chain. Each connector operation is delegated
> >>> + * to the bridge closest to the connector (at the end of the chain) that
> >>> + * provides the relevant functionality.
> >>> + *
> >>> + * To make use of this helper, all bridges in the chain shall report bridge
> >>> + * operation flags (&drm_bridge->ops) and bridge output type
> >>> + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> >>> + */
> >>> +
> >>> +/**
> >>> + * struct drm_bridge_connector - A connector backed by a chain of bridges
> >>> + */
> >>> +struct drm_bridge_connector {
> >>> +	/**
> >>> +	 * @base: The base DRM connector
> >>> +	 */
> >>> +	struct drm_connector base;
> >>> +	/**
> >>> +	 * @bridge:
> >>> +	 *
> >>> +	 * The first bridge in the chain (connected to the output of the CRTC).
> >>> +	 */
> >>> +	struct drm_bridge *bridge;
> >>> +	/**
> >>> +	 * @bridge_edid:
> >>> +	 *
> >>> +	 * The last bridge in the chain (closest to the connector) that provides
> >>> +	 * EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
> >>> +	 */
> >>> +	struct drm_bridge *bridge_edid;
> >>> +	/**
> >>> +	 * @bridge_hpd:
> >>> +	 *
> >>> +	 * The last bridge in the chain (closest to the connector) that provides
> >>> +	 * hot-plug detection notification, if any (see &DRM_BRIDGE_OP_HPD).
> >>> +	 */
> >>> +	struct drm_bridge *bridge_hpd;
> >>> +	/**
> >>> +	 * @bridge_detect:
> >>> +	 *
> >>> +	 * The last bridge in the chain (closest to the connector) that provides
> >>> +	 * connector detection, if any (see &DRM_BRIDGE_OP_DETECT).
> >>> +	 */
> >>> +	struct drm_bridge *bridge_detect;
> >>> +	/**
> >>> +	 * @bridge_detect:
> >>> +	 *
> >>> +	 * The last bridge in the chain (closest to the connector) that provides
> >>> +	 * connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
> >>> +	 */
> >>> +	struct drm_bridge *bridge_modes;
> >>> +	/**
> >>> +	 * @hdmi_mode: Valid for HDMI connectors only.
> >>> +	 */
> >>> +	bool hdmi_mode;
> >> 
> >> This should probably be in drm_display_info somewhere, not here?
> > 
> > Yes, and it's unused in this patch, I've just noticed that. Field
> > dropped.
> > 
> >> Wrt the overall design ... why do we need a new struct? If we assume (at
> >> least for now) that we only allow one encoder for such a bridge chain
> >> (currently still true), then you can always go from the connector to it's
> >> only possibel encoder. And from there to the bridge chain.
> >> 
> >> Furthermore all the special bridge pointers here can just be found at
> >> runtime by walking the bridge links. And none of these paths are hot
> >> enough to make this a problem.
> >> 
> >> With that your drm_bridge_connector here would become just a pile of
> >> functions as default implementations for connectors. Making it more
> >> modular and more helper-y and easier to transition gradually.
> > 
> > The main purpose of this structure is indeed to cache the bridge
> > pointers, which could be recalculated at runtime. I agree there's no
> > real hot path, but caching them still feels nice :-)
> 
> We've treated anything in atomic as not a hot-path, preferring clean code
> over fast. Except if someone can proof otherwise, which very few ever
> bother to even try :-)

But does it hurt to keep this structure ? It's internal to
drm_bridge_connector.c, not visible from the outside. As such it's an
implementation decision, and I think it makes the code easier to follow.
What's your main concern ? Not memory usage I assume, is there something
else that bothers you in particular ? From a driver point of view only
drm_connector is visible.

> > How do you go from the connector to its encoder though ? The
> > drm_connector encoder field is valid for non-atomic drivers only, and
> > the encoder_ids field is marked as not to be accessed directly. Should I
> > use drm_connector_for_each_possible_encoder() and pick the first encoder
> > ?
> 
> pick_single_encoder_for_connector. Was once exported even ... I think for
> bridge we can just hard-code the assumption that there's only one
> connector for a bridge chain.

I suppose I can rename it to
drm_atomic_helper_pick_single_encoder_for_connector() and export it. A
bit of a long name though :-)

> For more fancy topologies this ofc all breaks down, but maybe we can
> postpone solving that problem ...

Sure, no issue with that.

> >>> +};
> >>> +
> >>> +#define to_drm_bridge_connector(x) \
> >>> +	container_of(x, struct drm_bridge_connector, base)
> >>> +
> >>> +/* -----------------------------------------------------------------------------
> >>> + * Bridge Connector Hot-Plug Handling
> >>> + */
> >>> +
> >>> +static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
> >>> +					    enum drm_connector_status status)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +	struct drm_bridge *bridge;
> >>> +
> >>> +	if (status != connector_status_disconnected)
> >>> +		return;
> >>> +
> >>> +	/*
> >>> +	 * Notify all bridges in the pipeline of disconnection. This is required
> >>> +	 * to let the HDMI encoders reset their internal state related to
> >>> +	 * connection status, such as the CEC address.
> >>> +	 */
> >>> +	for (bridge = bridge_connector->bridge; bridge; bridge = bridge->next) {
> >>> +		if (bridge->funcs->lost_hotplug)
> >>> +			bridge->funcs->lost_hotplug(bridge);
> >> 
> >> So looking at this you pretty much implement my idea for hdp handling
> >> already, except you're calling it ->lost_hotplug and not ->notify_hpd.
> > 
> > Renamed already in my private tree, will be in v2 :-)
> > 
> >> Plus you require some callback registration. Essentially my design (that I
> >> explained in my reply to your bridge patch) would just make
> >> drm_bridge_connector_hpd_cb() the one and only hpd_cb, and punt all hpd
> >> handling to bridge drivers like you do here.
> >> 
> >> Ofc that leaves us with "who's calling drm_kms_helper_hotplug_event()",
> >> and that's what the new hdp_notify on the encoder and the global
> >> drm_mode_config_helper_funcs would be for.
> > 
> > Let's discuss that in the replies to the other patch, as the discussion
> > is already longer there. I'm not opposed to your proposal, but I've
> > asked a few questions to clarify it.
> > 
> >>> +	}
> >>> +}
> >>> +
> >>> +static void drm_bridge_connector_hpd_cb(void *cb_data,
> >>> +					enum drm_connector_status status)
> >>> +{
> >>> +	struct drm_bridge_connector *drm_bridge_connector = cb_data;
> >>> +	struct drm_connector *connector = &drm_bridge_connector->base;
> >>> +	struct drm_device *dev = connector->dev;
> >>> +	enum drm_connector_status old_status;
> >>> +
> >>> +	mutex_lock(&dev->mode_config.mutex);
> >>> +	old_status = connector->status;
> >>> +	connector->status = status;
> >>> +	mutex_unlock(&dev->mode_config.mutex);
> >>> +
> >>> +	if (old_status == status)
> >>> +		return;
> >>> +
> >>> +	drm_bridge_connector_hpd_notify(connector, status);
> >>> +
> >>> +	drm_kms_helper_hotplug_event(dev);
> >>> +}
> >>> +
> >>> +/**
> >>> + * drm_bridge_connector_enable_hpd - Enable hot-plug detection for the connector
> >>> + * @connector: The DRM bridge connector
> >>> + *
> >>> + * This function enables hot-plug detection for the given bridge connector.
> >>> + * This is typically used by display drivers in their resume handler.
> >>> + */
> >>> +void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> >>> +
> >>> +	if (hpd)
> >>> +		drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
> >>> +				      bridge_connector);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_connector_enable_hpd);
> >>> +
> >>> +/**
> >>> + * drm_bridge_connector_disable_hpd - Disable hot-plug detection for the
> >>> + * connector
> >>> + * @connector: The DRM bridge connector
> >>> + *
> >>> + * This function disables hot-plug detection for the given bridge connector.
> >>> + * This is typically used by display drivers in their suspend handler.
> >>> + */
> >>> +void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +	struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> >>> +
> >>> +	if (hpd)
> >>> +		drm_bridge_hpd_disable(hpd);
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_connector_disable_hpd);
> >>> +
> >>> +/* -----------------------------------------------------------------------------
> >>> + * Bridge Connector Functions
> >>> + */
> >>> +
> >>> +static enum drm_connector_status
> >>> +drm_bridge_connector_detect(struct drm_connector *connector, bool force)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +	struct drm_bridge *detect = bridge_connector->bridge_detect;
> >>> +	enum drm_connector_status status;
> >>> +
> >>> +	if (detect) {
> >>> +		status = detect->funcs->detect(detect);
> >>> +
> >>> +		drm_bridge_connector_hpd_notify(connector, status);
> >>> +	} else {
> >>> +		switch (connector->connector_type) {
> >>> +		case DRM_MODE_CONNECTOR_DPI:
> >>> +		case DRM_MODE_CONNECTOR_LVDS:
> >>> +		case DRM_MODE_CONNECTOR_DSI:
> >>> +			status = connector_status_connected;
> >>> +			break;
> >>> +		default:
> >>> +			status = connector_status_unknown;
> >>> +			break;
> >>> +		}
> >>> +	}
> >>> +
> >>> +	return status;
> >>> +}
> >>> +
> >>> +static void drm_bridge_connector_destroy(struct drm_connector *connector)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +
> >>> +	if (bridge_connector->bridge_hpd) {
> >>> +		struct drm_bridge *hpd = bridge_connector->bridge_hpd;
> >>> +
> >>> +		drm_bridge_hpd_disable(hpd);
> >>> +	}
> >>> +
> >>> +	drm_connector_unregister(connector);
> >>> +	drm_connector_cleanup(connector);
> >>> +
> >>> +	kfree(bridge_connector);
> >>> +}
> >>> +
> >>> +static const struct drm_connector_funcs drm_bridge_connector_funcs = {
> >>> +	.reset = drm_atomic_helper_connector_reset,
> >>> +	.detect = drm_bridge_connector_detect,
> >> 
> >> For that smooht helper library feeling I think we should export _detect
> >> and get_modes at least.
> > 
> > Already, even without a user ? Or should we wait until someone needs
> > them ?
> 
> I figured mostly to have an excuse for the kerneldoc ...
> 
> >>> +	.fill_modes = drm_helper_probe_single_connector_modes,
> >>> +	.destroy = drm_bridge_connector_destroy,
> >>> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> >>> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >>> +};
> >>> +
> >>> +/* -----------------------------------------------------------------------------
> >>> + * Bridge Connector Helper Functions
> >>> + */
> >>> +
> >>> +#define MAX_EDID  512
> >>> +
> >>> +static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> >>> +					       struct drm_bridge *bridge)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +	enum drm_connector_status status;
> >>> +	struct edid *edid;
> >>> +	int n;
> >>> +
> >>> +	status = drm_bridge_connector_detect(connector, false);
> >>> +	if (status != connector_status_connected)
> >>> +		goto no_edid;
> >>> +
> >>> +	edid = bridge->funcs->get_edid(bridge, connector);
> >>> +	if (!edid || !drm_edid_is_valid(edid)) {
> >>> +		kfree(edid);
> >>> +		goto no_edid;
> >>> +	}
> >>> +
> >>> +	drm_connector_update_edid_property(connector, edid);
> >>> +	n = drm_add_edid_modes(connector, edid);
> >>> +
> >>> +	bridge_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
> >>> +
> >>> +	kfree(edid);
> >>> +	return n;
> >>> +
> >>> +no_edid:
> >>> +	drm_connector_update_edid_property(connector, NULL);
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int drm_bridge_connector_get_modes(struct drm_connector *connector)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector =
> >>> +		to_drm_bridge_connector(connector);
> >>> +	struct drm_bridge *bridge;
> >>> +
> >>> +	/*
> >>> +	 * If display exposes EDID, then we parse that in the normal way to
> >>> +	 * build table of supported modes.
> >>> +	 */
> >>> +	bridge = bridge_connector->bridge_edid;
> >>> +	if (bridge)
> >>> +		return drm_bridge_connector_get_modes_edid(connector, bridge);
> >>> +
> >>> +	/*
> >>> +	 * Otherwise if the display pipeline reports modes (e.g. with a fixed
> >>> +	 * resolution panel or an analog TV output), query it.
> >>> +	 */
> >>> +	bridge = bridge_connector->bridge_modes;
> >>> +	if (bridge)
> >>> +		return bridge->funcs->get_modes(bridge, connector);
> >>> +
> >>> +	/*
> >>> +	 * We can't retrieve modes, which can happen for instance for a DVI or
> >>> +	 * VGA output with the DDC bus unconnected. The KMS core will add the
> >>> +	 * default modes.
> >>> +	 */
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
> >>> +	.get_modes = drm_bridge_connector_get_modes,
> >>> +	/* No need for .mode_valid(), the bridges are checked by the core. */
> >>> +};
> >>> +
> >>> +/* -----------------------------------------------------------------------------
> >>> + * Bridge Connector Initialisation
> >>> + */
> >>> +
> >>> +/**
> >>> + * drm_bridge_connector_init - Initialise a connector for a chain of bridges
> >>> + * @drm: the DRM device
> >>> + * @bridge: the bridge closest to the CRTC output
> >>> + *
> >>> + * Allocate, initialise and register a &drm_bridge_connector with the @drm
> >>> + * device. The connector is associated with a chain of bridges that starts at
> >>> + * the CRTC output with @bridge. All bridges in the chain shall report bridge
> >>> + * operation flags (&drm_bridge->ops) and bridge output type
> >>> + * (&drm_bridge->type), and none of them may create a DRM connector directly.
> >>> + *
> >>> + * Returns a pointer to the new connector on success, or a negative error
> >>> + * pointer otherwise.
> >>> + */
> >>> +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> >>> +						struct drm_bridge *bridge)
> >>> +{
> >>> +	struct drm_bridge_connector *bridge_connector;
> >>> +	struct drm_connector *connector;
> >>> +	int connector_type;
> >>> +
> >>> +	bridge_connector = kzalloc(sizeof(*bridge_connector), GFP_KERNEL);
> >>> +	if (!bridge_connector)
> >>> +		return ERR_PTR(-ENOMEM);
> >>> +
> >>> +	bridge_connector->bridge = bridge;
> >>> +
> >>> +	/*
> >>> +	 * Initialise connector status handling. First locate the furthest
> >>> +	 * bridges in the pipeline that support HPD and output detection. Then
> >>> +	 * initialise the connector polling mode, using HPD if available and
> >>> +	 * falling back to polling if supported. If neither HPD nor output
> >>> +	 * detection are available, we don't support hotplug detection at all.
> >>> +	 */
> >>> +	connector_type = DRM_MODE_CONNECTOR_Unknown;
> >>> +	for ( ; bridge; bridge = bridge->next) {
> >>> +		if (bridge->ops & DRM_BRIDGE_OP_EDID)
> >>> +			bridge_connector->bridge_edid = bridge;
> >>> +		if (bridge->ops & DRM_BRIDGE_OP_HPD)
> >>> +			bridge_connector->bridge_hpd = bridge;
> >>> +		if (bridge->ops & DRM_BRIDGE_OP_DETECT)
> >>> +			bridge_connector->bridge_detect = bridge;
> >>> +		if (bridge->ops & DRM_BRIDGE_OP_MODES)
> >>> +			bridge_connector->bridge_modes = bridge;
> >>> +
> >>> +		if (!bridge->next)
> >>> +			connector_type = bridge->type;
> >>> +	}
> >>> +
> >>> +	if (connector_type == DRM_MODE_CONNECTOR_Unknown) {
> >>> +		kfree(bridge_connector);
> >>> +		return ERR_PTR(-EINVAL);
> >>> +	}
> >>> +
> >>> +	/*
> >>> +	 * TODO: Handle interlace_allowed, doublescan_allowed, stereo_allowed
> >>> +	 * and ycbcr_420_allowed.
> >>> +	 */
> >>> +	connector = &bridge_connector->base;
> >>> +	drm_connector_init(drm, connector, &drm_bridge_connector_funcs,
> >>> +			   connector_type);
> >>> +	drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
> >>> +
> >>> +	if (bridge_connector->bridge_hpd)
> >>> +		connector->polled = DRM_CONNECTOR_POLL_HPD;
> >>> +	else if (bridge_connector->bridge_detect)
> >>> +		connector->polled = DRM_CONNECTOR_POLL_CONNECT
> >>> +				  | DRM_CONNECTOR_POLL_DISCONNECT;
> >>> +
> >>> +	return connector;
> >>> +}
> >>> +EXPORT_SYMBOL_GPL(drm_bridge_connector_init);
> >>> diff --git a/include/drm/drm_bridge_connector.h b/include/drm/drm_bridge_connector.h
> >>> new file mode 100644
> >>> index 000000000000..ec33b44954b8
> >>> --- /dev/null
> >>> +++ b/include/drm/drm_bridge_connector.h
> >>> @@ -0,0 +1,18 @@
> >>> +/* SPDX-License-Identifier: GPL-2.0+ */
> >>> +/*
> >>> + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>> + */
> >>> +
> >>> +#ifndef __DRM_BRIDGE_CONNECTOR_H__
> >>> +#define __DRM_BRIDGE_CONNECTOR_H__
> >>> +
> >>> +struct drm_bridge;
> >>> +struct drm_connector;
> >>> +struct drm_device;
> >>> +
> >>> +void drm_bridge_connector_enable_hpd(struct drm_connector *connector);
> >>> +void drm_bridge_connector_disable_hpd(struct drm_connector *connector);
> >>> +struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
> >>> +						struct drm_bridge *bridge);
> >>> +
> >>> +#endif /* __DRM_BRIDGE_CONNECTOR_H__ */

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-14 12:35                         ` Daniel Vetter
@ 2019-08-19 22:32                           ` Laurent Pinchart
  2019-08-20  8:30                             ` Daniel Vetter
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-19 22:32 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Wed, Aug 14, 2019 at 02:35:10PM +0200, Daniel Vetter wrote:
> On Thu, Aug 08, 2019 at 10:32:14PM +0300, Laurent Pinchart wrote:
> > On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>> Hi Laurent,
> >>>>>>>>>>
> >>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>> panel is very painful.
> >>>>>>>>>
> >>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>
> >>>>>>>>>> More comments inlined.
> >>>>>>>>>>
> >>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>> data:
> >>>>>>>>>>>
> >>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>   retrieval operations
> >>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>
> >>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>> operation's callback?
> >>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>
> >>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>
> >>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>
> >>>>>>>>>>> Add and document these.
> >>>>>>>>>>>
> >>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>> bridges.
> >>>>>>>>>>
> >>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>
> >>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>> ---
> >>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>
> >>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>   */
> >>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>  {
> >>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>> +
> >>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>  }
> >>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>  
> >>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>  }
> >>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>  
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>> + * the bridge.
> >>>>>>>>>>> + */
> >>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>> bridge attach:
> >>>>>>>>>>
> >>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>
> >>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>
> >>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>
> >>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>
> >>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>> without big sacrifices.
> >>>>>>>>>>
> >>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>
> >>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>> +			   void *data)
> >>>>>>>>>>> +{
> >>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>> +		return;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +unlock:
> >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>> +}
> >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>> +
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>> + */
> >>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>> +{
> >>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>> +		return;
> >>>>>>>>>>> +
> >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>> +}
> >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>> +
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>> + *
> >>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>> + */
> >>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>> +{
> >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>
> >>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>
> >>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>> 			continue;
> >>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>> 	}
> >>>>>>>>>
> >>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>
> >>>>>>>>> 	dev = bridge->dev
> >>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>
> >>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>> hpd they want/need.
> >>>>>>>>
> >>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>
> >>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>
> >>>>>>>> It looks more generic for me.
> >>>>>>>
> >>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>
> >>>>>> Regarding general idea:
> >>>>>>
> >>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>> source.
> >>>>>>
> >>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>
> >>>>>> And I prefer 1st approach, why:
> >>>>>>
> >>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>
> >>>>>> - is more generic, for example if bridge send signal to two
> >>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>
> >>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>
> >>>> If there will be two consumers, there will be two bridge attachments,
> >>>> thus there will be two notifications, it should work.
> >>>
> >>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>> callback is registered on the produce bridge, not on the consumer bridge
> >>> (or I'm totallly misreading what Laurent does here).
> >> 
> >> I have assumed that if devices exposes two hardware sink interfaces it
> >> will expose two separate bridges - of course it will not work with
> >> "bridge chaining" thing, but this is a different story.
> > 
> > Daniel is right that the current implementation only allows one
> > consumer. This is however not a limitation of the API, but of its
> > implementation, as I only needed a single consumer. The helpers in this
> > series ensure that neither the consumer nor the producer poke in the
> > drm_bridge structure to call back to the HPD handler:
> > 
> > - The consumer calls drm_bridge_hpd_enable() and
> >   drm_bridge_hpd_disable(), which could offer a reference-counted
> >   behaviour if desired without changes to the consumer.
> > 
> > - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >   which could also easily accommodate reference-counting in the drm
> >   bridge core without changes to the producer.
> > 
> > - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >   easily be extended to support multiple consumers without changes to
> >   the producer.
> > 
> > This is actually my second version of the HPD mechanism. The first
> > version was never posted, poked into drm_bridge, and required the
> > producer to be aware of the callbacks. After discussing this privately
> > with Daniel, I came up with the implementation in this series that,
> > while not supporting multiple consumers now, makes it easy to extend
> > later without minimal effort.
> > 
> > Daniel's proposed implementation above looks reasonable to me, provided
> > we can iterate over the bridges in an order that don't depend on the
> > position of the producer in the chain (should be easy to solve by
> > starting at the encoder for instance). It however looks a bit like a
> > midlayer to me :-) That's why I have a similar implementation in the
> > connector-bridge helper, which could be extended to call
> > encoder->helper_private->bridge_hpd_notify() and
> > dev->mode_config.helper_private->bridge_hpd_notify() instead of
> > hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> > drm_bridge_hpd_notify() would on the other hand set the notification
> > sequence towards the encoder and driver in stone. Daniel, do you think
> > that would be better ?
> 
> So the difference between the midlayer and the helper is that the helper
> can be ignored. Which the above still can:
> 
> - producer can choose to not call that function
> - consumer can choose not to have the callback
> 
> Now great helpers allow you to ignore only parts of them, so that you can
> mix&match. Which again I think with the bridge stuff we're discussing here
> is assured.

That's a bit difficult for the first part, as if the producer doesn't
notify of HPD events, consumers won't be able to get them :-) The second
part, consumers not having the callback, is already supported.

> So the final bit is how opinionated a helper can be, and imo it can be
> very opinionated and strict and inflexible. That means it won't be useful
> for every possible case, but those can be handled by simply not using the
> helper (or that part of the helpers). Examples
> 
> - simple display pipe is very opinionated, but trades that in for being
>   very useful for really simple displays
> 
> - similar with atomic helpers, there's a very strong suggestion that "if
>   it doesn't fit, write your own commit_tail()"

(On a side note, doing so is quite complex, and I understand why nobody
wants to really ditch the atomic helpers)

> And I think bridge helpers probably also need fairly opinioated, simply to
> make sure that all the bridge drivers work together in a coherent fashion.
> If we allow too much flexibility everyone bends the rules a bit, and
> nothing fits.

I agree with you on that.

> Wrt your question: One option would be to do the same thing like shared
> interrupt line handlers. As soon as the first interrupt handler says "I'
> ve handled this one" we stop processing. But that might lead to more
> confusion about who's responsible for an interrupt.

I don't think that's a good idea, as more than one consumer may need to
process the event. A real life example with two consumers would be a CEC
controller part of a bridge needing to get informed about HDMI
plug/unplug to set the CEC address in the device (this notification is
handled through the bridge notification operation), and the display
driver needing to report HPD to the DRM core.

As I'm still not sure why you think I should replace the existing
implementation with your above proposal, so I'll keep the existing code
for the v2 that I will post soon until we complete this discussion.

To hopefully help with the discussion, I would like to repeat my main
argument : moving the dispatching of the notification to
drm_bridge_hpd_notify() sets the order in which components (bridges,
encoders, drivers) are notified in stone, while keeping it in the
drm_bridge_connector helper allows drivers to not use the helper and
come up with a different implementation that fits their needs better.

> > I would like to remind everybody that this series isn't the last I will
> > ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> > open to suggestions, and can address problems on top of these patches,
> > provided obviously that this series doesn't go in the wrong direction.
> > I'm of course also willing to rework this series, but given the amount
> > of work we have in the drm_bridge realm, I can't fix everything in one
> > go :-)
> > 
> >>>>>> - it resembles hardware wires :)
> >>>>>
> >>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>> interested in that hpd singal. This includes:
> >>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>   uevent to the driver.
> >>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>  
> >>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>> what he wants to do here.
> > 
> > That's correct, and that's what I was trying to implement :-) The
> > notification, in this patch series, goes from the producer bridge to a
> > central place (namely the connector, with a helper implementation
> > available as part of this series, but custom implementations in display
> > drivers are fine if needed) that then dispatches the notification to all
> > bridges (through the .lost_hotplug() operation, which we could replace
> > by an .hpd_notify() operation) for the first two purposes listed above,
> > and then to the overall driver. The only thing I don't support yet is
> > dispatching to the display pipeline (item 4 in the list above) as I had
> > no need for that, and didn't want to develop an API with no user. This
> > would however not be difficult to do when needed, the need is taken into
> > account in the proposed implementation.
> > 
> >>>> I do not know the full picture, but the solution where particular bridge
> >>>> notifies everything unconditionally seems to me much less flexible.
> >>>>
> >>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>> will mimic your scenario.
> >>>>
> >>>> But there are also other scenarios where bridge does not want to
> >>>> propagate signal, because for example:
> >>>>
> >>>> - it wants to wait for other sinks to wake up,
> >>>
> >>> The other sink can just do that in their hpd callback.
> >>>
> >>>> - it propagates HPD signal via hardware wire,
> >>>
> >>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>> the wire/hw hpd interrupt.
> >> 
> >> If it should ignore HPD, why it should receive it at all - it is
> >> unnecessary noise. And I am afraid with more complicated pipelines it
> >> will be impossible for particular component (bridge/encoder/whatever) to
> >> distinguish if HPD notification which came from non-directly connected
> >> component should be ignored or not.
> >> 
> >>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>> device.
> >>>
> >>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>> board?
> >> 
> >> Bridge can have external connectors, and the user can connect there
> >> anything.
> >> 
> >>>> In general HPD is input signal for notify of state changes on particular
> >>>> bus, in case of typical video bridge on its output video bus.
> >>>>
> >>>> In case of bridges they have also input video buses, and they can send
> >>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>> if for most cases they looks similar.
> >>>
> >>> Ah, I think this is a problem we will eventually have. But it's not
> >>> something we're currently solving here at all I think.
> >> 
> >> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >> line, so this is not something from far future. And I guess with HPD
> >> broadcasting it could be racy/error prone, for example EDID reading can
> >> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >> controller via hw wires also).
> >> 
> >>>>>> And regarding implementation:
> >>>>>>
> >>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>
> >>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>
> >>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>> source we should locate it by parsing notification chain (what about
> >>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>
> >>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>
> >>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>> listener.
> >>>>
> >>>> Do we have real life examples?
> >>>>
> >>>> I want to distinguish two situations:
> >>>>
> >>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>
> >>>> - another device wants to know if output bus of the bridge has changed
> >>>> state.
> >>>
> >>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>> bridges can exchange state and information about each another. hpd is
> >>> about the physical world, i.e. "is there a cable plugged into the port
> >>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>> fun stuff like that, we have the atomic_check machinery for this.
> >> 
> >> My question was if we have real examples that upstream device requires
> >> knowledge about state of output line of the bridge?
> >> 
> >> To be more precise, we have following display pipeline:
> >> 
> >> A-->B-->C
> >> 
> >> And C sends HPD to B (ie signal that state of line between B and C
> >> changed). Does A really wants to know this information? or it should
> >> just need to know if state of line A-->B changed?
> > 
> > There's one real life example, where A is an HDMI encoder, B is an HDMI
> > ESD protector and level shifter, and C is the physical HDMI connector.
> > When the HDMI cable is unplugged, the CEC controller part of A needs to
> > be notified in order to reset the CEC state machine. One could however
> > argue that in that case the A-B link state changes too, but the
> > important part is that HPD detection is not performed by A, while A
> > needs to be informed of lost hotplug.
> > 
> >>>>> You seem to have some other idea here.
> >>>>>
> >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>> +}
> >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>> +
> >>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>  /**
> >>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>  
> >>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>  
> >>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>  	 */
> >>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>> +	 * output.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>> presence of another one?
> >>>>>>>>>>
> >>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>> and connector->edid correctly.
> >>>>>>>>>
> >>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>> correctly updated.
> >>>>>>>>>
> >>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>> +
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>> +	 *
> >>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>  };
> >>>>>>>>>>>  
> >>>>>>>>>>>  /**
> >>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>  };
> >>>>>>>>>>>  
> >>>>>>>>>>> +/**
> >>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>> + */
> >>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>> +};
> >>>>>>>>>>> +
> >>>>>>>>>>>  /**
> >>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>   */
> >>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	int type;
> >>>>>>>>>>> +	/** private: */
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>> +	/**
> >>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>> +	 */
> >>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>  };
> >>>>>>>>>>>  
> >>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>  
> >>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>> +			   void *data);
> >>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>> +
> >>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-19  8:38                                   ` Andrzej Hajda
@ 2019-08-19 22:45                                     ` Laurent Pinchart
  2019-08-22 12:17                                       ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-19 22:45 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Mon, Aug 19, 2019 at 10:38:35AM +0200, Andrzej Hajda wrote:
> On 14.08.2019 14:40, Daniel Vetter wrote:
> > On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
> >> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
> >>> On 11.08.2019 00:43, Laurent Pinchart wrote:
> >>>> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
> >>>>> On 08.08.2019 21:32, Laurent Pinchart wrote:
> >>>>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >>>>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>>>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>>>> Hi Laurent,
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>>>>>>> panel is very painful.
> >>>>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> More comments inlined.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>>>>>>> data:
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>>>>>>   retrieval operations
> >>>>>>>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>>>>>>> operation's callback?
> >>>>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> Add and document these.
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>>>>>>> bridges.
> >>>>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>>>>>>> ---
> >>>>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>>>>>>   */
> >>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>  {
> >>>>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>  }
> >>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>  }
> >>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>>>>>>> + * the bridge.
> >>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>>>>>>> bridge attach:
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>>>>>>> without big sacrifices.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>>>>> +			   void *data)
> >>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +unlock:
> >>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>>>>>>> 			continue;
> >>>>>>>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>>>>>>> 	}
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> 	dev = bridge->dev
> >>>>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>>>>>>> hpd they want/need.
> >>>>>>>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> It looks more generic for me.
> >>>>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>>>>>> Regarding general idea:
> >>>>>>>>>>>
> >>>>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>>>>>>> source.
> >>>>>>>>>>>
> >>>>>>>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>>>>>>
> >>>>>>>>>>> And I prefer 1st approach, why:
> >>>>>>>>>>>
> >>>>>>>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>>>>>>
> >>>>>>>>>>> - is more generic, for example if bridge send signal to two
> >>>>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>>>>>> If there will be two consumers, there will be two bridge attachments,
> >>>>>>>>> thus there will be two notifications, it should work.
> >>>>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>>>>>>> callback is registered on the produce bridge, not on the consumer bridge
> >>>>>>>> (or I'm totallly misreading what Laurent does here).
> >>>>>>> I have assumed that if devices exposes two hardware sink interfaces it
> >>>>>>> will expose two separate bridges - of course it will not work with
> >>>>>>> "bridge chaining" thing, but this is a different story.
> >>>>>> Daniel is right that the current implementation only allows one
> >>>>>> consumer. This is however not a limitation of the API, but of its
> >>>>>> implementation, as I only needed a single consumer. The helpers in this
> >>>>>> series ensure that neither the consumer nor the producer poke in the
> >>>>>> drm_bridge structure to call back to the HPD handler:
> >>>>>>
> >>>>>> - The consumer calls drm_bridge_hpd_enable() and
> >>>>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
> >>>>>>   behaviour if desired without changes to the consumer.
> >>>>>>
> >>>>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >>>>>>   which could also easily accommodate reference-counting in the drm
> >>>>>>   bridge core without changes to the producer.
> >>>>>>
> >>>>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >>>>>>   easily be extended to support multiple consumers without changes to
> >>>>>>   the producer.
> >>>>>>
> >>>>>> This is actually my second version of the HPD mechanism. The first
> >>>>>> version was never posted, poked into drm_bridge, and required the
> >>>>>> producer to be aware of the callbacks. After discussing this privately
> >>>>>> with Daniel, I came up with the implementation in this series that,
> >>>>>> while not supporting multiple consumers now, makes it easy to extend
> >>>>>> later without minimal effort.
> >>>>>>
> >>>>>> Daniel's proposed implementation above looks reasonable to me, provided
> >>>>>> we can iterate over the bridges in an order that don't depend on the
> >>>>>> position of the producer in the chain (should be easy to solve by
> >>>>>> starting at the encoder for instance). It however looks a bit like a
> >>>>>> midlayer to me :-) That's why I have a similar implementation in the
> >>>>>> connector-bridge helper, which could be extended to call
> >>>>>> encoder->helper_private->bridge_hpd_notify() and
> >>>>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> >>>>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> >>>>>> drm_bridge_hpd_notify() would on the other hand set the notification
> >>>>>> sequence towards the encoder and driver in stone. Daniel, do you think
> >>>>>> that would be better ?
> >>>>>>
> >>>>>> I would like to remind everybody that this series isn't the last I will
> >>>>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> >>>>>> open to suggestions, and can address problems on top of these patches,
> >>>>>> provided obviously that this series doesn't go in the wrong direction.
> >>>>>> I'm of course also willing to rework this series, but given the amount
> >>>>>> of work we have in the drm_bridge realm, I can't fix everything in one
> >>>>>> go :-)
> >>>>>>
> >>>>>>>>>>> - it resembles hardware wires :)
> >>>>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>>>>>>> interested in that hpd singal. This includes:
> >>>>>>>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>>>>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>>>>>>   uevent to the driver.
> >>>>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>>>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>>>>>>  
> >>>>>>>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>>>>>>> what he wants to do here.
> >>>>>> That's correct, and that's what I was trying to implement :-) The
> >>>>>> notification, in this patch series, goes from the producer bridge to a
> >>>>>> central place (namely the connector, with a helper implementation
> >>>>>> available as part of this series, but custom implementations in display
> >>>>>> drivers are fine if needed) that then dispatches the notification to all
> >>>>>> bridges (through the .lost_hotplug() operation, which we could replace
> >>>>>> by an .hpd_notify() operation) for the first two purposes listed above,
> >>>>>> and then to the overall driver. The only thing I don't support yet is
> >>>>>> dispatching to the display pipeline (item 4 in the list above) as I had
> >>>>>> no need for that, and didn't want to develop an API with no user. This
> >>>>>> would however not be difficult to do when needed, the need is taken into
> >>>>>> account in the proposed implementation.
> >>>>>>
> >>>>>>>>> I do not know the full picture, but the solution where particular bridge
> >>>>>>>>> notifies everything unconditionally seems to me much less flexible.
> >>>>>>>>>
> >>>>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>>>>>>> will mimic your scenario.
> >>>>>>>>>
> >>>>>>>>> But there are also other scenarios where bridge does not want to
> >>>>>>>>> propagate signal, because for example:
> >>>>>>>>>
> >>>>>>>>> - it wants to wait for other sinks to wake up,
> >>>>>>>>>
> >>>>>>>> The other sink can just do that in their hpd callback.
> >>>>>>>>
> >>>>>>>>> - it propagates HPD signal via hardware wire,
> >>>>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>>>>>>> the wire/hw hpd interrupt.
> >>>>>>>>
> >>>>>>> If it should ignore HPD, why it should receive it at all - it is
> >>>>>>> unnecessary noise. And I am afraid with more complicated pipelines it
> >>>>>>> will be impossible for particular component (bridge/encoder/whatever) to
> >>>>>>> distinguish if HPD notification which came from non-directly connected
> >>>>>>> component should be ignored or not.
> >>>>>>>
> >>>>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>>>>>>> device.
> >>>>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>>>>>>> board?
> >>>>>>> Bridge can have external connectors, and the user can connect there
> >>>>>>> anything.
> >>>>>>>
> >>>>>>>>> In general HPD is input signal for notify of state changes on particular
> >>>>>>>>> bus, in case of typical video bridge on its output video bus.
> >>>>>>>>>
> >>>>>>>>> In case of bridges they have also input video buses, and they can send
> >>>>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>>>>>>> if for most cases they looks similar.
> >>>>>>>> Ah, I think this is a problem we will eventually have. But it's not
> >>>>>>>> something we're currently solving here at all I think.
> >>>>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >>>>>>> line, so this is not something from far future. And I guess with HPD
> >>>>>>> broadcasting it could be racy/error prone, for example EDID reading can
> >>>>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >>>>>>> controller via hw wires also).
> >>>>>>>
> >>>>>>>>>>> And regarding implementation:
> >>>>>>>>>>>
> >>>>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>>>>>>
> >>>>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>>>>>>
> >>>>>>>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>>>>>>> source we should locate it by parsing notification chain (what about
> >>>>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>>>>>>
> >>>>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>>>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>>>>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>>>>>>> listener.
> >>>>>>>>> Do we have real life examples?
> >>>>>>>>>
> >>>>>>>>> I want to distinguish two situations:
> >>>>>>>>>
> >>>>>>>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>>>>>>
> >>>>>>>>> - another device wants to know if output bus of the bridge has changed
> >>>>>>>>> state.
> >>>>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>>>>>>> bridges can exchange state and information about each another. hpd is
> >>>>>>>> about the physical world, i.e. "is there a cable plugged into the port
> >>>>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>>>>>>> fun stuff like that, we have the atomic_check machinery for this.
> >>>>>>> My question was if we have real examples that upstream device requires
> >>>>>>> knowledge about state of output line of the bridge?
> >>>>>>>
> >>>>>>> To be more precise, we have following display pipeline:
> >>>>>>>
> >>>>>>> A-->B-->C
> >>>>>>>
> >>>>>>> And C sends HPD to B (ie signal that state of line between B and C
> >>>>>>> changed). Does A really wants to know this information? or it should
> >>>>>>> just need to know if state of line A-->B changed?
> >>>>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
> >>>>>> ESD protector and level shifter, and C is the physical HDMI connector.
> >>>>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
> >>>>>> be notified in order to reset the CEC state machine. One could however
> >>>>>> argue that in that case the A-B link state changes too, but the
> >>>>>> important part is that HPD detection is not performed by A, while A
> >>>>>> needs to be informed of lost hotplug.
> >>>>> I have no full picture but I guess in this case C sends HPD to B using
> >>>>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
> >>>>> say that B does not participate in HPD transmission/forwarding,
> >>>> No, in this case A doesn't receive any hardware HPD signal, it requires
> >>>> HPD notification through software.
> >>>>
> >>>>> some shifters with 'advanced power saving' can even perform wake-up of
> >>>>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
> >>>>> to A is indeed different than HPD sent from C to B.
> >>>>>
> >>>>> Btw, with the above logic of propagation of HPD callback (proposed by
> >>>>> Daniel) I guess it will work this way:
> >>>>>
> >>>>> - A will receive HPD signal via HW,
> >>>>>
> >>>>> - then B and C will receive HPD callback via framework.
> >>>>>
> >>>>> Am I right?
> >>>> It's the other way around.
> >>>>
> >>>> In this case the HPD signal from the connector (C) is routed to an input
> >>>> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
> >>>> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
> >>>> IRQ and receive the hardware HPD notification. The driver for the HDMI
> >>>> encoder (A) needs to receive HPD notification in software, through the
> >>>> framework.
> >>> If this is GPIO I wonder why do not query this gpio by encoder directly,
> >>> rules of ownership of such gpios seems to be grey area, so in such case
> >>> I would advise to put it in the driver who really needs it.
> >>>
> >>> This way it will be much simpler.
> >> First to fall, multiple drivers may need to be informed of HPD events
> >> coming from a GPIO, so we would need to duplicate it in multiple places,
> >> and I don't think the GPIO framework allows acquiring a GPIO multiple
> >> times.
> >>
> >> Then, the GPIO is described in DT, and DT doesn't care about which
> >> driver needs HPD events. DT specifies the GPIO in the node of the device
> >> it belongs to, this is defined in DT bindings, and must be the same on
> >> all boards, while depending on the board different devices may need to
> >> be informed of HPD events.
> >>
> >> For those two reasons HPD GPIO handling and consumption of HPD events
> >> can't always be grouped in the same driver.
> >>
> >>> Going back to HPD notifications, as I said earlier broadcasting HPD
> >>> notification unconditionally to every member of the chain with hope that
> >>> the member will be able to filter-out undesired notification seems to me
> >>> incorrect - maybe it can solve some problems but is not flexible enough
> >>> to be usable in other scenarios.
> >>>
> >>> If my arguments do not convince you please just continue with your
> >>> ideas, we can always add NO_HPD_BROADCAST somewhere :)
> >> :-) I would like to understand the problems you're referring to though,
> >> and hopefully solve them. If you could describe one of the scenarios
> >> where you think this mechanism wouldn't be usable that would help. In
> >> the meantime I will post a new version of the series with these
> >> operations kept as-is to get the rest of the patches reviewed.
> > See my little thing about midlayers, I think midlayers with lots of flags
> > for everything aren't a good idea. They should be more opinionated about
> > how things work.
> >
> > So if there's a case where this broadcasting of various things doesn't
> > work, let's dig into it.
> > -Daniel
> 
> OK, almost real life example:
> 
> A -> B -> C
> 
> A - RGB/HDMI converter,
> 
> B - HDMI/MHL converter,
> 
> C - uUSB controller (MUIC).
> 
> 
> C - detects presence of MHL sink and routes MHL lines to B in such case.
> 
> B - has no hardware logic to detect HPD, but it's firmware can read EDID
> from downstream component via HW lines and it has hardware lines to
> upstream component to send EDID,
> 
> A - can read EDID from B via hardware lines, but does not have hardware HPD.

It probably doesn't matter much for the overall discussion, but out of
curiosity, does B have a CBUS interface towards C and a DDC (I2C)
interface towards A ? And does A read the EDID on DDC and expose it
towards the SoC through a custom protocol (for instance as the ADV7511
does), or does it forward the DDC lines to the SoC ?

> So how it should work (according to specification):
> 
> 1. C detects MHL sink.
> 
> 2. C switches his mux to route lines to B.
> 
> 3. C sends HPD notification to B.
> 
> 4. B powers on, its firmware reads EDID from downstream lines (possibly
> adjusting it) and makes it available to upstream component A.
> 
> 5. B sends HPD notification to A.
> 
> 
> I do not know how it could work with HPD broadcasting.
> 
> I guess C should be HPD provider, but in case of HPD broadcasting A and
> B would receive notification in the same time, as a result A would start
> reading EDID too early - fail.

That's an interesting case indeed. Now I understand what you meant
earlier.

The HPD notification from C to B is purely internal, and should not be
visible from a DRM/KMS point of view. It just happens that this hardware
setup has a more complex HPD sequence that requires software
intervention in the middle of the sequence. As such, if we forget about
this patch series for a minute, C would need a custom API to send MHL
notification to B, and the HPD for DRM/KMS would be notified by B, right
?

I think it's possible to handle both the MHL notification and the
user-visible HPD notification through the same bridge API, provided that
we offer a way for a bridge to block forwarding of the HPD notification.
This will also require calling the HPD notifiers on bridges in the sink
to source order. Both are doable, the bridge HPD notifier operation
could return a bool that blocks propagation of the notification. Would
that work for you ?

> >>>>>>>>>> You seem to have some other idea here.
> >>>>>>>>>>
> >>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>>>>>>  	 */
> >>>>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>>>>>>> +	 * output.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>>>>>>> presence of another one?
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>>>>>>> and connector->edid correctly.
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>>>>>>> correctly updated.
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>>>>>>> +};
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>>>>>>   */
> >>>>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	int type;
> >>>>>>>>>>>>>>>> +	/** private: */
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>>>>> +			   void *data);
> >>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-19 22:32                           ` Laurent Pinchart
@ 2019-08-20  8:30                             ` Daniel Vetter
  2019-08-26 15:57                               ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Daniel Vetter @ 2019-08-20  8:30 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Tue, Aug 20, 2019 at 01:32:09AM +0300, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Wed, Aug 14, 2019 at 02:35:10PM +0200, Daniel Vetter wrote:
> > On Thu, Aug 08, 2019 at 10:32:14PM +0300, Laurent Pinchart wrote:
> > > On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> > >> On 16.07.2019 11:00, Daniel Vetter wrote:
> > >>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> > >>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> > >>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> > >>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> > >>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> > >>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> > >>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > >>>>>>>>>> Hi Laurent,
> > >>>>>>>>>>
> > >>>>>>>>>> I like the approach, current practice when almost every bridge should
> > >>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> > >>>>>>>>>> panel is very painful.
> > >>>>>>>>>
> > >>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> > >>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> > >>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> > >>>>>>>>> get a feeling for what your hpd_cb usually does.
> > >>>>>>>>>
> > >>>>>>>>>> More comments inlined.
> > >>>>>>>>>>
> > >>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> > >>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> > >>>>>>>>>>> data:
> > >>>>>>>>>>>
> > >>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> > >>>>>>>>>>>   retrieval operations
> > >>>>>>>>>>> - Bitmask of supported operations
> > >>>>>>>>>>
> > >>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> > >>>>>>>>>> operation's callback?
> > >>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> > >>>>>>>>>
> > >>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > >>>>>>>>> add, generally good excuse to not have to think through the design between
> > >>>>>>>>> different parts of drivers - "just" add another flag.
> > >>>>>>>>>
> > >>>>>>>>>>> - Bridge output type
> > >>>>>>>>>>>
> > >>>>>>>>>>> Add and document these.
> > >>>>>>>>>>>
> > >>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> > >>>>>>>>>>> notification in a way that is as transparent as possible for the
> > >>>>>>>>>>> bridges.
> > >>>>>>>>>>
> > >>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> > >>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> > >>>>>>>>>> right? More comments about it later.
> > >>>>>>>>>>
> > >>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>>>>>>>>>> ---
> > >>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> > >>>>>>>>>>>
> > >>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> > >>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >>>>>>>>>>>   */
> > >>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> > >>>>>>>>>>>  {
> > >>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> > >>>>>>>>>>> +
> > >>>>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> > >>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >>>>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>>>  	list_del_init(&bridge->list);
> > >>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> > >>>>>>>>>>>  }
> > >>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> > >>>>>>>>>>>  
> > >>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>  }
> > >>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >>>>>>>>>>>  
> > >>>>>>>>>>> +/**
> > >>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > >>>>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>>>> + * @cb: hot-plug detection callback
> > >>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > >>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> > >>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> > >>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> > >>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> > >>>>>>>>>>> + * the bridge.
> > >>>>>>>>>>> + */
> > >>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> > >>>>>>>>>> bridge attach:
> > >>>>>>>>>>
> > >>>>>>>>>> bridge->hpd_cb = cb;
> > >>>>>>>>>>
> > >>>>>>>>>> bridge->hpd_data = data;
> > >>>>>>>>>>
> > >>>>>>>>>> ret = drm_bridge_attach(...);
> > >>>>>>>>>
> > >>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> > >>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> > >>>>>>>>>
> > >>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> > >>>>>>>>>> without big sacrifices.
> > >>>>>>>>>>
> > >>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > >>>>>>>>>> notifies about sink status change, how it translates to this cb?
> > >>>>>>>>>>
> > >>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>>>> +			   void *data)
> > >>>>>>>>>>> +{
> > >>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> > >>>>>>>>>>> +		return;
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > >>>>>>>>>>> +		goto unlock;
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	bridge->hpd_cb = cb;
> > >>>>>>>>>>> +	bridge->hpd_data = data;
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +unlock:
> > >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>>>> +}
> > >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +/**
> > >>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > >>>>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > >>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > >>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> > >>>>>>>>>>> + * output status change occurs.
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>>>> + */
> > >>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > >>>>>>>>>>> +{
> > >>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> > >>>>>>>>>>> +		return;
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	bridge->hpd_cb = NULL;
> > >>>>>>>>>>> +	bridge->hpd_data = NULL;
> > >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>>>> +}
> > >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +/**
> > >>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> > >>>>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>>>> + * @status: output connection status
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> > >>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> > >>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > >>>>>>>>>>> + *
> > >>>>>>>>>>> + * This function shall be called in a context that can sleep.
> > >>>>>>>>>>> + */
> > >>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>>>> +			   enum drm_connector_status status)
> > >>>>>>>>>>> +{
> > >>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>>>> +	if (bridge->hpd_cb)
> > >>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > >>>>>>>>>
> > >>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> > >>>>>>>>>
> > >>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> > >>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> > >>>>>>>>> 		if (tmp_bridge == bridge)
> > >>>>>>>>> 			continue;
> > >>>>>>>>> 		if (bridge->hpd_notify);
> > >>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > >>>>>>>>> 	}
> > >>>>>>>>>
> > >>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> > >>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> > >>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > >>>>>>>>>
> > >>>>>>>>> 	dev = bridge->dev
> > >>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > >>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > >>>>>>>>>
> > >>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> > >>>>>>>>> hpd they want/need.
> > >>>>>>>>
> > >>>>>>>> As I understand you want to notify every member of the pipeline.
> > >>>>>>>>
> > >>>>>>>> I think it should be enough to notify only the source, and then source
> > >>>>>>>> should decide if/when the hpd should be propagated upstream.
> > >>>>>>>>
> > >>>>>>>> It looks more generic for me.
> > >>>>>>>
> > >>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> > >>>>>>> the one from Laurent? Kinda confused here.
> > >>>>>>
> > >>>>>> Regarding general idea:
> > >>>>>>
> > >>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> > >>>>>> source.
> > >>>>>>
> > >>>>>> 2. Your is to notify all other bridges and encoder.
> > >>>>>>
> > >>>>>> And I prefer 1st approach, why:
> > >>>>>>
> > >>>>>> - the source can decide if/when and to who propagate the signal,
> > >>>>>>
> > >>>>>> - is more generic, for example if bridge send signal to two
> > >>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> > >>>>>
> > >>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> > >>>>> consumer. There's only 1 callback. So you're example doesn't work.
> > >>>>
> > >>>> If there will be two consumers, there will be two bridge attachments,
> > >>>> thus there will be two notifications, it should work.
> > >>>
> > >>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> > >>> callback is registered on the produce bridge, not on the consumer bridge
> > >>> (or I'm totallly misreading what Laurent does here).
> > >> 
> > >> I have assumed that if devices exposes two hardware sink interfaces it
> > >> will expose two separate bridges - of course it will not work with
> > >> "bridge chaining" thing, but this is a different story.
> > > 
> > > Daniel is right that the current implementation only allows one
> > > consumer. This is however not a limitation of the API, but of its
> > > implementation, as I only needed a single consumer. The helpers in this
> > > series ensure that neither the consumer nor the producer poke in the
> > > drm_bridge structure to call back to the HPD handler:
> > > 
> > > - The consumer calls drm_bridge_hpd_enable() and
> > >   drm_bridge_hpd_disable(), which could offer a reference-counted
> > >   behaviour if desired without changes to the consumer.
> > > 
> > > - The producer gets configured by .hpd_enable() and .hpd_disable(),
> > >   which could also easily accommodate reference-counting in the drm
> > >   bridge core without changes to the producer.
> > > 
> > > - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> > >   easily be extended to support multiple consumers without changes to
> > >   the producer.
> > > 
> > > This is actually my second version of the HPD mechanism. The first
> > > version was never posted, poked into drm_bridge, and required the
> > > producer to be aware of the callbacks. After discussing this privately
> > > with Daniel, I came up with the implementation in this series that,
> > > while not supporting multiple consumers now, makes it easy to extend
> > > later without minimal effort.
> > > 
> > > Daniel's proposed implementation above looks reasonable to me, provided
> > > we can iterate over the bridges in an order that don't depend on the
> > > position of the producer in the chain (should be easy to solve by
> > > starting at the encoder for instance). It however looks a bit like a
> > > midlayer to me :-) That's why I have a similar implementation in the
> > > connector-bridge helper, which could be extended to call
> > > encoder->helper_private->bridge_hpd_notify() and
> > > dev->mode_config.helper_private->bridge_hpd_notify() instead of
> > > hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> > > drm_bridge_hpd_notify() would on the other hand set the notification
> > > sequence towards the encoder and driver in stone. Daniel, do you think
> > > that would be better ?
> > 
> > So the difference between the midlayer and the helper is that the helper
> > can be ignored. Which the above still can:
> > 
> > - producer can choose to not call that function
> > - consumer can choose not to have the callback
> > 
> > Now great helpers allow you to ignore only parts of them, so that you can
> > mix&match. Which again I think with the bridge stuff we're discussing here
> > is assured.
> 
> That's a bit difficult for the first part, as if the producer doesn't
> notify of HPD events, consumers won't be able to get them :-) The second
> part, consumers not having the callback, is already supported.
> 
> > So the final bit is how opinionated a helper can be, and imo it can be
> > very opinionated and strict and inflexible. That means it won't be useful
> > for every possible case, but those can be handled by simply not using the
> > helper (or that part of the helpers). Examples
> > 
> > - simple display pipe is very opinionated, but trades that in for being
> >   very useful for really simple displays
> > 
> > - similar with atomic helpers, there's a very strong suggestion that "if
> >   it doesn't fit, write your own commit_tail()"
> 
> (On a side note, doing so is quite complex, and I understand why nobody
> wants to really ditch the atomic helpers)

But most drivers do overwrite parts of it, which is kinda my point:
Everyone still keeps using at least some parts of atomic, and benefitting
from the opinionated guidelines those have.

> > And I think bridge helpers probably also need fairly opinioated, simply to
> > make sure that all the bridge drivers work together in a coherent fashion.
> > If we allow too much flexibility everyone bends the rules a bit, and
> > nothing fits.
> 
> I agree with you on that.
> 
> > Wrt your question: One option would be to do the same thing like shared
> > interrupt line handlers. As soon as the first interrupt handler says "I'
> > ve handled this one" we stop processing. But that might lead to more
> > confusion about who's responsible for an interrupt.
> 
> I don't think that's a good idea, as more than one consumer may need to
> process the event. A real life example with two consumers would be a CEC
> controller part of a bridge needing to get informed about HDMI
> plug/unplug to set the CEC address in the device (this notification is
> handled through the bridge notification operation), and the display
> driver needing to report HPD to the DRM core.
> 
> As I'm still not sure why you think I should replace the existing
> implementation with your above proposal, so I'll keep the existing code
> for the v2 that I will post soon until we complete this discussion.
> 
> To hopefully help with the discussion, I would like to repeat my main
> argument : moving the dispatching of the notification to
> drm_bridge_hpd_notify() sets the order in which components (bridges,
> encoders, drivers) are notified in stone, while keeping it in the
> drm_bridge_connector helper allows drivers to not use the helper and
> come up with a different implementation that fits their needs better.

That "set things in stone" is actually what I want. Well, not stone, but
really clear semantics. You're essentially creating a notifier, except
there's only every one notified entity at most. Ime bad things happen with
notifiers, it's unavoidable.

So maybe what we need instead is a bridge_hpd_process callback (in
mode_config.helpers or wherever, or on the encoder, dunno), with the above
default implementation. But you can then overwrite it.

Or another option would be that at least on DT platforms, DT gets to spec
the entire hpd routing.

I just fear that if we let bridge drivers all manage this themselves we'll
end up with a formadible mess of slight incompatibilities.
-Daniel

> 
> > > I would like to remind everybody that this series isn't the last I will
> > > ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> > > open to suggestions, and can address problems on top of these patches,
> > > provided obviously that this series doesn't go in the wrong direction.
> > > I'm of course also willing to rework this series, but given the amount
> > > of work we have in the drm_bridge realm, I can't fix everything in one
> > > go :-)
> > > 
> > >>>>>> - it resembles hardware wires :)
> > >>>>>
> > >>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> > >>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> > >>>>> interested in that hpd singal. This includes:
> > >>>>> - Other bridges, e.g. if they provide CEC support.
> > >>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> > >>>>> - Overall driver, so it can update the modes/connector status and send the
> > >>>>>   uevent to the driver.
> > >>>>> - Overall display pipeline for this specific bridge, maybe you need to
> > >>>>>   shut down/re-enable the pipe because $reasons.
> > >>>>>  
> > >>>>> That's at least my understanding from lots of chats with Laurent about
> > >>>>> what he wants to do here.
> > > 
> > > That's correct, and that's what I was trying to implement :-) The
> > > notification, in this patch series, goes from the producer bridge to a
> > > central place (namely the connector, with a helper implementation
> > > available as part of this series, but custom implementations in display
> > > drivers are fine if needed) that then dispatches the notification to all
> > > bridges (through the .lost_hotplug() operation, which we could replace
> > > by an .hpd_notify() operation) for the first two purposes listed above,
> > > and then to the overall driver. The only thing I don't support yet is
> > > dispatching to the display pipeline (item 4 in the list above) as I had
> > > no need for that, and didn't want to develop an API with no user. This
> > > would however not be difficult to do when needed, the need is taken into
> > > account in the proposed implementation.
> > > 
> > >>>> I do not know the full picture, but the solution where particular bridge
> > >>>> notifies everything unconditionally seems to me much less flexible.
> > >>>>
> > >>>> If HPD signals is received by the consumer, if there are no obstacles it
> > >>>> can propagate it further, upstream bridge/encoder or to drm core - it
> > >>>> will mimic your scenario.
> > >>>>
> > >>>> But there are also other scenarios where bridge does not want to
> > >>>> propagate signal, because for example:
> > >>>>
> > >>>> - it wants to wait for other sinks to wake up,
> > >>>
> > >>> The other sink can just do that in their hpd callback.
> > >>>
> > >>>> - it propagates HPD signal via hardware wire,
> > >>>
> > >>> Again, the other sink can just not listen to sw hpd in that case, and use
> > >>> the wire/hw hpd interrupt.
> > >> 
> > >> If it should ignore HPD, why it should receive it at all - it is
> > >> unnecessary noise. And I am afraid with more complicated pipelines it
> > >> will be impossible for particular component (bridge/encoder/whatever) to
> > >> distinguish if HPD notification which came from non-directly connected
> > >> component should be ignored or not.
> > >> 
> > >>>> - first it wants to verify if the sink is valid/compatible/authorized
> > >>>> device.
> > >>>
> > >>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> > >>> board?
> > >> 
> > >> Bridge can have external connectors, and the user can connect there
> > >> anything.
> > >> 
> > >>>> In general HPD is input signal for notify of state changes on particular
> > >>>> bus, in case of typical video bridge on its output video bus.
> > >>>>
> > >>>> In case of bridges they have also input video buses, and they can send
> > >>>> HPD signal via this bus, but this is indeed different HPD signal, even
> > >>>> if for most cases they looks similar.
> > >>>
> > >>> Ah, I think this is a problem we will eventually have. But it's not
> > >>> something we're currently solving here at all I think.
> > >> 
> > >> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> > >> line, so this is not something from far future. And I guess with HPD
> > >> broadcasting it could be racy/error prone, for example EDID reading can
> > >> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> > >> controller via hw wires also).
> > >> 
> > >>>>>> And regarding implementation:
> > >>>>>>
> > >>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> > >>>>>>
> > >>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> > >>>>>>
> > >>>>>> Your proposition is more straightforward, but if we want to notify only
> > >>>>>> source we should locate it by parsing notification chain (what about
> > >>>>>> unchained bridges), or store pointer somewhere during attachment.
> > >>>>>>
> > >>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> > >>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> > >>>>>
> > >>>>> Uh I think we're not talking about the same thing really. My understanding
> > >>>>> is that this callback is if someone (outside of this bridge) is interested
> > >>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> > >>>>> listener.
> > >>>>
> > >>>> Do we have real life examples?
> > >>>>
> > >>>> I want to distinguish two situations:
> > >>>>
> > >>>> - another device wants to know if input bus of the bridge has changed state,
> > >>>>
> > >>>> - another device wants to know if output bus of the bridge has changed
> > >>>> state.
> > >>>
> > >>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> > >>> bridges can exchange state and information about each another. hpd is
> > >>> about the physical world, i.e. "is there a cable plugged into the port
> > >>> I'm driving?". We're not going to use fake hpd to update bridge state and
> > >>> fun stuff like that, we have the atomic_check machinery for this.
> > >> 
> > >> My question was if we have real examples that upstream device requires
> > >> knowledge about state of output line of the bridge?
> > >> 
> > >> To be more precise, we have following display pipeline:
> > >> 
> > >> A-->B-->C
> > >> 
> > >> And C sends HPD to B (ie signal that state of line between B and C
> > >> changed). Does A really wants to know this information? or it should
> > >> just need to know if state of line A-->B changed?
> > > 
> > > There's one real life example, where A is an HDMI encoder, B is an HDMI
> > > ESD protector and level shifter, and C is the physical HDMI connector.
> > > When the HDMI cable is unplugged, the CEC controller part of A needs to
> > > be notified in order to reset the CEC state machine. One could however
> > > argue that in that case the A-B link state changes too, but the
> > > important part is that HPD detection is not performed by A, while A
> > > needs to be informed of lost hotplug.
> > > 
> > >>>>> You seem to have some other idea here.
> > >>>>>
> > >>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>>>> +}
> > >>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > >>>>>>>>>>> +
> > >>>>>>>>>>>  #ifdef CONFIG_OF
> > >>>>>>>>>>>  /**
> > >>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > >>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > >>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> > >>>>>>>>>>> --- a/include/drm/drm_bridge.h
> > >>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> > >>>>>>>>>>> @@ -23,8 +23,9 @@
> > >>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> > >>>>>>>>>>>  #define __DRM_BRIDGE_H__
> > >>>>>>>>>>>  
> > >>>>>>>>>>> -#include <linux/list.h>
> > >>>>>>>>>>>  #include <linux/ctype.h>
> > >>>>>>>>>>> +#include <linux/list.h>
> > >>>>>>>>>>> +#include <linux/mutex.h>
> > >>>>>>>>>>>  #include <drm/drm_mode_object.h>
> > >>>>>>>>>>>  #include <drm/drm_modes.h>
> > >>>>>>>>>>>  
> > >>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >>>>>>>>>>>  	 */
> > >>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >>>>>>>>>>>  				    struct drm_atomic_state *state);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @detect:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> > >>>>>>>>>>> +	 * considered as always having a component attached to its output.
> > >>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> > >>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * RETURNS:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @get_modes:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> > >>>>>>>>>>> +	 * with drm_mode_probed_add().
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> > >>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> > >>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> > >>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * RETURNS:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> > >>>>>>>>>>> +			 struct drm_connector *connector);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @get_edid:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> > >>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> > >>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> > >>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> > >>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> > >>>>>>>>>>> +	 * output.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * RETURNS:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > >>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> > >>>>>>>>>>> +	 * the returned edid structure with kfree().
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > >>>>>>>>>>> +				 struct drm_connector *connector);
> > >>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> > >>>>>>>>>> presence of another one?
> > >>>>>>>>>>
> > >>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> > >>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> > >>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > >>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> > >>>>>>>>> and connector->edid correctly.
> > >>>>>>>>>
> > >>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> > >>>>>>>>> in the connector is up-to-date? With your current callback design that's
> > >>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > >>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> > >>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> > >>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> > >>>>>>>>> correctly updated.
> > >>>>>>>>>
> > >>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> > >>>>>>>>> edid, which is not so awesome :-)
> > >>>>>>>>>
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @lost_hotplug:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> > >>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> > >>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> > >>>>>>>>>>> +	 * HDMI bridges.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @hpd_enable:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> > >>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > >>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> > >>>>>>>>>>> +	 * @hpd_disable.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> > >>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> > >>>>>>>>>>> +
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @hpd_disable:
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> > >>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > >>>>>>>>>>> +	 * connection status occurs.
> > >>>>>>>>>>> +	 *
> > >>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> > >>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >>>>>>>>>>>  };
> > >>>>>>>>>>>  
> > >>>>>>>>>>>  /**
> > >>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >>>>>>>>>>>  	bool dual_link;
> > >>>>>>>>>>>  };
> > >>>>>>>>>>>  
> > >>>>>>>>>>> +/**
> > >>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > >>>>>>>>>>> + */
> > >>>>>>>>>>> +enum drm_bridge_ops {
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > >>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> > >>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > >>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> > >>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > >>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> > >>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> > >>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > >>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> > >>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > >>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> > >>>>>>>>>>> +};
> > >>>>>>>>>>> +
> > >>>>>>>>>>>  /**
> > >>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> > >>>>>>>>>>>   */
> > >>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> > >>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> > >>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> > >>>>>>>>>>>  	void *driver_private;
> > >>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> > >>>>>>>>>>> +	enum drm_bridge_ops ops;
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> > >>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > >>>>>>>>>>> +	 * identifies the type of connected display.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	int type;
> > >>>>>>>>>>> +	/** private: */
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	struct mutex hpd_mutex;
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> > >>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > >>>>>>>>>>> +	/**
> > >>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > >>>>>>>>>>> +	 * @hpd_cb.
> > >>>>>>>>>>> +	 */
> > >>>>>>>>>>> +	void *hpd_data;
> > >>>>>>>>>>>  };
> > >>>>>>>>>>>  
> > >>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> > >>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>>  			      struct drm_atomic_state *state);
> > >>>>>>>>>>>  
> > >>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>>>> +			   void *data);
> > >>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > >>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>>>> +			   enum drm_connector_status status);
> > >>>>>>>>>>> +
> > >>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >>>>>>>>>>>  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-19  2:37                       ` Laurent Pinchart
@ 2019-08-20  8:41                         ` Daniel Vetter
  0 siblings, 0 replies; 166+ messages in thread
From: Daniel Vetter @ 2019-08-20  8:41 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On Mon, Aug 19, 2019 at 05:37:36AM +0300, Laurent Pinchart wrote:
> Hi Daniel,
> 
> On Sat, Aug 17, 2019 at 03:14:56AM +0300, Laurent Pinchart wrote:
> > On Sat, Aug 17, 2019 at 02:30:08AM +0300, Laurent Pinchart wrote:
> > > On Wed, Aug 14, 2019 at 07:02:29PM +0200, Daniel Vetter wrote:
> > >> On Wed, Aug 14, 2019 at 04:30:57PM +0300, Laurent Pinchart wrote:
> > >>> On Wed, Aug 14, 2019 at 03:03:29PM +0200, Daniel Vetter wrote:
> > >>>> On Thu, Aug 08, 2019 at 09:36:31PM +0300, Laurent Pinchart wrote:
> > >>>>> On Thu, Aug 08, 2019 at 09:19:48PM +0300, Laurent Pinchart wrote:
> > >>>>>> On Thu, Jul 11, 2019 at 09:35:48AM +0200, Daniel Vetter wrote:
> > >>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> > >>>>>>>> Hi Laurent,
> > >>>>>>>> 
> > >>>>>>>> I like the approach, current practice when almost every bridge should
> > >>>>>>>> optionally implement connector, or alternatively downstream bridge or
> > >>>>>>>> panel is very painful.
> > >>>>>>> 
> > >>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> > >>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> > >>>>>>> it's all used in the end. I probably should go and do that, at least to
> > >>>>>>> get a feeling for what your hpd_cb usually does.
> > >>>>>>> 
> > >>>>>>>> More comments inlined.
> > >>>>>>>> 
> > >>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> > >>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> > >>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> > >>>>>>>>> data:
> > >>>>>>>>>
> > >>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> > >>>>>>>>>   retrieval operations
> > >>>>>>>>> - Bitmask of supported operations
> > >>>>>>>> 
> > >>>>>>>> 
> > >>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> > >>>>>>>> operation's callback?
> > >>>>>>> 
> > >>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> > >>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> > >>>>>>> add, generally good excuse to not have to think through the design between
> > >>>>>>> different parts of drivers - "just" add another flag.
> > >>>>>> 
> > >>>>>> The reason is that a bridge may support an operation (as in implemented
> > >>>>>> in the bridge hardware), but that operation may not be supported on a
> > >>>>>> particular board. For instance an HDMI encoder may support reading EDID
> > >>>>>> when the DDC lines are connected to the encoder, but a board may connect
> > >>>>>> the DDC lines to an I2C port of the SoC. We thus need to decouple
> > >>>>>> if a particular instance of the device supports the operation (exposed
> > >>>>>> by the ops flags) from the function pointers.
> > >>>>>> 
> > >>>>>> We could of course allocate the drm_bridge_funcs structure dynamically
> > >>>>>> for each bridge instance, and fill it with function pointers manually,
> > >>>>>> leaving the unused ops always NULL, but that would require making the
> > >>>>>> structure writable, which is considered a security issue. That's why I
> > >>>>>> decided to keep the drm_bridge_funcs structure as a global static const
> > >>>>>> structure, and add an ops bitmask.
> > >>>>>> 
> > >>>>>>>>> - Bridge output type
> > >>>>>>>>>
> > >>>>>>>>> Add and document these.
> > >>>>>>>>>
> > >>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> > >>>>>>>>> notification in a way that is as transparent as possible for the
> > >>>>>>>>> bridges.
> > >>>>>>>> 
> > >>>>>>>> Documentation of new opses does not explain how it should cooperate with
> > >>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> > >>>>>>>> right? More comments about it later.
> > >>>>>> 
> > >>>>>> No, the whole point is that they should not be chained at all. A bridge
> > >>>>>> does not have to propagate, for instance, .get_edid() to the next
> > >>>>>> bridge. That's one of the core design principles in this series, I want
> > >>>>>> to keep the bridges as simple as possible, and move the complexity of
> > >>>>>> the boilerplate code that is currently copied all around to helpers. See
> > >>>>>> patch "drm: Add helper to create a connector for a chain of bridges" for
> > >>>>>> more information about how this is used, with a helper that delegates
> > >>>>>> the connector operations to the correct bridge in the chain based on the
> > >>>>>> ops reported by each bridge.
> > >>>>>> 
> > >>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >>>>>>>>> ---
> > >>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> > >>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> > >>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> > >>>>>>>>>
> > >>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> > >>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> > >>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> > >>>>>>>>>   */
> > >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> > >>>>>>>>>  {
> > >>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> > >>>>>>>>> +
> > >>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> > >>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> > >>>>>>>>>  	mutex_lock(&bridge_lock);
> > >>>>>>>>>  	list_del_init(&bridge->list);
> > >>>>>>>>>  	mutex_unlock(&bridge_lock);
> > >>>>>>>>> +
> > >>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> > >>>>>>>>>  }
> > >>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> > >>>>>>>>>  
> > >>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>  }
> > >>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> > >>>>>>>>>  
> > >>>>>>>>> +/**
> > >>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> > >>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>> + * @cb: hot-plug detection callback
> > >>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> > >>>>>>>>> + *
> > >>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> > >>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> > >>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> > >>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> > >>>>>>>>> + *
> > >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>> + *
> > >>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> > >>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> > >>>>>>>>> + * the bridge.
> > >>>>>>>>> + */
> > >>>>>>>> 
> > >>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> > >>>>>>>> bridge attach:
> > >>>>>>>> 
> > >>>>>>>> bridge->hpd_cb = cb;
> > >>>>>>>> 
> > >>>>>>>> bridge->hpd_data = data;
> > >>>>>>>> 
> > >>>>>>>> ret = drm_bridge_attach(...);
> > >>>>>>> 
> > >>>>>>> Yeah I like this more. The other problem here is, what if you need more
> > >>>>>>> than 1 callback registers on the same bridge hdp signal?
> > >>>>>> 
> > >>>>>> That's why I decided to hide hide HPD through helpers,
> > >>>>>> drm_bridge_hpd_enable() and drm_bridge_hpd_disable() on the listener
> > >>>>>> side, and drm_bridge_hpd_notify() on the event reporter side. While the
> > >>>>>> current implementation is limited to a single listener, only the helpers
> > >>>>>> would need to be changed to extend that to multiple listeners.
> > >>>>>> 
> > >>>>>> Note that the .hpd_enable() and .hpd_disable() operations also allow the
> > >>>>>> bridge to disable HPD detection when not used. Doing so keeps the bridge
> > >>>>>> simple, it only needs to care about reporting HPD events when they're
> > >>>>>> enabled, without caring who (if anyone) is listening, and gets clear
> > >>>>>> instructions on whether to enable or disable the HPD hardware (in case
> > >>>>>> it can be disabled).
> > >>>>>> 
> > >>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> > >>>>>>>> without big sacrifices.
> > >>>>>>>> 
> > >>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> > >>>>>>>> notifies about sink status change, how it translates to this cb?
> > >>>>>> 
> > >>>>>> This is something this series doesn't implement. I don't think it would
> > >>>>>> be a big deal, but my knowledge of HPD (especially for DisplayPort) ends
> > >>>>>> here. If you can elaborate on what would be needed, I can implement
> > >>>>>> that.
> > >>>>>> 
> > >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>> +			   void *data)
> > >>>>>>>>> +{
> > >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> > >>>>>>>>> +		return;
> > >>>>>>>>> +
> > >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>> +
> > >>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> > >>>>>>>>> +		goto unlock;
> > >>>>>>>>> +
> > >>>>>>>>> +	bridge->hpd_cb = cb;
> > >>>>>>>>> +	bridge->hpd_data = data;
> > >>>>>>>>> +
> > >>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +unlock:
> > >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>> +}
> > >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> > >>>>>>>>> +
> > >>>>>>>>> +/**
> > >>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> > >>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>> + *
> > >>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> > >>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> > >>>>>>>>> + * function returns the callback will not be called by the bridge when an
> > >>>>>>>>> + * output status change occurs.
> > >>>>>>>>> + *
> > >>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> > >>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> > >>>>>>>>> + */
> > >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> > >>>>>>>>> +{
> > >>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> > >>>>>>>>> +		return;
> > >>>>>>>>> +
> > >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	bridge->hpd_cb = NULL;
> > >>>>>>>>> +	bridge->hpd_data = NULL;
> > >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>> +}
> > >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> > >>>>>>>>> +
> > >>>>>>>>> +/**
> > >>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> > >>>>>>>>> + * @bridge: bridge control structure
> > >>>>>>>>> + * @status: output connection status
> > >>>>>>>>> + *
> > >>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> > >>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> > >>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> > >>>>>>>>> + *
> > >>>>>>>>> + * This function shall be called in a context that can sleep.
> > >>>>>>>>> + */
> > >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>> +			   enum drm_connector_status status)
> > >>>>>>>>> +{
> > >>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> > >>>>>>>>> +	if (bridge->hpd_cb)
> > >>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> > >>>>>>> 
> > >>>>>>> So this isn't quite what I had in mind. Instead something like this:
> > >>>>>>> 
> > >>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> > >>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> > >>>>>>> 		if (tmp_bridge == bridge)
> > >>>>>>> 			continue;
> > >>>>>>> 		if (bridge->hpd_notify);
> > >>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> > >>>>>>> 	}
> > >>>>>>> 
> > >>>>>>> 	encoder = encoder_for_bridge(bridge);
> > >>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> > >>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> > >>>>>>> 
> > >>>>>>> 	dev = bridge->dev
> > >>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> > >>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> > >>>>>>> 
> > >>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> > >>>>>>> hpd they want/need.
> > >>>>>> 
> > >>>>>> I'll reply to this further down the mail thread, to address additional
> > >>>>>> comments.
> > >>>>>> 
> > >>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> > >>>>>>>>> +}
> > >>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> > >>>>>>>>> +
> > >>>>>>>>>  #ifdef CONFIG_OF
> > >>>>>>>>>  /**
> > >>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> > >>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> > >>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> > >>>>>>>>> --- a/include/drm/drm_bridge.h
> > >>>>>>>>> +++ b/include/drm/drm_bridge.h
> > >>>>>>>>> @@ -23,8 +23,9 @@
> > >>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> > >>>>>>>>>  #define __DRM_BRIDGE_H__
> > >>>>>>>>>  
> > >>>>>>>>> -#include <linux/list.h>
> > >>>>>>>>>  #include <linux/ctype.h>
> > >>>>>>>>> +#include <linux/list.h>
> > >>>>>>>>> +#include <linux/mutex.h>
> > >>>>>>>>>  #include <drm/drm_mode_object.h>
> > >>>>>>>>>  #include <drm/drm_modes.h>
> > >>>>>>>>>  
> > >>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> > >>>>>>>>>  	 */
> > >>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> > >>>>>>>>>  				    struct drm_atomic_state *state);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @detect:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Check if anything is attached to the bridge output.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> > >>>>>>>>> +	 * considered as always having a component attached to its output.
> > >>>>>>>>> +	 * Bridges that implement this callback shall set the
> > >>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * RETURNS:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @get_modes:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> > >>>>>>>>> +	 * with drm_mode_probed_add().
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> > >>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> > >>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> > >>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * RETURNS:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> > >>>>>>>>> +			 struct drm_connector *connector);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @get_edid:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> > >>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> > >>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> > >>>>>>>>> +	 * the @get_modes callback unimplemented.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * The caller of this operation shall first verify the output
> > >>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> > >>>>>>>>> +	 * output.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> > >>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * RETURNS:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> > >>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> > >>>>>>>>> +	 * the returned edid structure with kfree().
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> > >>>>>>>>> +				 struct drm_connector *connector);
> > >>>>>>>> 
> > >>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> > >>>>>>>> presence of another one?
> > >>>>>>>> 
> > >>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> > >>>>>>>> some helper function to .get_modes cb, which will do the same?
> > >>>>>>> 
> > >>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> > >>>>>>> case, and require that if it has an edid it must fill out connector->info
> > >>>>>>> and connector->edid correctly.
> > >>>>>> 
> > >>>>>> I think that's doable, I'll have a look.
> > >>>>> 
> > >>>>> So I had a look, and while this is doable, it would essentially mean
> > >>>>> that all bridges that retrieve modes from EDID would have to roll out
> > >>>>> their own version of the following code:
> > >>>>> 
> > >>>>> static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
> > >>>>> 					       struct drm_bridge *bridge)
> > >>>>> {
> > >>>>> 	enum drm_connector_status status;
> > >>>>> 	struct edid *edid;
> > >>>>> 	int n;
> > >>>>> 
> > >>>>> 	status = drm_bridge_connector_detect(connector, false);
> > >>>>> 	if (status != connector_status_connected)
> > >>>>> 		goto no_edid;
> > >>>>> 
> > >>>>> 	edid = bridge->funcs->get_edid(bridge, connector);
> > >>>>> 	if (!edid || !drm_edid_is_valid(edid)) {
> > >>>>> 		kfree(edid);
> > >>>>> 		goto no_edid;
> > >>>>> 	}
> > >>>>> 
> > >>>>> 	drm_connector_update_edid_property(connector, edid);
> > >>>>> 	n = drm_add_edid_modes(connector, edid);
> > >>>>> 
> > >>>>> 	kfree(edid);
> > >>>>> 	return n;
> > >>>>> 
> > >>>>> no_edid:
> > >>>>> 	drm_connector_update_edid_property(connector, NULL);
> > >>>>> 	return 0;
> > >>>>> }
> > >>>>> 
> > >>>>> Is this desired ?
> > >>>> 
> > >>>> We store the edid, and we store a lot of decoded information in
> > >>>> drm_connector->display_info. Can't they just look there? Re-fetching the
> > >>>> edid definitely sounds like the wrong thing to do.
> > >>>> 
> > >>>> We might run into some ordering issue here I guess with hotplugs and who's
> > >>>> fetching the edid and everything like that.
> > >>> 
> > >>> That's exactly what I was about to answer after reading your first
> > >>> paragraph :-) I believe caching EDID is a good idea, but my familiarity
> > >>> with hotplug-related issues is limited to a handful of systems, and I'm
> > >>> sure I'm missing some common problems. If you can tell me how you think
> > >>> this should be done, I can give it a try.
> > >> 
> > >> I think all you need to do is make sure that when handling a hpd, the edid
> > >> is fetched first. Before other parts of the bridge try to reconfigure
> > >> themselves ...
> > >> 
> > >>>> Also maybe I'm missing the point here, and thinking too much of
> > >>>> ->get_modes on the connector. But then I'm not clear on why the bridge
> > >>>> needs the connector, and why it instead can't just return the edid it can
> > >>>> read and let the caller/core figure out everything else?
> > >>> 
> > >>> That's exactly what the .get_edid() operation that you asked me to
> > >>> remove did... :-) You didn't like the fact that it duplicated the
> > >>> .get_modes() logic. Should I add it back, and clearly document
> > >>> .get_modes() as a fallback used only when the connector doesn't use EDID
> > >>> ?
> > >> 
> > >> I guess I'm making a bit a fool of myself here. What I meant is that if we
> > >> do want to keep ->get_edid, then why do you need to pass the connector?
> > >> Just return the edid blob, and let the caller parse it, and stuff all
> > >> relevant data into drm_connector. Just thinking along the lines of your
> > >> goal of making the bridge drivers as dumb as possible.
> > >> 
> > >> Same thing for ->get_modes would be neat too, but we don't have a nice
> > >> datastructure for this. We'd need to pass both a list_head and a pointer
> > >> to the drm_display_info I think.
> > >> 
> > >> That would decouple bridges even more from connector, which I think is
> > >> somewhere on your goal list ...
> > > 
> > > So I had a look at that. We would need to remove the connector argument
> > > from drm_do_get_edid(). The connector currently stores a few fields
> > > related to EDID parsing:
> > > 
> > >         /**
> > >          * @null_edid_counter: track sinks that give us all zeros for the EDID.
> > >          * Needed to workaround some HW bugs where we get all 0s
> > >          */
> > >         int null_edid_counter;
> > > 
> > >         /** @bad_edid_counter: track sinks that give us an EDID with invalid checksum */
> > >         unsigned bad_edid_counter;
> > > 
> > >         /**
> > >          * @edid_corrupt: Indicates whether the last read EDID was corrupt. Used
> > >          * in Displayport compliance testing - Displayport Link CTS Core 1.2
> > >          * rev1.1 4.2.2.6
> > >          */
> > >         bool edid_corrupt;
> > > 
> > > We would need to decouple that from drm_connector. One option would be
> > > to create a new drm_edid structure that would store those three fields,
> > > as well as a struct edid, and return it from drm_do_get_edid() instead
> > > of the raw struct edid. Would you prefer a different solution ? Do you
> > > think that's a prerequisite to for this patch ?
> > 
> > The more I look at EDID parsing, the more it feels that we should
> > redesign the whole HPD and EDID get handling, and the more it becomes
> > out of scope for this patch series :-S EDID retrieval and extraction of
> > information from EDID is intertwined, for instance drm_get_edid()
> > performs the following:
> > 
> > struct edid *drm_get_edid(struct drm_connector *connector,
> > 			  struct i2c_adapter *adapter)
> > {
> > 	struct edid *edid;
> > 
> > 	if (connector->force == DRM_FORCE_OFF)
> > 		return NULL;
> > 
> > 	if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
> > 		return NULL;
> > 
> > 	edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
> > 	if (edid)
> > 		drm_get_displayid(connector, edid);
> > 	return edid;
> > }
> > 
> > I don't think the drm_get_displayid() call belongs there. Moving it to
> > the numerous callers of drm_get_edid() doesn't seem a good idea. Ideally
> > it should be done at the same time as populating the modes from EDID,
> > but I'm pretty sure that would break things, with not all EDID retrieval
> > resulting in modes updates.
> > 
> > A few drivers call drm_do_get_edid() directly, in order to provide a
> > custom EDID block read function, and they skip connector->force handling
> > and drm_get_displayid() as a result. In most case I assume that's a bug
> > that just went unnoticed.
> > 
> > Decoupling EDID read from drm_connector for bridges in a proper way seem
> > like a huge piece of work to me, and I really can't make it a
> > prerequisite for this patch.

Well, that's what you're doing with the bridge get_edid stuff. Higher
levels need to wrap that up in the drm_do_get_edid. And yes we have a mess
here :-/

> I still gave it a try, and it resulted in
> 
> 	git://linuxtv.org/pinchartl/media.git omapdrm/edid
> 
> Could you have a look at the last five patches in the branch ?
> 
> drm/edid: Reorganise the DisplayID parsing code
> drm/edid: Move functions to avoid forward declaration
> drm/edid: Move DisplayID tile parsing to drm_connector.c
> drm/edid: Honour connector->force in drm_do_get_edid()
> [WIP] drm/edid: Decouple EDID retrieval from connector
> 
> While the first four patches probably make sense and could be merged
> independently of the last one, it's really the fifth patch that makes
> decoupling of .get_edid() from drm_connector possible. And it's the
> patch I'm the least happy with in this whole series :-S As written in
> it's commit message, is it worth it ?

Hm ... the trouble I'm seeing is that if we give the bridges the
connector, your nice attempt at pulling connectors out of bridges will be
fooled.

Otoh, this is indeed a bit a mess, and not clean at all either.

I'd say up to you which of these wind-mills you'd prefer to tilt, and in
which order :-) Just make sure the kerneldoc for the hooks explains what's
going on, and what people should or should not do with the connector
argument.
-Daniel

> 
> > >>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> > >>>>>>> in the connector is up-to-date? With your current callback design that's
> > >>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> > >>>>>>> should guarantee that it'll first walk the connectors to update status and
> > >>>>>>> edid/mode list for the final drm_connector. And then instead of just
> > >>>>>>> passing the simple "status", it'll pass the connector, with everything
> > >>>>>>> correctly updated.
> > >>>>>>> 
> > >>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> > >>>>>>> edid, which is not so awesome :-)
> > >>>>>> 
> > >>>>>> With the current design there's a single listener, so it's not a big
> > >>>>>> deal :-) Furthermore, the listener is the helper that creates a
> > >>>>>> connector on top of a chain of bridges, so it's a pretty good place to
> > >>>>>> handle this. See the call to drm_kms_helper_hotplug_event() in
> > >>>>>> drm_bridge_connector_hpd_cb().
> > >>>>>> 
> > >>>>>> I'm all for reworking HPD and mode fetching, but I think it's a bit too
> > >>>>>> big of a requirement as a prerequisite for this series (or as part of
> > >>>>>> this series). We have hardware that can report HPD with various level of
> > >>>>>> details (from "something happened on a connector" to "this particular
> > >>>>>> event happened on this particular connector"), and we channel that
> > >>>>>> through helpers such as drm_kms_helper_hotplug_event() that lose the
> > >>>>>> details and go through a heavy mechanism to refetch everything. I
> > >>>>>> understand this is needed in many cases, but I think there's room for
> > >>>>>> improvement. This series, in my opinion, doesn't go in the wrong
> > >>>>>> direction in that regard, as it eventually calls
> > >>>>>> drm_kms_helper_hotplug_event(), so I think improvements would make sense
> > >>>>>> on top of it. I'm even willing to work on this, provided I get feedback
> > >>>>>> on what is desired.
> > >>>>>> 
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @lost_hotplug:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Notify the bridge of display disconnection.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> > >>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> > >>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> > >>>>>>>>> +	 * HDMI bridges.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_enable:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> > >>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> > >>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> > >>>>>>>>> +	 * @hpd_disable.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> > >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> > >>>>>>>>> +
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_disable:
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> > >>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> > >>>>>>>>> +	 * connection status occurs.
> > >>>>>>>>> +	 *
> > >>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> > >>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> > >>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> > >>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> > >>>>>>>>>  };
> > >>>>>>>>>  
> > >>>>>>>>>  /**
> > >>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> > >>>>>>>>>  	bool dual_link;
> > >>>>>>>>>  };
> > >>>>>>>>>  
> > >>>>>>>>> +/**
> > >>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> > >>>>>>>>> + */
> > >>>>>>>>> +enum drm_bridge_ops {
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> > >>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> > >>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> > >>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> > >>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> > >>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> > >>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> > >>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> > >>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> > >>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> > >>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> > >>>>>>>>> +};
> > >>>>>>>>> +
> > >>>>>>>>>  /**
> > >>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> > >>>>>>>>>   */
> > >>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> > >>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> > >>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> > >>>>>>>>>  	void *driver_private;
> > >>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> > >>>>>>>>> +	enum drm_bridge_ops ops;
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @type: Type of the connection at the bridge output
> > >>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> > >>>>>>>>> +	 * identifies the type of connected display.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	int type;
> > >>>>>>>>> +	/** private: */
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	struct mutex hpd_mutex;
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> > >>>>>>>>> +	 * drm_bridge_hpd_enable().
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> > >>>>>>>>> +	/**
> > >>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> > >>>>>>>>> +	 * @hpd_cb.
> > >>>>>>>>> +	 */
> > >>>>>>>>> +	void *hpd_data;
> > >>>>>>>>>  };
> > >>>>>>>>>  
> > >>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> > >>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> > >>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> > >>>>>>>>>  			      struct drm_atomic_state *state);
> > >>>>>>>>>  
> > >>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> > >>>>>>>>> +			   void (*cb)(void *data,
> > >>>>>>>>> +				      enum drm_connector_status status),
> > >>>>>>>>> +			   void *data);
> > >>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> > >>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> > >>>>>>>>> +			   enum drm_connector_status status);
> > >>>>>>>>> +
> > >>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> > >>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> > >>>>>>>>>  					u32 connector_type);
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-19 22:45                                     ` Laurent Pinchart
@ 2019-08-22 12:17                                       ` Andrzej Hajda
  2019-08-26 16:27                                         ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-08-22 12:17 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On 20.08.2019 00:45, Laurent Pinchart wrote:
> Hi Andrzej,
>
> On Mon, Aug 19, 2019 at 10:38:35AM +0200, Andrzej Hajda wrote:
>> On 14.08.2019 14:40, Daniel Vetter wrote:
>>> On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
>>>> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
>>>>> On 11.08.2019 00:43, Laurent Pinchart wrote:
>>>>>> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
>>>>>>> On 08.08.2019 21:32, Laurent Pinchart wrote:
>>>>>>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
>>>>>>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
>>>>>>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
>>>>>>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
>>>>>>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>>>>>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>>>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>>>>>> Hi Laurent,
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
>>>>>>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>>>>>>>>>>>>> panel is very painful.
>>>>>>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>>>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>>>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>>>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> More comments inlined.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>>>>>>>>>>>>> data:
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>>>>>>>>>>>>   retrieval operations
>>>>>>>>>>>>>>>>>> - Bitmask of supported operations
>>>>>>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>>>>>>>>>>>>> operation's callback?
>>>>>>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>>>>>>>>>>>>> add, generally good excuse to not have to think through the design between
>>>>>>>>>>>>>>>> different parts of drivers - "just" add another flag.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> - Bridge output type
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Add and document these.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>>>>>>>>>>>>> bridges.
>>>>>>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>>>>>>>>>>>>> right? More comments about it later.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>>>>>>>>>>>>   */
>>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>>>  {
>>>>>>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>  }
>>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>  }
>>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>>>> + * @cb: hot-plug detection callback
>>>>>>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>>>>>>>>>>>>> + * the bridge.
>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>>>>>>>>>>>>> bridge attach:
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> bridge->hpd_cb = cb;
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> bridge->hpd_data = data;
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> ret = drm_bridge_attach(...);
>>>>>>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>>>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>>>>>>>>>>>>> without big sacrifices.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>>>>>>> +			   void *data)
>>>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>>>>>>>>>>>>> +		goto unlock;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>>>>>>>>>>>>> +	bridge->hpd_data = data;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +unlock:
>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>>>>>>>>>>>>> + * output status change occurs.
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>>>> + * @status: output connection status
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status)
>>>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +	if (bridge->hpd_cb)
>>>>>>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>>>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>>>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>>>>>>>>>>>>> 		if (tmp_bridge == bridge)
>>>>>>>>>>>>>>>> 			continue;
>>>>>>>>>>>>>>>> 		if (bridge->hpd_notify);
>>>>>>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>>>>>>>>>>>>> 	}
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>>>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>>>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> 	dev = bridge->dev
>>>>>>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>>>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>>>>>>>>>>>>> hpd they want/need.
>>>>>>>>>>>>>>> As I understand you want to notify every member of the pipeline.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I think it should be enough to notify only the source, and then source
>>>>>>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> It looks more generic for me.
>>>>>>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>>>>>>>>>>>>> the one from Laurent? Kinda confused here.
>>>>>>>>>>>>> Regarding general idea:
>>>>>>>>>>>>>
>>>>>>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
>>>>>>>>>>>>> source.
>>>>>>>>>>>>>
>>>>>>>>>>>>> 2. Your is to notify all other bridges and encoder.
>>>>>>>>>>>>>
>>>>>>>>>>>>> And I prefer 1st approach, why:
>>>>>>>>>>>>>
>>>>>>>>>>>>> - the source can decide if/when and to who propagate the signal,
>>>>>>>>>>>>>
>>>>>>>>>>>>> - is more generic, for example if bridge send signal to two
>>>>>>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
>>>>>>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
>>>>>>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
>>>>>>>>>>> If there will be two consumers, there will be two bridge attachments,
>>>>>>>>>>> thus there will be two notifications, it should work.
>>>>>>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
>>>>>>>>>> callback is registered on the produce bridge, not on the consumer bridge
>>>>>>>>>> (or I'm totallly misreading what Laurent does here).
>>>>>>>>> I have assumed that if devices exposes two hardware sink interfaces it
>>>>>>>>> will expose two separate bridges - of course it will not work with
>>>>>>>>> "bridge chaining" thing, but this is a different story.
>>>>>>>> Daniel is right that the current implementation only allows one
>>>>>>>> consumer. This is however not a limitation of the API, but of its
>>>>>>>> implementation, as I only needed a single consumer. The helpers in this
>>>>>>>> series ensure that neither the consumer nor the producer poke in the
>>>>>>>> drm_bridge structure to call back to the HPD handler:
>>>>>>>>
>>>>>>>> - The consumer calls drm_bridge_hpd_enable() and
>>>>>>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
>>>>>>>>   behaviour if desired without changes to the consumer.
>>>>>>>>
>>>>>>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
>>>>>>>>   which could also easily accommodate reference-counting in the drm
>>>>>>>>   bridge core without changes to the producer.
>>>>>>>>
>>>>>>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
>>>>>>>>   easily be extended to support multiple consumers without changes to
>>>>>>>>   the producer.
>>>>>>>>
>>>>>>>> This is actually my second version of the HPD mechanism. The first
>>>>>>>> version was never posted, poked into drm_bridge, and required the
>>>>>>>> producer to be aware of the callbacks. After discussing this privately
>>>>>>>> with Daniel, I came up with the implementation in this series that,
>>>>>>>> while not supporting multiple consumers now, makes it easy to extend
>>>>>>>> later without minimal effort.
>>>>>>>>
>>>>>>>> Daniel's proposed implementation above looks reasonable to me, provided
>>>>>>>> we can iterate over the bridges in an order that don't depend on the
>>>>>>>> position of the producer in the chain (should be easy to solve by
>>>>>>>> starting at the encoder for instance). It however looks a bit like a
>>>>>>>> midlayer to me :-) That's why I have a similar implementation in the
>>>>>>>> connector-bridge helper, which could be extended to call
>>>>>>>> encoder->helper_private->bridge_hpd_notify() and
>>>>>>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
>>>>>>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
>>>>>>>> drm_bridge_hpd_notify() would on the other hand set the notification
>>>>>>>> sequence towards the encoder and driver in stone. Daniel, do you think
>>>>>>>> that would be better ?
>>>>>>>>
>>>>>>>> I would like to remind everybody that this series isn't the last I will
>>>>>>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
>>>>>>>> open to suggestions, and can address problems on top of these patches,
>>>>>>>> provided obviously that this series doesn't go in the wrong direction.
>>>>>>>> I'm of course also willing to rework this series, but given the amount
>>>>>>>> of work we have in the drm_bridge realm, I can't fix everything in one
>>>>>>>> go :-)
>>>>>>>>
>>>>>>>>>>>>> - it resembles hardware wires :)
>>>>>>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
>>>>>>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
>>>>>>>>>>>> interested in that hpd singal. This includes:
>>>>>>>>>>>> - Other bridges, e.g. if they provide CEC support.
>>>>>>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
>>>>>>>>>>>> - Overall driver, so it can update the modes/connector status and send the
>>>>>>>>>>>>   uevent to the driver.
>>>>>>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
>>>>>>>>>>>>   shut down/re-enable the pipe because $reasons.
>>>>>>>>>>>>  
>>>>>>>>>>>> That's at least my understanding from lots of chats with Laurent about
>>>>>>>>>>>> what he wants to do here.
>>>>>>>> That's correct, and that's what I was trying to implement :-) The
>>>>>>>> notification, in this patch series, goes from the producer bridge to a
>>>>>>>> central place (namely the connector, with a helper implementation
>>>>>>>> available as part of this series, but custom implementations in display
>>>>>>>> drivers are fine if needed) that then dispatches the notification to all
>>>>>>>> bridges (through the .lost_hotplug() operation, which we could replace
>>>>>>>> by an .hpd_notify() operation) for the first two purposes listed above,
>>>>>>>> and then to the overall driver. The only thing I don't support yet is
>>>>>>>> dispatching to the display pipeline (item 4 in the list above) as I had
>>>>>>>> no need for that, and didn't want to develop an API with no user. This
>>>>>>>> would however not be difficult to do when needed, the need is taken into
>>>>>>>> account in the proposed implementation.
>>>>>>>>
>>>>>>>>>>> I do not know the full picture, but the solution where particular bridge
>>>>>>>>>>> notifies everything unconditionally seems to me much less flexible.
>>>>>>>>>>>
>>>>>>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
>>>>>>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
>>>>>>>>>>> will mimic your scenario.
>>>>>>>>>>>
>>>>>>>>>>> But there are also other scenarios where bridge does not want to
>>>>>>>>>>> propagate signal, because for example:
>>>>>>>>>>>
>>>>>>>>>>> - it wants to wait for other sinks to wake up,
>>>>>>>>>>>
>>>>>>>>>> The other sink can just do that in their hpd callback.
>>>>>>>>>>
>>>>>>>>>>> - it propagates HPD signal via hardware wire,
>>>>>>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
>>>>>>>>>> the wire/hw hpd interrupt.
>>>>>>>>>>
>>>>>>>>> If it should ignore HPD, why it should receive it at all - it is
>>>>>>>>> unnecessary noise. And I am afraid with more complicated pipelines it
>>>>>>>>> will be impossible for particular component (bridge/encoder/whatever) to
>>>>>>>>> distinguish if HPD notification which came from non-directly connected
>>>>>>>>> component should be ignored or not.
>>>>>>>>>
>>>>>>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
>>>>>>>>>>> device.
>>>>>>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
>>>>>>>>>> board?
>>>>>>>>> Bridge can have external connectors, and the user can connect there
>>>>>>>>> anything.
>>>>>>>>>
>>>>>>>>>>> In general HPD is input signal for notify of state changes on particular
>>>>>>>>>>> bus, in case of typical video bridge on its output video bus.
>>>>>>>>>>>
>>>>>>>>>>> In case of bridges they have also input video buses, and they can send
>>>>>>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
>>>>>>>>>>> if for most cases they looks similar.
>>>>>>>>>> Ah, I think this is a problem we will eventually have. But it's not
>>>>>>>>>> something we're currently solving here at all I think.
>>>>>>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
>>>>>>>>> line, so this is not something from far future. And I guess with HPD
>>>>>>>>> broadcasting it could be racy/error prone, for example EDID reading can
>>>>>>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
>>>>>>>>> controller via hw wires also).
>>>>>>>>>
>>>>>>>>>>>>> And regarding implementation:
>>>>>>>>>>>>>
>>>>>>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>>>>>>>>>>>>
>>>>>>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Your proposition is more straightforward, but if we want to notify only
>>>>>>>>>>>>> source we should locate it by parsing notification chain (what about
>>>>>>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
>>>>>>>>>>>>>
>>>>>>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
>>>>>>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
>>>>>>>>>>>> Uh I think we're not talking about the same thing really. My understanding
>>>>>>>>>>>> is that this callback is if someone (outside of this bridge) is interested
>>>>>>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
>>>>>>>>>>>> listener.
>>>>>>>>>>> Do we have real life examples?
>>>>>>>>>>>
>>>>>>>>>>> I want to distinguish two situations:
>>>>>>>>>>>
>>>>>>>>>>> - another device wants to know if input bus of the bridge has changed state,
>>>>>>>>>>>
>>>>>>>>>>> - another device wants to know if output bus of the bridge has changed
>>>>>>>>>>> state.
>>>>>>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
>>>>>>>>>> bridges can exchange state and information about each another. hpd is
>>>>>>>>>> about the physical world, i.e. "is there a cable plugged into the port
>>>>>>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
>>>>>>>>>> fun stuff like that, we have the atomic_check machinery for this.
>>>>>>>>> My question was if we have real examples that upstream device requires
>>>>>>>>> knowledge about state of output line of the bridge?
>>>>>>>>>
>>>>>>>>> To be more precise, we have following display pipeline:
>>>>>>>>>
>>>>>>>>> A-->B-->C
>>>>>>>>>
>>>>>>>>> And C sends HPD to B (ie signal that state of line between B and C
>>>>>>>>> changed). Does A really wants to know this information? or it should
>>>>>>>>> just need to know if state of line A-->B changed?
>>>>>>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
>>>>>>>> ESD protector and level shifter, and C is the physical HDMI connector.
>>>>>>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
>>>>>>>> be notified in order to reset the CEC state machine. One could however
>>>>>>>> argue that in that case the A-B link state changes too, but the
>>>>>>>> important part is that HPD detection is not performed by A, while A
>>>>>>>> needs to be informed of lost hotplug.
>>>>>>> I have no full picture but I guess in this case C sends HPD to B using
>>>>>>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
>>>>>>> say that B does not participate in HPD transmission/forwarding,
>>>>>> No, in this case A doesn't receive any hardware HPD signal, it requires
>>>>>> HPD notification through software.
>>>>>>
>>>>>>> some shifters with 'advanced power saving' can even perform wake-up of
>>>>>>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
>>>>>>> to A is indeed different than HPD sent from C to B.
>>>>>>>
>>>>>>> Btw, with the above logic of propagation of HPD callback (proposed by
>>>>>>> Daniel) I guess it will work this way:
>>>>>>>
>>>>>>> - A will receive HPD signal via HW,
>>>>>>>
>>>>>>> - then B and C will receive HPD callback via framework.
>>>>>>>
>>>>>>> Am I right?
>>>>>> It's the other way around.
>>>>>>
>>>>>> In this case the HPD signal from the connector (C) is routed to an input
>>>>>> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
>>>>>> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
>>>>>> IRQ and receive the hardware HPD notification. The driver for the HDMI
>>>>>> encoder (A) needs to receive HPD notification in software, through the
>>>>>> framework.
>>>>> If this is GPIO I wonder why do not query this gpio by encoder directly,
>>>>> rules of ownership of such gpios seems to be grey area, so in such case
>>>>> I would advise to put it in the driver who really needs it.
>>>>>
>>>>> This way it will be much simpler.
>>>> First to fall, multiple drivers may need to be informed of HPD events
>>>> coming from a GPIO, so we would need to duplicate it in multiple places,
>>>> and I don't think the GPIO framework allows acquiring a GPIO multiple
>>>> times.
>>>>
>>>> Then, the GPIO is described in DT, and DT doesn't care about which
>>>> driver needs HPD events. DT specifies the GPIO in the node of the device
>>>> it belongs to, this is defined in DT bindings, and must be the same on
>>>> all boards, while depending on the board different devices may need to
>>>> be informed of HPD events.
>>>>
>>>> For those two reasons HPD GPIO handling and consumption of HPD events
>>>> can't always be grouped in the same driver.
>>>>
>>>>> Going back to HPD notifications, as I said earlier broadcasting HPD
>>>>> notification unconditionally to every member of the chain with hope that
>>>>> the member will be able to filter-out undesired notification seems to me
>>>>> incorrect - maybe it can solve some problems but is not flexible enough
>>>>> to be usable in other scenarios.
>>>>>
>>>>> If my arguments do not convince you please just continue with your
>>>>> ideas, we can always add NO_HPD_BROADCAST somewhere :)
>>>> :-) I would like to understand the problems you're referring to though,
>>>> and hopefully solve them. If you could describe one of the scenarios
>>>> where you think this mechanism wouldn't be usable that would help. In
>>>> the meantime I will post a new version of the series with these
>>>> operations kept as-is to get the rest of the patches reviewed.
>>> See my little thing about midlayers, I think midlayers with lots of flags
>>> for everything aren't a good idea. They should be more opinionated about
>>> how things work.
>>>
>>> So if there's a case where this broadcasting of various things doesn't
>>> work, let's dig into it.
>>> -Daniel
>> OK, almost real life example:
>>
>> A -> B -> C
>>
>> A - RGB/HDMI converter,
>>
>> B - HDMI/MHL converter,
>>
>> C - uUSB controller (MUIC).
>>
>>
>> C - detects presence of MHL sink and routes MHL lines to B in such case.
>>
>> B - has no hardware logic to detect HPD, but it's firmware can read EDID
>> from downstream component via HW lines and it has hardware lines to
>> upstream component to send EDID,
>>
>> A - can read EDID from B via hardware lines, but does not have hardware HPD.
> It probably doesn't matter much for the overall discussion, but out of
> curiosity, does B have a CBUS interface towards C and a DDC (I2C)
> interface towards A ?


Yes, but C (MUIC) is not MHL aware, beside initial 1K resistance
detection on ID pin, AFAIK.


>  And does A read the EDID on DDC and expose it
> towards the SoC through a custom protocol (for instance as the ADV7511
> does), or does it forward the DDC lines to the SoC ?
>
>> So how it should work (according to specification):
>>
>> 1. C detects MHL sink.
>>
>> 2. C switches his mux to route lines to B.
>>
>> 3. C sends HPD notification to B.
>>
>> 4. B powers on, its firmware reads EDID from downstream lines (possibly
>> adjusting it) and makes it available to upstream component A.
>>
>> 5. B sends HPD notification to A.
>>
>>
>> I do not know how it could work with HPD broadcasting.
>>
>> I guess C should be HPD provider, but in case of HPD broadcasting A and
>> B would receive notification in the same time, as a result A would start
>> reading EDID too early - fail.
> That's an interesting case indeed. Now I understand what you meant
> earlier.
>
> The HPD notification from C to B is purely internal, and should not be
> visible from a DRM/KMS point of view. It just happens that this hardware
> setup has a more complex HPD sequence that requires software
> intervention in the middle of the sequence. As such, if we forget about
> this patch series for a minute, C would need a custom API to send MHL
> notification to B, and the HPD for DRM/KMS would be notified by B, right
> ?


I just want to convince you that maybe all HPD signals
(hardware/software) are purely internal (ie they should be handled only
by upstream devices), and the hotplug event should be sent to
userspace/drm_core only when WHOLE pipeline is ready to query modes
(i2c/sideband channels/whatever is functional).


>
> I think it's possible to handle both the MHL notification and the
> user-visible HPD notification through the same bridge API, provided that
> we offer a way for a bridge to block forwarding of the HPD notification.
> This will also require calling the HPD notifiers on bridges in the sink
> to source order. Both are doable, the bridge HPD notifier operation
> could return a bool that blocks propagation of the notification. Would
> that work for you ?


It could work, in this case.

But it will still have problems with non-linear pipelines - where stream
is split to two or more bridges/panels.


Regards

Andrzej

 

>
>>>>>>>>>>>> You seem to have some other idea here.
>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>  #ifdef CONFIG_OF
>>>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>> -#include <linux/list.h>
>>>>>>>>>>>>>>>>>>  #include <linux/ctype.h>
>>>>>>>>>>>>>>>>>> +#include <linux/list.h>
>>>>>>>>>>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>>>>>>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>>>>>>>>>>>>  	 */
>>>>>>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @detect:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @get_modes:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>> +			 struct drm_connector *connector);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @get_edid:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>>>>>>>>>>>>> +	 * output.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>> +				 struct drm_connector *connector);
>>>>>>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>>>>>>>>>>>>> presence of another one?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>>>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>>>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>>>>>>>>>>>>> and connector->edid correctly.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>>>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
>>>>>>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>>>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>>>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>>>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>>>>>>>>>>>>> correctly updated.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>>>>>>>>>>>>> edid, which is not so awesome :-)
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @lost_hotplug:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>>>>>>>>>>>>> +	 * HDMI bridges.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @hpd_enable:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>>>>>>>>>>>>> +	 * @hpd_disable.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @hpd_disable:
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>>>>>>>>>>>>> +	 * connection status occurs.
>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>>>>>>>>>>>>  	bool dual_link;
>>>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>> +enum drm_bridge_ops {
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>>>>>>>>>>>>   */
>>>>>>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>>>>>>>>>>>>  	void *driver_private;
>>>>>>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>>>>>>>>>>>>> +	 * identifies the type of connected display.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	int type;
>>>>>>>>>>>>>>>>>> +	/** private: */
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	struct mutex hpd_mutex;
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>>>>>>>>>>>>> +	 * @hpd_cb.
>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>> +	void *hpd_data;
>>>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>>>>>>> +			   void *data);
>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>>>>>>>>>>>>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-20  8:30                             ` Daniel Vetter
@ 2019-08-26 15:57                               ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-26 15:57 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Daniel,

On Tue, Aug 20, 2019 at 10:30:46AM +0200, Daniel Vetter wrote:
> On Tue, Aug 20, 2019 at 01:32:09AM +0300, Laurent Pinchart wrote:
> > On Wed, Aug 14, 2019 at 02:35:10PM +0200, Daniel Vetter wrote:
> >> On Thu, Aug 08, 2019 at 10:32:14PM +0300, Laurent Pinchart wrote:
> >>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >>>> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>> Hi Laurent,
> >>>>>>>>>>>>
> >>>>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>>>> panel is very painful.
> >>>>>>>>>>>
> >>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>>>
> >>>>>>>>>>>> More comments inlined.
> >>>>>>>>>>>>
> >>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>>>> data:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>>>   retrieval operations
> >>>>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>>>
> >>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>>>> operation's callback?
> >>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>>>
> >>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>>>
> >>>>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> Add and document these.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>>>> bridges.
> >>>>>>>>>>>>
> >>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>>>
> >>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>>>> ---
> >>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>>>   */
> >>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>>>  {
> >>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>>>  }
> >>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  }
> >>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>>>> + * the bridge.
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>>>> bridge attach:
> >>>>>>>>>>>>
> >>>>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>>>
> >>>>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>>>
> >>>>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>>>
> >>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>>>
> >>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>>>> without big sacrifices.
> >>>>>>>>>>>>
> >>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>>>
> >>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>> +			   void *data)
> >>>>>>>>>>>>> +{
> >>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +unlock:
> >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +}
> >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>>>> +{
> >>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +}
> >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>>>> + *
> >>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>>>> +{
> >>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>>>
> >>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>>>
> >>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>>>> 			continue;
> >>>>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>>>> 	}
> >>>>>>>>>>>
> >>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>>>
> >>>>>>>>>>> 	dev = bridge->dev
> >>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>>>
> >>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>>>> hpd they want/need.
> >>>>>>>>>>
> >>>>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>>>
> >>>>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>>>
> >>>>>>>>>> It looks more generic for me.
> >>>>>>>>>
> >>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>>>
> >>>>>>>> Regarding general idea:
> >>>>>>>>
> >>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>>>> source.
> >>>>>>>>
> >>>>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>>>
> >>>>>>>> And I prefer 1st approach, why:
> >>>>>>>>
> >>>>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>>>
> >>>>>>>> - is more generic, for example if bridge send signal to two
> >>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>>>
> >>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>>>
> >>>>>> If there will be two consumers, there will be two bridge attachments,
> >>>>>> thus there will be two notifications, it should work.
> >>>>>
> >>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>>>> callback is registered on the produce bridge, not on the consumer bridge
> >>>>> (or I'm totallly misreading what Laurent does here).
> >>>> 
> >>>> I have assumed that if devices exposes two hardware sink interfaces it
> >>>> will expose two separate bridges - of course it will not work with
> >>>> "bridge chaining" thing, but this is a different story.
> >>> 
> >>> Daniel is right that the current implementation only allows one
> >>> consumer. This is however not a limitation of the API, but of its
> >>> implementation, as I only needed a single consumer. The helpers in this
> >>> series ensure that neither the consumer nor the producer poke in the
> >>> drm_bridge structure to call back to the HPD handler:
> >>> 
> >>> - The consumer calls drm_bridge_hpd_enable() and
> >>>   drm_bridge_hpd_disable(), which could offer a reference-counted
> >>>   behaviour if desired without changes to the consumer.
> >>> 
> >>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >>>   which could also easily accommodate reference-counting in the drm
> >>>   bridge core without changes to the producer.
> >>> 
> >>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >>>   easily be extended to support multiple consumers without changes to
> >>>   the producer.
> >>> 
> >>> This is actually my second version of the HPD mechanism. The first
> >>> version was never posted, poked into drm_bridge, and required the
> >>> producer to be aware of the callbacks. After discussing this privately
> >>> with Daniel, I came up with the implementation in this series that,
> >>> while not supporting multiple consumers now, makes it easy to extend
> >>> later without minimal effort.
> >>> 
> >>> Daniel's proposed implementation above looks reasonable to me, provided
> >>> we can iterate over the bridges in an order that don't depend on the
> >>> position of the producer in the chain (should be easy to solve by
> >>> starting at the encoder for instance). It however looks a bit like a
> >>> midlayer to me :-) That's why I have a similar implementation in the
> >>> connector-bridge helper, which could be extended to call
> >>> encoder->helper_private->bridge_hpd_notify() and
> >>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> >>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> >>> drm_bridge_hpd_notify() would on the other hand set the notification
> >>> sequence towards the encoder and driver in stone. Daniel, do you think
> >>> that would be better ?
> >> 
> >> So the difference between the midlayer and the helper is that the helper
> >> can be ignored. Which the above still can:
> >> 
> >> - producer can choose to not call that function
> >> - consumer can choose not to have the callback
> >> 
> >> Now great helpers allow you to ignore only parts of them, so that you can
> >> mix&match. Which again I think with the bridge stuff we're discussing here
> >> is assured.
> > 
> > That's a bit difficult for the first part, as if the producer doesn't
> > notify of HPD events, consumers won't be able to get them :-) The second
> > part, consumers not having the callback, is already supported.
> > 
> >> So the final bit is how opinionated a helper can be, and imo it can be
> >> very opinionated and strict and inflexible. That means it won't be useful
> >> for every possible case, but those can be handled by simply not using the
> >> helper (or that part of the helpers). Examples
> >> 
> >> - simple display pipe is very opinionated, but trades that in for being
> >>   very useful for really simple displays
> >> 
> >> - similar with atomic helpers, there's a very strong suggestion that "if
> >>   it doesn't fit, write your own commit_tail()"
> > 
> > (On a side note, doing so is quite complex, and I understand why nobody
> > wants to really ditch the atomic helpers)
> 
> But most drivers do overwrite parts of it, which is kinda my point:
> Everyone still keeps using at least some parts of atomic, and benefitting
> from the opinionated guidelines those have.

Ah, yes, reusing the existing helpers to tweak the behaviour is indeed
fine (although based on my experience with the rcar-du driver, it often
trades one set of issues for another, but that may be because more
helpers would be needed for different classes of devices).

> >> And I think bridge helpers probably also need fairly opinioated, simply to
> >> make sure that all the bridge drivers work together in a coherent fashion.
> >> If we allow too much flexibility everyone bends the rules a bit, and
> >> nothing fits.
> > 
> > I agree with you on that.
> > 
> >> Wrt your question: One option would be to do the same thing like shared
> >> interrupt line handlers. As soon as the first interrupt handler says "I'
> >> ve handled this one" we stop processing. But that might lead to more
> >> confusion about who's responsible for an interrupt.
> > 
> > I don't think that's a good idea, as more than one consumer may need to
> > process the event. A real life example with two consumers would be a CEC
> > controller part of a bridge needing to get informed about HDMI
> > plug/unplug to set the CEC address in the device (this notification is
> > handled through the bridge notification operation), and the display
> > driver needing to report HPD to the DRM core.
> > 
> > As I'm still not sure why you think I should replace the existing
> > implementation with your above proposal, so I'll keep the existing code
> > for the v2 that I will post soon until we complete this discussion.
> > 
> > To hopefully help with the discussion, I would like to repeat my main
> > argument : moving the dispatching of the notification to
> > drm_bridge_hpd_notify() sets the order in which components (bridges,
> > encoders, drivers) are notified in stone, while keeping it in the
> > drm_bridge_connector helper allows drivers to not use the helper and
> > come up with a different implementation that fits their needs better.
> 
> That "set things in stone" is actually what I want. Well, not stone, but
> really clear semantics. You're essentially creating a notifier, except
> there's only every one notified entity at most. Ime bad things happen with
> notifiers, it's unavoidable.
> 
> So maybe what we need instead is a bridge_hpd_process callback (in
> mode_config.helpers or wherever, or on the encoder, dunno), with the above
> default implementation. But you can then overwrite it.

That sounds reasonable, but I think I'd make it a connector callback
instead, as it's really about a chain of bridges + connector. What do
you think ?

> Or another option would be that at least on DT platforms, DT gets to spec
> the entire hpd routing.
> 
> I just fear that if we let bridge drivers all manage this themselves we'll
> end up with a formadible mess of slight incompatibilities.

Bridges drivers don't manage this themselves in the current proposal :-)
The behaviour is implemented in a helper, which registers itself as a
listener for HPD. Drivers don't have to use that helper.

I can replace that registration mechanism with a connector call already
if you prefer.

> >>> I would like to remind everybody that this series isn't the last I will
> >>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> >>> open to suggestions, and can address problems on top of these patches,
> >>> provided obviously that this series doesn't go in the wrong direction.
> >>> I'm of course also willing to rework this series, but given the amount
> >>> of work we have in the drm_bridge realm, I can't fix everything in one
> >>> go :-)
> >>> 
> >>>>>>>> - it resembles hardware wires :)
> >>>>>>>
> >>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>>>> interested in that hpd singal. This includes:
> >>>>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>>>   uevent to the driver.
> >>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>>>  
> >>>>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>>>> what he wants to do here.
> >>> 
> >>> That's correct, and that's what I was trying to implement :-) The
> >>> notification, in this patch series, goes from the producer bridge to a
> >>> central place (namely the connector, with a helper implementation
> >>> available as part of this series, but custom implementations in display
> >>> drivers are fine if needed) that then dispatches the notification to all
> >>> bridges (through the .lost_hotplug() operation, which we could replace
> >>> by an .hpd_notify() operation) for the first two purposes listed above,
> >>> and then to the overall driver. The only thing I don't support yet is
> >>> dispatching to the display pipeline (item 4 in the list above) as I had
> >>> no need for that, and didn't want to develop an API with no user. This
> >>> would however not be difficult to do when needed, the need is taken into
> >>> account in the proposed implementation.
> >>> 
> >>>>>> I do not know the full picture, but the solution where particular bridge
> >>>>>> notifies everything unconditionally seems to me much less flexible.
> >>>>>>
> >>>>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>>>> will mimic your scenario.
> >>>>>>
> >>>>>> But there are also other scenarios where bridge does not want to
> >>>>>> propagate signal, because for example:
> >>>>>>
> >>>>>> - it wants to wait for other sinks to wake up,
> >>>>>
> >>>>> The other sink can just do that in their hpd callback.
> >>>>>
> >>>>>> - it propagates HPD signal via hardware wire,
> >>>>>
> >>>>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>>>> the wire/hw hpd interrupt.
> >>>> 
> >>>> If it should ignore HPD, why it should receive it at all - it is
> >>>> unnecessary noise. And I am afraid with more complicated pipelines it
> >>>> will be impossible for particular component (bridge/encoder/whatever) to
> >>>> distinguish if HPD notification which came from non-directly connected
> >>>> component should be ignored or not.
> >>>> 
> >>>>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>>>> device.
> >>>>>
> >>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>>>> board?
> >>>> 
> >>>> Bridge can have external connectors, and the user can connect there
> >>>> anything.
> >>>> 
> >>>>>> In general HPD is input signal for notify of state changes on particular
> >>>>>> bus, in case of typical video bridge on its output video bus.
> >>>>>>
> >>>>>> In case of bridges they have also input video buses, and they can send
> >>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>>>> if for most cases they looks similar.
> >>>>>
> >>>>> Ah, I think this is a problem we will eventually have. But it's not
> >>>>> something we're currently solving here at all I think.
> >>>> 
> >>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >>>> line, so this is not something from far future. And I guess with HPD
> >>>> broadcasting it could be racy/error prone, for example EDID reading can
> >>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >>>> controller via hw wires also).
> >>>> 
> >>>>>>>> And regarding implementation:
> >>>>>>>>
> >>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>>>
> >>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>>>
> >>>>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>>>> source we should locate it by parsing notification chain (what about
> >>>>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>>>
> >>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>>>
> >>>>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>>>> listener.
> >>>>>>
> >>>>>> Do we have real life examples?
> >>>>>>
> >>>>>> I want to distinguish two situations:
> >>>>>>
> >>>>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>>>
> >>>>>> - another device wants to know if output bus of the bridge has changed
> >>>>>> state.
> >>>>>
> >>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>>>> bridges can exchange state and information about each another. hpd is
> >>>>> about the physical world, i.e. "is there a cable plugged into the port
> >>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>>>> fun stuff like that, we have the atomic_check machinery for this.
> >>>> 
> >>>> My question was if we have real examples that upstream device requires
> >>>> knowledge about state of output line of the bridge?
> >>>> 
> >>>> To be more precise, we have following display pipeline:
> >>>> 
> >>>> A-->B-->C
> >>>> 
> >>>> And C sends HPD to B (ie signal that state of line between B and C
> >>>> changed). Does A really wants to know this information? or it should
> >>>> just need to know if state of line A-->B changed?
> >>> 
> >>> There's one real life example, where A is an HDMI encoder, B is an HDMI
> >>> ESD protector and level shifter, and C is the physical HDMI connector.
> >>> When the HDMI cable is unplugged, the CEC controller part of A needs to
> >>> be notified in order to reset the CEC state machine. One could however
> >>> argue that in that case the A-B link state changes too, but the
> >>> important part is that HPD detection is not performed by A, while A
> >>> needs to be informed of lost hotplug.
> >>> 
> >>>>>>> You seem to have some other idea here.
> >>>>>>>
> >>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>> +}
> >>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>>>  	 */
> >>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>>>> +	 * output.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>>>> presence of another one?
> >>>>>>>>>>>>
> >>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>>>> and connector->edid correctly.
> >>>>>>>>>>>
> >>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>>>> correctly updated.
> >>>>>>>>>>>
> >>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>>>
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>  };
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>>  /**
> >>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>>>  };
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> +/**
> >>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>>>> + */
> >>>>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>>>> +};
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>>>   */
> >>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	int type;
> >>>>>>>>>>>>> +	/** private: */
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>>>  };
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>>>  
> >>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>> +			   void *data);
> >>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>>>> +
> >>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-22 12:17                                       ` Andrzej Hajda
@ 2019-08-26 16:27                                         ` Laurent Pinchart
  2019-08-29 16:48                                           ` Andrzej Hajda
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-26 16:27 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Thu, Aug 22, 2019 at 02:17:16PM +0200, Andrzej Hajda wrote:
> On 20.08.2019 00:45, Laurent Pinchart wrote:
> > On Mon, Aug 19, 2019 at 10:38:35AM +0200, Andrzej Hajda wrote:
> >> On 14.08.2019 14:40, Daniel Vetter wrote:
> >>> On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
> >>>> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
> >>>>> On 11.08.2019 00:43, Laurent Pinchart wrote:
> >>>>>> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
> >>>>>>> On 08.08.2019 21:32, Laurent Pinchart wrote:
> >>>>>>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >>>>>>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>>>>>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>>>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>>>>>> Hi Laurent,
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>>>>>>>>> panel is very painful.
> >>>>>>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> More comments inlined.
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>>>>>>>>> data:
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>>>>>>>>   retrieval operations
> >>>>>>>>>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>>>>>>>>> operation's callback?
> >>>>>>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Add and document these.
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>>>>>>>>> bridges.
> >>>>>>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>>>>>>>>> ---
> >>>>>>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>>>>>>>>   */
> >>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>>>  {
> >>>>>>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>  }
> >>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>  }
> >>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>>>>>>>>> + * the bridge.
> >>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>>>>>>>>> bridge attach:
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>>>>>>>>> without big sacrifices.
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>>>>>>> +			   void *data)
> >>>>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +unlock:
> >>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>>>>>>>>> 			continue;
> >>>>>>>>>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>>>>>>>>> 	}
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> 	dev = bridge->dev
> >>>>>>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>>>>>>>>> hpd they want/need.
> >>>>>>>>>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> It looks more generic for me.
> >>>>>>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>>>>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>>>>>>>> Regarding general idea:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>>>>>>>>> source.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> And I prefer 1st approach, why:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - is more generic, for example if bridge send signal to two
> >>>>>>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>>>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>>>>>>>> If there will be two consumers, there will be two bridge attachments,
> >>>>>>>>>>> thus there will be two notifications, it should work.
> >>>>>>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>>>>>>>>> callback is registered on the produce bridge, not on the consumer bridge
> >>>>>>>>>> (or I'm totallly misreading what Laurent does here).
> >>>>>>>>> I have assumed that if devices exposes two hardware sink interfaces it
> >>>>>>>>> will expose two separate bridges - of course it will not work with
> >>>>>>>>> "bridge chaining" thing, but this is a different story.
> >>>>>>>> Daniel is right that the current implementation only allows one
> >>>>>>>> consumer. This is however not a limitation of the API, but of its
> >>>>>>>> implementation, as I only needed a single consumer. The helpers in this
> >>>>>>>> series ensure that neither the consumer nor the producer poke in the
> >>>>>>>> drm_bridge structure to call back to the HPD handler:
> >>>>>>>>
> >>>>>>>> - The consumer calls drm_bridge_hpd_enable() and
> >>>>>>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
> >>>>>>>>   behaviour if desired without changes to the consumer.
> >>>>>>>>
> >>>>>>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >>>>>>>>   which could also easily accommodate reference-counting in the drm
> >>>>>>>>   bridge core without changes to the producer.
> >>>>>>>>
> >>>>>>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >>>>>>>>   easily be extended to support multiple consumers without changes to
> >>>>>>>>   the producer.
> >>>>>>>>
> >>>>>>>> This is actually my second version of the HPD mechanism. The first
> >>>>>>>> version was never posted, poked into drm_bridge, and required the
> >>>>>>>> producer to be aware of the callbacks. After discussing this privately
> >>>>>>>> with Daniel, I came up with the implementation in this series that,
> >>>>>>>> while not supporting multiple consumers now, makes it easy to extend
> >>>>>>>> later without minimal effort.
> >>>>>>>>
> >>>>>>>> Daniel's proposed implementation above looks reasonable to me, provided
> >>>>>>>> we can iterate over the bridges in an order that don't depend on the
> >>>>>>>> position of the producer in the chain (should be easy to solve by
> >>>>>>>> starting at the encoder for instance). It however looks a bit like a
> >>>>>>>> midlayer to me :-) That's why I have a similar implementation in the
> >>>>>>>> connector-bridge helper, which could be extended to call
> >>>>>>>> encoder->helper_private->bridge_hpd_notify() and
> >>>>>>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> >>>>>>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> >>>>>>>> drm_bridge_hpd_notify() would on the other hand set the notification
> >>>>>>>> sequence towards the encoder and driver in stone. Daniel, do you think
> >>>>>>>> that would be better ?
> >>>>>>>>
> >>>>>>>> I would like to remind everybody that this series isn't the last I will
> >>>>>>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> >>>>>>>> open to suggestions, and can address problems on top of these patches,
> >>>>>>>> provided obviously that this series doesn't go in the wrong direction.
> >>>>>>>> I'm of course also willing to rework this series, but given the amount
> >>>>>>>> of work we have in the drm_bridge realm, I can't fix everything in one
> >>>>>>>> go :-)
> >>>>>>>>
> >>>>>>>>>>>>> - it resembles hardware wires :)
> >>>>>>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>>>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>>>>>>>>> interested in that hpd singal. This includes:
> >>>>>>>>>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>>>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>>>>>>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>>>>>>>>   uevent to the driver.
> >>>>>>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>>>>>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>>>>>>>>  
> >>>>>>>>>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>>>>>>>>> what he wants to do here.
> >>>>>>>> That's correct, and that's what I was trying to implement :-) The
> >>>>>>>> notification, in this patch series, goes from the producer bridge to a
> >>>>>>>> central place (namely the connector, with a helper implementation
> >>>>>>>> available as part of this series, but custom implementations in display
> >>>>>>>> drivers are fine if needed) that then dispatches the notification to all
> >>>>>>>> bridges (through the .lost_hotplug() operation, which we could replace
> >>>>>>>> by an .hpd_notify() operation) for the first two purposes listed above,
> >>>>>>>> and then to the overall driver. The only thing I don't support yet is
> >>>>>>>> dispatching to the display pipeline (item 4 in the list above) as I had
> >>>>>>>> no need for that, and didn't want to develop an API with no user. This
> >>>>>>>> would however not be difficult to do when needed, the need is taken into
> >>>>>>>> account in the proposed implementation.
> >>>>>>>>
> >>>>>>>>>>> I do not know the full picture, but the solution where particular bridge
> >>>>>>>>>>> notifies everything unconditionally seems to me much less flexible.
> >>>>>>>>>>>
> >>>>>>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>>>>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>>>>>>>>> will mimic your scenario.
> >>>>>>>>>>>
> >>>>>>>>>>> But there are also other scenarios where bridge does not want to
> >>>>>>>>>>> propagate signal, because for example:
> >>>>>>>>>>>
> >>>>>>>>>>> - it wants to wait for other sinks to wake up,
> >>>>>>>>>>>
> >>>>>>>>>> The other sink can just do that in their hpd callback.
> >>>>>>>>>>
> >>>>>>>>>>> - it propagates HPD signal via hardware wire,
> >>>>>>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>>>>>>>>> the wire/hw hpd interrupt.
> >>>>>>>>>>
> >>>>>>>>> If it should ignore HPD, why it should receive it at all - it is
> >>>>>>>>> unnecessary noise. And I am afraid with more complicated pipelines it
> >>>>>>>>> will be impossible for particular component (bridge/encoder/whatever) to
> >>>>>>>>> distinguish if HPD notification which came from non-directly connected
> >>>>>>>>> component should be ignored or not.
> >>>>>>>>>
> >>>>>>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>>>>>>>>> device.
> >>>>>>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>>>>>>>>> board?
> >>>>>>>>> Bridge can have external connectors, and the user can connect there
> >>>>>>>>> anything.
> >>>>>>>>>
> >>>>>>>>>>> In general HPD is input signal for notify of state changes on particular
> >>>>>>>>>>> bus, in case of typical video bridge on its output video bus.
> >>>>>>>>>>>
> >>>>>>>>>>> In case of bridges they have also input video buses, and they can send
> >>>>>>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>>>>>>>>> if for most cases they looks similar.
> >>>>>>>>>> Ah, I think this is a problem we will eventually have. But it's not
> >>>>>>>>>> something we're currently solving here at all I think.
> >>>>>>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >>>>>>>>> line, so this is not something from far future. And I guess with HPD
> >>>>>>>>> broadcasting it could be racy/error prone, for example EDID reading can
> >>>>>>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >>>>>>>>> controller via hw wires also).
> >>>>>>>>>
> >>>>>>>>>>>>> And regarding implementation:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>>>>>>>>> source we should locate it by parsing notification chain (what about
> >>>>>>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>>>>>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>>>>>>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>>>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>>>>>>>>> listener.
> >>>>>>>>>>> Do we have real life examples?
> >>>>>>>>>>>
> >>>>>>>>>>> I want to distinguish two situations:
> >>>>>>>>>>>
> >>>>>>>>>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>>>>>>>>
> >>>>>>>>>>> - another device wants to know if output bus of the bridge has changed
> >>>>>>>>>>> state.
> >>>>>>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>>>>>>>>> bridges can exchange state and information about each another. hpd is
> >>>>>>>>>> about the physical world, i.e. "is there a cable plugged into the port
> >>>>>>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>>>>>>>>> fun stuff like that, we have the atomic_check machinery for this.
> >>>>>>>>> My question was if we have real examples that upstream device requires
> >>>>>>>>> knowledge about state of output line of the bridge?
> >>>>>>>>>
> >>>>>>>>> To be more precise, we have following display pipeline:
> >>>>>>>>>
> >>>>>>>>> A-->B-->C
> >>>>>>>>>
> >>>>>>>>> And C sends HPD to B (ie signal that state of line between B and C
> >>>>>>>>> changed). Does A really wants to know this information? or it should
> >>>>>>>>> just need to know if state of line A-->B changed?
> >>>>>>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
> >>>>>>>> ESD protector and level shifter, and C is the physical HDMI connector.
> >>>>>>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
> >>>>>>>> be notified in order to reset the CEC state machine. One could however
> >>>>>>>> argue that in that case the A-B link state changes too, but the
> >>>>>>>> important part is that HPD detection is not performed by A, while A
> >>>>>>>> needs to be informed of lost hotplug.
> >>>>>>> I have no full picture but I guess in this case C sends HPD to B using
> >>>>>>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
> >>>>>>> say that B does not participate in HPD transmission/forwarding,
> >>>>>> No, in this case A doesn't receive any hardware HPD signal, it requires
> >>>>>> HPD notification through software.
> >>>>>>
> >>>>>>> some shifters with 'advanced power saving' can even perform wake-up of
> >>>>>>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
> >>>>>>> to A is indeed different than HPD sent from C to B.
> >>>>>>>
> >>>>>>> Btw, with the above logic of propagation of HPD callback (proposed by
> >>>>>>> Daniel) I guess it will work this way:
> >>>>>>>
> >>>>>>> - A will receive HPD signal via HW,
> >>>>>>>
> >>>>>>> - then B and C will receive HPD callback via framework.
> >>>>>>>
> >>>>>>> Am I right?
> >>>>>> It's the other way around.
> >>>>>>
> >>>>>> In this case the HPD signal from the connector (C) is routed to an input
> >>>>>> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
> >>>>>> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
> >>>>>> IRQ and receive the hardware HPD notification. The driver for the HDMI
> >>>>>> encoder (A) needs to receive HPD notification in software, through the
> >>>>>> framework.
> >>>>> If this is GPIO I wonder why do not query this gpio by encoder directly,
> >>>>> rules of ownership of such gpios seems to be grey area, so in such case
> >>>>> I would advise to put it in the driver who really needs it.
> >>>>>
> >>>>> This way it will be much simpler.
> >>>> First to fall, multiple drivers may need to be informed of HPD events
> >>>> coming from a GPIO, so we would need to duplicate it in multiple places,
> >>>> and I don't think the GPIO framework allows acquiring a GPIO multiple
> >>>> times.
> >>>>
> >>>> Then, the GPIO is described in DT, and DT doesn't care about which
> >>>> driver needs HPD events. DT specifies the GPIO in the node of the device
> >>>> it belongs to, this is defined in DT bindings, and must be the same on
> >>>> all boards, while depending on the board different devices may need to
> >>>> be informed of HPD events.
> >>>>
> >>>> For those two reasons HPD GPIO handling and consumption of HPD events
> >>>> can't always be grouped in the same driver.
> >>>>
> >>>>> Going back to HPD notifications, as I said earlier broadcasting HPD
> >>>>> notification unconditionally to every member of the chain with hope that
> >>>>> the member will be able to filter-out undesired notification seems to me
> >>>>> incorrect - maybe it can solve some problems but is not flexible enough
> >>>>> to be usable in other scenarios.
> >>>>>
> >>>>> If my arguments do not convince you please just continue with your
> >>>>> ideas, we can always add NO_HPD_BROADCAST somewhere :)
> >>>> :-) I would like to understand the problems you're referring to though,
> >>>> and hopefully solve them. If you could describe one of the scenarios
> >>>> where you think this mechanism wouldn't be usable that would help. In
> >>>> the meantime I will post a new version of the series with these
> >>>> operations kept as-is to get the rest of the patches reviewed.
> >>> See my little thing about midlayers, I think midlayers with lots of flags
> >>> for everything aren't a good idea. They should be more opinionated about
> >>> how things work.
> >>>
> >>> So if there's a case where this broadcasting of various things doesn't
> >>> work, let's dig into it.
> >>>
> >> OK, almost real life example:
> >>
> >> A -> B -> C
> >>
> >> A - RGB/HDMI converter,
> >>
> >> B - HDMI/MHL converter,
> >>
> >> C - uUSB controller (MUIC).
> >>
> >>
> >> C - detects presence of MHL sink and routes MHL lines to B in such case.
> >>
> >> B - has no hardware logic to detect HPD, but it's firmware can read EDID
> >> from downstream component via HW lines and it has hardware lines to
> >> upstream component to send EDID,
> >>
> >> A - can read EDID from B via hardware lines, but does not have hardware HPD.
> > 
> > It probably doesn't matter much for the overall discussion, but out of
> > curiosity, does B have a CBUS interface towards C and a DDC (I2C)
> > interface towards A ?
> 
> Yes, but C (MUIC) is not MHL aware, beside initial 1K resistance
> detection on ID pin, AFAIK.
> 
> >  And does A read the EDID on DDC and expose it
> > towards the SoC through a custom protocol (for instance as the ADV7511
> > does), or does it forward the DDC lines to the SoC ?
> >
> >> So how it should work (according to specification):
> >>
> >> 1. C detects MHL sink.
> >>
> >> 2. C switches his mux to route lines to B.
> >>
> >> 3. C sends HPD notification to B.
> >>
> >> 4. B powers on, its firmware reads EDID from downstream lines (possibly
> >> adjusting it) and makes it available to upstream component A.
> >>
> >> 5. B sends HPD notification to A.
> >>
> >>
> >> I do not know how it could work with HPD broadcasting.
> >>
> >> I guess C should be HPD provider, but in case of HPD broadcasting A and
> >> B would receive notification in the same time, as a result A would start
> >> reading EDID too early - fail.
> >
> > That's an interesting case indeed. Now I understand what you meant
> > earlier.
> >
> > The HPD notification from C to B is purely internal, and should not be
> > visible from a DRM/KMS point of view. It just happens that this hardware
> > setup has a more complex HPD sequence that requires software
> > intervention in the middle of the sequence. As such, if we forget about
> > this patch series for a minute, C would need a custom API to send MHL
> > notification to B, and the HPD for DRM/KMS would be notified by B, right
> > ?
> 
> I just want to convince you that maybe all HPD signals
> (hardware/software) are purely internal (ie they should be handled only
> by upstream devices), and the hotplug event should be sent to
> userspace/drm_core only when WHOLE pipeline is ready to query modes
> (i2c/sideband channels/whatever is functional).

I think that most HPD events are not internal, and that the above case
is more an exception than a rule :-) It should however be supported, and
I agree that HPD should be notified to the DRM core only when it has
traversed the whole pipeline, yes.

I'd like to keep bridge drivers simple though, and avoid requiring
manual HPD propagation as I think that's the common case. That's why I
proposed blocking the propagation below. What do you think ?

This also means that, if we switch to a model where propagation can be
disabled, a bridge will only notify upstream (closer to the CRTC)
bridges. If, in a A-B-C chain, bridge B receives the external HPD event,
then bridge C would never be notified. Do you think that could be an
issue ?

> > I think it's possible to handle both the MHL notification and the
> > user-visible HPD notification through the same bridge API, provided that
> > we offer a way for a bridge to block forwarding of the HPD notification.
> > This will also require calling the HPD notifiers on bridges in the sink
> > to source order. Both are doable, the bridge HPD notifier operation
> > could return a bool that blocks propagation of the notification. Would
> > that work for you ?
> 
> It could work, in this case.
> 
> But it will still have problems with non-linear pipelines - where stream
> is split to two or more bridges/panels.

I agree, but that's not supported by the bridge API for now. I'm not
sure I'm looking forward to dealing with this, but I think it will be
needed :-)

> >>>>>>>>>>>> You seem to have some other idea here.
> >>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>>>>>>>>  	 */
> >>>>>>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>>>>>>>>> +	 * output.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>>>>>>>>> presence of another one?
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>>>>>>>>> and connector->edid correctly.
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>>>>>>>>> correctly updated.
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>>>>>>>>> +};
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>>>>>>>>   */
> >>>>>>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	int type;
> >>>>>>>>>>>>>>>>>> +	/** private: */
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>>>>>>> +			   void *data);
> >>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362
  2019-07-07 18:18   ` [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362 Laurent Pinchart
  2019-07-09 14:32     ` Andrzej Hajda
@ 2019-08-27  6:16     ` Tomi Valkeinen
  1 sibling, 0 replies; 166+ messages in thread
From: Tomi Valkeinen @ 2019-08-27  6:16 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

On 07/07/2019 21:18, Laurent Pinchart wrote:
> The TI OP362 is an analog video amplifier controlled through a GPIO. Add
> support for it to the simple-bridge driver.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/bridge/simple-bridge.c | 5 +++++
>   1 file changed, 5 insertions(+)
> 
> diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
> index a7edf3c39627..7495b9bef865 100644
> --- a/drivers/gpu/drm/bridge/simple-bridge.c
> +++ b/drivers/gpu/drm/bridge/simple-bridge.c
> @@ -296,6 +296,11 @@ static const struct of_device_id simple_bridge_match[] = {
>   			.timings = &default_bridge_timings,
>   			.type = DRM_MODE_CONNECTOR_VGA,
>   		},
> +	}, {
> +		.compatible = "ti,opa362",
> +		.data = &(const struct simple_bridge_info) {
> +			.type = DRM_MODE_CONNECTOR_Composite,
> +		},

I have to say I'm pretty clueless about the analog TV, but OMAP DSS 
supports also s-video outputs. But I don't know if OPA362 can be used 
with s-video, or does it dictate composite.

In any case,

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>

  Tomi
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 11/60] drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter
       [not found]     ` <3347b6c8-6f2d-17d6-3dc8-e62a3bac634b@ti.com>
@ 2019-08-27  7:30       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-27  7:30 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

Hi Tomi,

On Tue, Aug 27, 2019 at 09:36:00AM +0300, Tomi Valkeinen wrote:
> On 07/07/2019 21:18, Laurent Pinchart wrote:
> > The TI TPD12S015 is an HDMI level shifter and ESD protector controlled
> > through GPIOs. Add a DRM bridge driver for the device.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   drivers/gpu/drm/bridge/Kconfig        |   8 +
> >   drivers/gpu/drm/bridge/Makefile       |   1 +
> >   drivers/gpu/drm/bridge/ti-tpd12s015.c | 204 ++++++++++++++++++++++++++
> >   3 files changed, 213 insertions(+)
> >   create mode 100644 drivers/gpu/drm/bridge/ti-tpd12s015.c
> > 
> > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > index 295a62f65ef9..3928651a0819 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -159,6 +159,14 @@ config DRM_TI_SN65DSI86
> >   	help
> >   	  Texas Instruments SN65DSI86 DSI to eDP Bridge driver
> >   
> > +config DRM_TI_TPD12S015
> > +	tristate "TI TPD12S015 HDMI level shifter and ESD protection"
> > +	depends on OF
> > +	select DRM_KMS_HELPER
> > +	help
> > +	  Texas Instruments TPD12S015 HDMI level shifter and ESD protection
> > +	  driver.
> > +
> >   source "drivers/gpu/drm/bridge/analogix/Kconfig"
> >   
> >   source "drivers/gpu/drm/bridge/adv7511/Kconfig"
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index e5987b3aaf62..ce635651e31b 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -17,4 +17,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
> >   obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
> >   obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
> >   obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
> > +obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
> >   obj-y += synopsys/
> > diff --git a/drivers/gpu/drm/bridge/ti-tpd12s015.c b/drivers/gpu/drm/bridge/ti-tpd12s015.c
> > new file mode 100644
> > index 000000000000..d01f0c4133a2
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/ti-tpd12s015.c
> > @@ -0,0 +1,204 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * TPD12S015 HDMI ESD protection & level shifter chip driver
> > + *
> > + * Copyright (C) 2013 Texas Instruments Incorporated
> > + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
> > + */
> 
> You may want to mention that the driver is based on an existing omapdrm 
> driver. Otherwise the above lines don't quite make sense when adding a 
> new driver =).

Indeed. I'll fix that.

> > +
> > +#include <linux/delay.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include <drm/drm_bridge.h>
> > +
> > +struct tpd12s015_device {
> > +	struct drm_bridge bridge;
> > +
> > +	struct gpio_desc *ct_cp_hpd_gpio;
> > +	struct gpio_desc *ls_oe_gpio;
> > +	struct gpio_desc *hpd_gpio;
> > +	int hpd_irq;
> > +
> > +	struct drm_bridge *next_bridge;
> > +};
> > +
> > +static inline struct tpd12s015_device *to_tpd12s015(struct drm_bridge *bridge)
> > +{
> > +	return container_of(bridge, struct tpd12s015_device, bridge);
> > +}
> > +
> > +static int tpd12s015_attach(struct drm_bridge *bridge, bool create_connector)
> > +{
> > +	struct tpd12s015_device *tpd = to_tpd12s015(bridge);
> > +	int ret;
> > +
> > +	if (create_connector)
> > +		return -EINVAL;
> > +
> > +	ret = drm_bridge_attach(bridge->encoder, tpd->next_bridge,
> > +				bridge, false);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 1);
> > +	gpiod_set_value_cansleep(tpd->ls_oe_gpio, 1);
> 
> These are fine (and as they were in the old driver), but just talking 
> out loud: LS_OE is needed when talking over i2c,

I2C or CEC, right ? For I2C this would require modelling the chip as an
I2C gate, which will have an impact on DT bindings. CEC would likely
need a similar treatment. If someone really wants to save power I think
that would be possible, but I won't do so as part of this series.

> and CT_CP_HPD is needed when interested in HPD.
> 
> Not sure what kind of power-savings are possible, but at least in theory 
> we could turn those off at times. At least when going to system suspend.
> 
> > +	/* DC-DC converter needs at max 300us to get to 90% of 5V. */
> > +	usleep_range(300, 1000);
> > +
> > +	return 0;
> > +}
> > +
> > +static void tpd12s015_detach(struct drm_bridge *bridge)
> > +{
> > +	struct tpd12s015_device *tpd = to_tpd12s015(bridge);
> > +
> > +	gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 0);
> > +	gpiod_set_value_cansleep(tpd->ls_oe_gpio, 0);
> > +}
> > +
> > +static enum drm_connector_status tpd12s015_detect(struct drm_bridge *bridge)
> > +{
> > +	struct tpd12s015_device *tpd = to_tpd12s015(bridge);
> > +
> > +	if (gpiod_get_value_cansleep(tpd->hpd_gpio))
> > +		return connector_status_connected;
> > +	else
> > +		return connector_status_disconnected;
> > +}
> > +
> > +static void tpd12s015_hpd_enable(struct drm_bridge *bridge)
> > +{
> > +}
> > +
> > +static void tpd12s015_hpd_disable(struct drm_bridge *bridge)
> > +{
> > +}
> 
> Shouldn't we manage the CT_HPD_GPIO here, and request the irq here? I'm 
> fine with leaving that for later, though, as it may be better to keep 
> this new driver as similar to the old one as possible to prevent 
> regressions during this big patch series.

That's a good point. It's fairly easy to do, so I'll give it a try
already.

> I guess it's fine for a bridge to do hpd_notify even if hpd_enable has 
> not been called?

Yes, the HPD events are blocked by the core in that case.

> > +static const struct drm_bridge_funcs tpd12s015_bridge_funcs = {
> > +	.attach			= tpd12s015_attach,
> > +	.detach			= tpd12s015_detach,
> > +	.detect			= tpd12s015_detect,
> > +	.hpd_enable		= tpd12s015_hpd_enable,
> > +	.hpd_disable		= tpd12s015_hpd_disable,
> > +};
> > +
> > +static irqreturn_t tpd12s015_hpd_isr(int irq, void *data)
> > +{
> > +	struct tpd12s015_device *tpd = data;
> > +	struct drm_bridge *bridge = &tpd->bridge;
> > +
> > +	drm_bridge_hpd_notify(bridge, tpd12s015_detect(bridge));
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int tpd12s015_probe(struct platform_device *pdev)
> > +{
> > +	struct tpd12s015_device *tpd;
> > +	struct device_node *node;
> > +	struct gpio_desc *gpio;
> > +	int ret;
> > +
> > +	tpd = devm_kzalloc(&pdev->dev, sizeof(*tpd), GFP_KERNEL);
> > +	if (!tpd)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, tpd);
> > +
> > +	tpd->bridge.funcs = &tpd12s015_bridge_funcs;
> > +	tpd->bridge.of_node = pdev->dev.of_node;
> > +	tpd->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> > +	tpd->bridge.ops = DRM_BRIDGE_OP_DETECT;
> > +
> > +	/* Get the next bridge, connected to port@1. */
> > +	node = of_graph_get_remote_node(pdev->dev.of_node, 1, -1);
> > +	if (!node)
> > +		return -ENODEV;
> > +
> > +	tpd->next_bridge = of_drm_find_bridge(node);
> > +	of_node_put(node);
> > +
> > +	if (!tpd->next_bridge)
> > +		return -EPROBE_DEFER;
> > +
> > +	/* Get the control and HPD GPIOs. */
> > +	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
> > +					     GPIOD_OUT_LOW);
> > +	if (IS_ERR(gpio))
> > +		return PTR_ERR(gpio);
> > +
> > +	tpd->ct_cp_hpd_gpio = gpio;
> > +
> > +	gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
> > +					     GPIOD_OUT_LOW);
> > +	if (IS_ERR(gpio))
> > +		return PTR_ERR(gpio);
> > +
> > +	tpd->ls_oe_gpio = gpio;
> > +
> > +	gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, GPIOD_IN);
> > +	if (IS_ERR(gpio))
> > +		return PTR_ERR(gpio);
> > +
> > +	tpd->hpd_gpio = gpio;
> > +
> > +	/* Register the IRQ if the HPD GPIO is IRQ-capable. */
> > +	if (tpd->hpd_gpio)
> > +		tpd->hpd_irq = gpiod_to_irq(tpd->hpd_gpio);
> 
> We always have tpd->hpd_gpio here, don't we?

Good point, I'll fix that.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 13/60] drm/bridge: tfp410: Don't include drmP.h
       [not found]     ` <3bb82dc4-434a-aaac-8ea1-3aff0991e790@ti.com>
@ 2019-08-27  7:47       ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-08-27  7:47 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

Hi Tomi,

On Tue, Aug 27, 2019 at 09:38:18AM +0300, Tomi Valkeinen wrote:
> On 07/07/2019 21:18, Laurent Pinchart wrote:
> > The drmP.h header is deprecated, replace it with the headers
> > specifically needed by the tfp410 driver. While at it, replace the DRM
> > print macros with dev_info() and dev_err() instead of including
> > drm_print.h
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   drivers/gpu/drm/bridge/ti-tfp410.c | 6 ++++--
> >   1 file changed, 4 insertions(+), 2 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
> > index 8d4690e436c3..a1cad777b057 100644
> > --- a/drivers/gpu/drm/bridge/ti-tfp410.c
> > +++ b/drivers/gpu/drm/bridge/ti-tfp410.c
> > @@ -18,6 +18,7 @@
> >   #include <linux/platform_device.h>
> >   
> >   #include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_bridge.h>
> >   #include <drm/drm_crtc.h>
> >   #include <drm/drm_print.h>
> >   #include <drm/drm_probe_helper.h>
> 
> You're not actually removing drmP.h. So is the drm_bridge.h needed?

A previous version of the patch did, and I didn't notice someone beat me
to it :-) drm_bridge.h is needed but is included through drm_crtc.h.
Note that Boris has submitted "[PATCH v2 01/21] drm: Stop including
drm_bridge.h from drm_crtc.h" which will change this, and include
drm_bridge.h explicitly in this driver, so I'll drop this patch.

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-26 16:27                                         ` Laurent Pinchart
@ 2019-08-29 16:48                                           ` Andrzej Hajda
  2019-12-02 15:40                                             ` Laurent Pinchart
  0 siblings, 1 reply; 166+ messages in thread
From: Andrzej Hajda @ 2019-08-29 16:48 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

On 26.08.2019 18:27, Laurent Pinchart wrote:
> Hi Andrzej,
>
> On Thu, Aug 22, 2019 at 02:17:16PM +0200, Andrzej Hajda wrote:
>> On 20.08.2019 00:45, Laurent Pinchart wrote:
>>> On Mon, Aug 19, 2019 at 10:38:35AM +0200, Andrzej Hajda wrote:
>>>> On 14.08.2019 14:40, Daniel Vetter wrote:
>>>>> On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
>>>>>> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
>>>>>>> On 11.08.2019 00:43, Laurent Pinchart wrote:
>>>>>>>> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
>>>>>>>>> On 08.08.2019 21:32, Laurent Pinchart wrote:
>>>>>>>>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
>>>>>>>>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
>>>>>>>>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
>>>>>>>>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
>>>>>>>>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
>>>>>>>>>>>>>>>>>>> Hi Laurent,
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
>>>>>>>>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
>>>>>>>>>>>>>>>>>>> panel is very painful.
>>>>>>>>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
>>>>>>>>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
>>>>>>>>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
>>>>>>>>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> More comments inlined.
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
>>>>>>>>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
>>>>>>>>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
>>>>>>>>>>>>>>>>>>>> data:
>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
>>>>>>>>>>>>>>>>>>>>   retrieval operations
>>>>>>>>>>>>>>>>>>>> - Bitmask of supported operations
>>>>>>>>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
>>>>>>>>>>>>>>>>>>> operation's callback?
>>>>>>>>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
>>>>>>>>>>>>>>>>>> add, generally good excuse to not have to think through the design between
>>>>>>>>>>>>>>>>>> different parts of drivers - "just" add another flag.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> - Bridge output type
>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> Add and document these.
>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
>>>>>>>>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
>>>>>>>>>>>>>>>>>>>> bridges.
>>>>>>>>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
>>>>>>>>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
>>>>>>>>>>>>>>>>>>> right? More comments about it later.
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
>>>>>>>>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
>>>>>>>>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
>>>>>>>>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
>>>>>>>>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
>>>>>>>>>>>>>>>>>>>>   */
>>>>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>>>>>  {
>>>>>>>>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
>>>>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
>>>>>>>>>>>>>>>>>>>>  	list_del_init(&bridge->list);
>>>>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>>  }
>>>>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>>  }
>>>>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
>>>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>>>>>> + * @cb: hot-plug detection callback
>>>>>>>>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
>>>>>>>>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
>>>>>>>>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
>>>>>>>>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
>>>>>>>>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
>>>>>>>>>>>>>>>>>>>> + * the bridge.
>>>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
>>>>>>>>>>>>>>>>>>> bridge attach:
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> bridge->hpd_cb = cb;
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> bridge->hpd_data = data;
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> ret = drm_bridge_attach(...);
>>>>>>>>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
>>>>>>>>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
>>>>>>>>>>>>>>>>>>> without big sacrifices.
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
>>>>>>>>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>>>>>>>>> +			   void *data)
>>>>>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
>>>>>>>>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
>>>>>>>>>>>>>>>>>>>> +		goto unlock;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
>>>>>>>>>>>>>>>>>>>> +	bridge->hpd_data = data;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +unlock:
>>>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
>>>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
>>>>>>>>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
>>>>>>>>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
>>>>>>>>>>>>>>>>>>>> + * output status change occurs.
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
>>>>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
>>>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
>>>>>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
>>>>>>>>>>>>>>>>>>>> +		return;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
>>>>>>>>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
>>>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
>>>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
>>>>>>>>>>>>>>>>>>>> + * @status: output connection status
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
>>>>>>>>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
>>>>>>>>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
>>>>>>>>>>>>>>>>>>>> + *
>>>>>>>>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
>>>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status)
>>>>>>>>>>>>>>>>>>>> +{
>>>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +	if (bridge->hpd_cb)
>>>>>>>>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
>>>>>>>>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
>>>>>>>>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
>>>>>>>>>>>>>>>>>> 		if (tmp_bridge == bridge)
>>>>>>>>>>>>>>>>>> 			continue;
>>>>>>>>>>>>>>>>>> 		if (bridge->hpd_notify);
>>>>>>>>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
>>>>>>>>>>>>>>>>>> 	}
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
>>>>>>>>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
>>>>>>>>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> 	dev = bridge->dev
>>>>>>>>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
>>>>>>>>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
>>>>>>>>>>>>>>>>>> hpd they want/need.
>>>>>>>>>>>>>>>>> As I understand you want to notify every member of the pipeline.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I think it should be enough to notify only the source, and then source
>>>>>>>>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> It looks more generic for me.
>>>>>>>>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
>>>>>>>>>>>>>>>> the one from Laurent? Kinda confused here.
>>>>>>>>>>>>>>> Regarding general idea:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
>>>>>>>>>>>>>>> source.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 2. Your is to notify all other bridges and encoder.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> And I prefer 1st approach, why:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> - the source can decide if/when and to who propagate the signal,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> - is more generic, for example if bridge send signal to two
>>>>>>>>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
>>>>>>>>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
>>>>>>>>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
>>>>>>>>>>>>> If there will be two consumers, there will be two bridge attachments,
>>>>>>>>>>>>> thus there will be two notifications, it should work.
>>>>>>>>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
>>>>>>>>>>>> callback is registered on the produce bridge, not on the consumer bridge
>>>>>>>>>>>> (or I'm totallly misreading what Laurent does here).
>>>>>>>>>>> I have assumed that if devices exposes two hardware sink interfaces it
>>>>>>>>>>> will expose two separate bridges - of course it will not work with
>>>>>>>>>>> "bridge chaining" thing, but this is a different story.
>>>>>>>>>> Daniel is right that the current implementation only allows one
>>>>>>>>>> consumer. This is however not a limitation of the API, but of its
>>>>>>>>>> implementation, as I only needed a single consumer. The helpers in this
>>>>>>>>>> series ensure that neither the consumer nor the producer poke in the
>>>>>>>>>> drm_bridge structure to call back to the HPD handler:
>>>>>>>>>>
>>>>>>>>>> - The consumer calls drm_bridge_hpd_enable() and
>>>>>>>>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
>>>>>>>>>>   behaviour if desired without changes to the consumer.
>>>>>>>>>>
>>>>>>>>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
>>>>>>>>>>   which could also easily accommodate reference-counting in the drm
>>>>>>>>>>   bridge core without changes to the producer.
>>>>>>>>>>
>>>>>>>>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
>>>>>>>>>>   easily be extended to support multiple consumers without changes to
>>>>>>>>>>   the producer.
>>>>>>>>>>
>>>>>>>>>> This is actually my second version of the HPD mechanism. The first
>>>>>>>>>> version was never posted, poked into drm_bridge, and required the
>>>>>>>>>> producer to be aware of the callbacks. After discussing this privately
>>>>>>>>>> with Daniel, I came up with the implementation in this series that,
>>>>>>>>>> while not supporting multiple consumers now, makes it easy to extend
>>>>>>>>>> later without minimal effort.
>>>>>>>>>>
>>>>>>>>>> Daniel's proposed implementation above looks reasonable to me, provided
>>>>>>>>>> we can iterate over the bridges in an order that don't depend on the
>>>>>>>>>> position of the producer in the chain (should be easy to solve by
>>>>>>>>>> starting at the encoder for instance). It however looks a bit like a
>>>>>>>>>> midlayer to me :-) That's why I have a similar implementation in the
>>>>>>>>>> connector-bridge helper, which could be extended to call
>>>>>>>>>> encoder->helper_private->bridge_hpd_notify() and
>>>>>>>>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
>>>>>>>>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
>>>>>>>>>> drm_bridge_hpd_notify() would on the other hand set the notification
>>>>>>>>>> sequence towards the encoder and driver in stone. Daniel, do you think
>>>>>>>>>> that would be better ?
>>>>>>>>>>
>>>>>>>>>> I would like to remind everybody that this series isn't the last I will
>>>>>>>>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
>>>>>>>>>> open to suggestions, and can address problems on top of these patches,
>>>>>>>>>> provided obviously that this series doesn't go in the wrong direction.
>>>>>>>>>> I'm of course also willing to rework this series, but given the amount
>>>>>>>>>> of work we have in the drm_bridge realm, I can't fix everything in one
>>>>>>>>>> go :-)
>>>>>>>>>>
>>>>>>>>>>>>>>> - it resembles hardware wires :)
>>>>>>>>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
>>>>>>>>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
>>>>>>>>>>>>>> interested in that hpd singal. This includes:
>>>>>>>>>>>>>> - Other bridges, e.g. if they provide CEC support.
>>>>>>>>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
>>>>>>>>>>>>>> - Overall driver, so it can update the modes/connector status and send the
>>>>>>>>>>>>>>   uevent to the driver.
>>>>>>>>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
>>>>>>>>>>>>>>   shut down/re-enable the pipe because $reasons.
>>>>>>>>>>>>>>  
>>>>>>>>>>>>>> That's at least my understanding from lots of chats with Laurent about
>>>>>>>>>>>>>> what he wants to do here.
>>>>>>>>>> That's correct, and that's what I was trying to implement :-) The
>>>>>>>>>> notification, in this patch series, goes from the producer bridge to a
>>>>>>>>>> central place (namely the connector, with a helper implementation
>>>>>>>>>> available as part of this series, but custom implementations in display
>>>>>>>>>> drivers are fine if needed) that then dispatches the notification to all
>>>>>>>>>> bridges (through the .lost_hotplug() operation, which we could replace
>>>>>>>>>> by an .hpd_notify() operation) for the first two purposes listed above,
>>>>>>>>>> and then to the overall driver. The only thing I don't support yet is
>>>>>>>>>> dispatching to the display pipeline (item 4 in the list above) as I had
>>>>>>>>>> no need for that, and didn't want to develop an API with no user. This
>>>>>>>>>> would however not be difficult to do when needed, the need is taken into
>>>>>>>>>> account in the proposed implementation.
>>>>>>>>>>
>>>>>>>>>>>>> I do not know the full picture, but the solution where particular bridge
>>>>>>>>>>>>> notifies everything unconditionally seems to me much less flexible.
>>>>>>>>>>>>>
>>>>>>>>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
>>>>>>>>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
>>>>>>>>>>>>> will mimic your scenario.
>>>>>>>>>>>>>
>>>>>>>>>>>>> But there are also other scenarios where bridge does not want to
>>>>>>>>>>>>> propagate signal, because for example:
>>>>>>>>>>>>>
>>>>>>>>>>>>> - it wants to wait for other sinks to wake up,
>>>>>>>>>>>>>
>>>>>>>>>>>> The other sink can just do that in their hpd callback.
>>>>>>>>>>>>
>>>>>>>>>>>>> - it propagates HPD signal via hardware wire,
>>>>>>>>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
>>>>>>>>>>>> the wire/hw hpd interrupt.
>>>>>>>>>>>>
>>>>>>>>>>> If it should ignore HPD, why it should receive it at all - it is
>>>>>>>>>>> unnecessary noise. And I am afraid with more complicated pipelines it
>>>>>>>>>>> will be impossible for particular component (bridge/encoder/whatever) to
>>>>>>>>>>> distinguish if HPD notification which came from non-directly connected
>>>>>>>>>>> component should be ignored or not.
>>>>>>>>>>>
>>>>>>>>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
>>>>>>>>>>>>> device.
>>>>>>>>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
>>>>>>>>>>>> board?
>>>>>>>>>>> Bridge can have external connectors, and the user can connect there
>>>>>>>>>>> anything.
>>>>>>>>>>>
>>>>>>>>>>>>> In general HPD is input signal for notify of state changes on particular
>>>>>>>>>>>>> bus, in case of typical video bridge on its output video bus.
>>>>>>>>>>>>>
>>>>>>>>>>>>> In case of bridges they have also input video buses, and they can send
>>>>>>>>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
>>>>>>>>>>>>> if for most cases they looks similar.
>>>>>>>>>>>> Ah, I think this is a problem we will eventually have. But it's not
>>>>>>>>>>>> something we're currently solving here at all I think.
>>>>>>>>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
>>>>>>>>>>> line, so this is not something from far future. And I guess with HPD
>>>>>>>>>>> broadcasting it could be racy/error prone, for example EDID reading can
>>>>>>>>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
>>>>>>>>>>> controller via hw wires also).
>>>>>>>>>>>
>>>>>>>>>>>>>>> And regarding implementation:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Your proposition is more straightforward, but if we want to notify only
>>>>>>>>>>>>>>> source we should locate it by parsing notification chain (what about
>>>>>>>>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
>>>>>>>>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
>>>>>>>>>>>>>> Uh I think we're not talking about the same thing really. My understanding
>>>>>>>>>>>>>> is that this callback is if someone (outside of this bridge) is interested
>>>>>>>>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
>>>>>>>>>>>>>> listener.
>>>>>>>>>>>>> Do we have real life examples?
>>>>>>>>>>>>>
>>>>>>>>>>>>> I want to distinguish two situations:
>>>>>>>>>>>>>
>>>>>>>>>>>>> - another device wants to know if input bus of the bridge has changed state,
>>>>>>>>>>>>>
>>>>>>>>>>>>> - another device wants to know if output bus of the bridge has changed
>>>>>>>>>>>>> state.
>>>>>>>>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
>>>>>>>>>>>> bridges can exchange state and information about each another. hpd is
>>>>>>>>>>>> about the physical world, i.e. "is there a cable plugged into the port
>>>>>>>>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
>>>>>>>>>>>> fun stuff like that, we have the atomic_check machinery for this.
>>>>>>>>>>> My question was if we have real examples that upstream device requires
>>>>>>>>>>> knowledge about state of output line of the bridge?
>>>>>>>>>>>
>>>>>>>>>>> To be more precise, we have following display pipeline:
>>>>>>>>>>>
>>>>>>>>>>> A-->B-->C
>>>>>>>>>>>
>>>>>>>>>>> And C sends HPD to B (ie signal that state of line between B and C
>>>>>>>>>>> changed). Does A really wants to know this information? or it should
>>>>>>>>>>> just need to know if state of line A-->B changed?
>>>>>>>>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
>>>>>>>>>> ESD protector and level shifter, and C is the physical HDMI connector.
>>>>>>>>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
>>>>>>>>>> be notified in order to reset the CEC state machine. One could however
>>>>>>>>>> argue that in that case the A-B link state changes too, but the
>>>>>>>>>> important part is that HPD detection is not performed by A, while A
>>>>>>>>>> needs to be informed of lost hotplug.
>>>>>>>>> I have no full picture but I guess in this case C sends HPD to B using
>>>>>>>>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
>>>>>>>>> say that B does not participate in HPD transmission/forwarding,
>>>>>>>> No, in this case A doesn't receive any hardware HPD signal, it requires
>>>>>>>> HPD notification through software.
>>>>>>>>
>>>>>>>>> some shifters with 'advanced power saving' can even perform wake-up of
>>>>>>>>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
>>>>>>>>> to A is indeed different than HPD sent from C to B.
>>>>>>>>>
>>>>>>>>> Btw, with the above logic of propagation of HPD callback (proposed by
>>>>>>>>> Daniel) I guess it will work this way:
>>>>>>>>>
>>>>>>>>> - A will receive HPD signal via HW,
>>>>>>>>>
>>>>>>>>> - then B and C will receive HPD callback via framework.
>>>>>>>>>
>>>>>>>>> Am I right?
>>>>>>>> It's the other way around.
>>>>>>>>
>>>>>>>> In this case the HPD signal from the connector (C) is routed to an input
>>>>>>>> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
>>>>>>>> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
>>>>>>>> IRQ and receive the hardware HPD notification. The driver for the HDMI
>>>>>>>> encoder (A) needs to receive HPD notification in software, through the
>>>>>>>> framework.
>>>>>>> If this is GPIO I wonder why do not query this gpio by encoder directly,
>>>>>>> rules of ownership of such gpios seems to be grey area, so in such case
>>>>>>> I would advise to put it in the driver who really needs it.
>>>>>>>
>>>>>>> This way it will be much simpler.
>>>>>> First to fall, multiple drivers may need to be informed of HPD events
>>>>>> coming from a GPIO, so we would need to duplicate it in multiple places,
>>>>>> and I don't think the GPIO framework allows acquiring a GPIO multiple
>>>>>> times.
>>>>>>
>>>>>> Then, the GPIO is described in DT, and DT doesn't care about which
>>>>>> driver needs HPD events. DT specifies the GPIO in the node of the device
>>>>>> it belongs to, this is defined in DT bindings, and must be the same on
>>>>>> all boards, while depending on the board different devices may need to
>>>>>> be informed of HPD events.
>>>>>>
>>>>>> For those two reasons HPD GPIO handling and consumption of HPD events
>>>>>> can't always be grouped in the same driver.
>>>>>>
>>>>>>> Going back to HPD notifications, as I said earlier broadcasting HPD
>>>>>>> notification unconditionally to every member of the chain with hope that
>>>>>>> the member will be able to filter-out undesired notification seems to me
>>>>>>> incorrect - maybe it can solve some problems but is not flexible enough
>>>>>>> to be usable in other scenarios.
>>>>>>>
>>>>>>> If my arguments do not convince you please just continue with your
>>>>>>> ideas, we can always add NO_HPD_BROADCAST somewhere :)
>>>>>> :-) I would like to understand the problems you're referring to though,
>>>>>> and hopefully solve them. If you could describe one of the scenarios
>>>>>> where you think this mechanism wouldn't be usable that would help. In
>>>>>> the meantime I will post a new version of the series with these
>>>>>> operations kept as-is to get the rest of the patches reviewed.
>>>>> See my little thing about midlayers, I think midlayers with lots of flags
>>>>> for everything aren't a good idea. They should be more opinionated about
>>>>> how things work.
>>>>>
>>>>> So if there's a case where this broadcasting of various things doesn't
>>>>> work, let's dig into it.
>>>>>
>>>> OK, almost real life example:
>>>>
>>>> A -> B -> C
>>>>
>>>> A - RGB/HDMI converter,
>>>>
>>>> B - HDMI/MHL converter,
>>>>
>>>> C - uUSB controller (MUIC).
>>>>
>>>>
>>>> C - detects presence of MHL sink and routes MHL lines to B in such case.
>>>>
>>>> B - has no hardware logic to detect HPD, but it's firmware can read EDID
>>>> from downstream component via HW lines and it has hardware lines to
>>>> upstream component to send EDID,
>>>>
>>>> A - can read EDID from B via hardware lines, but does not have hardware HPD.
>>> It probably doesn't matter much for the overall discussion, but out of
>>> curiosity, does B have a CBUS interface towards C and a DDC (I2C)
>>> interface towards A ?
>> Yes, but C (MUIC) is not MHL aware, beside initial 1K resistance
>> detection on ID pin, AFAIK.
>>
>>>  And does A read the EDID on DDC and expose it
>>> towards the SoC through a custom protocol (for instance as the ADV7511
>>> does), or does it forward the DDC lines to the SoC ?
>>>
>>>> So how it should work (according to specification):
>>>>
>>>> 1. C detects MHL sink.
>>>>
>>>> 2. C switches his mux to route lines to B.
>>>>
>>>> 3. C sends HPD notification to B.
>>>>
>>>> 4. B powers on, its firmware reads EDID from downstream lines (possibly
>>>> adjusting it) and makes it available to upstream component A.
>>>>
>>>> 5. B sends HPD notification to A.
>>>>
>>>>
>>>> I do not know how it could work with HPD broadcasting.
>>>>
>>>> I guess C should be HPD provider, but in case of HPD broadcasting A and
>>>> B would receive notification in the same time, as a result A would start
>>>> reading EDID too early - fail.
>>> That's an interesting case indeed. Now I understand what you meant
>>> earlier.
>>>
>>> The HPD notification from C to B is purely internal, and should not be
>>> visible from a DRM/KMS point of view. It just happens that this hardware
>>> setup has a more complex HPD sequence that requires software
>>> intervention in the middle of the sequence. As such, if we forget about
>>> this patch series for a minute, C would need a custom API to send MHL
>>> notification to B, and the HPD for DRM/KMS would be notified by B, right
>>> ?
>> I just want to convince you that maybe all HPD signals
>> (hardware/software) are purely internal (ie they should be handled only
>> by upstream devices), and the hotplug event should be sent to
>> userspace/drm_core only when WHOLE pipeline is ready to query modes
>> (i2c/sideband channels/whatever is functional).
> I think that most HPD events are not internal, and that the above case
> is more an exception than a rule :-) It should however be supported, and
> I agree that HPD should be notified to the DRM core only when it has
> traversed the whole pipeline, yes.
>
> I'd like to keep bridge drivers simple though, and avoid requiring
> manual HPD propagation as I think that's the common case. That's why I
> proposed blocking the propagation below. What do you think ?
>
> This also means that, if we switch to a model where propagation can be
> disabled, a bridge will only notify upstream (closer to the CRTC)
> bridges. If, in a A-B-C chain, bridge B receives the external HPD event,
> then bridge C would never be notified. Do you think that could be an
> issue ?


As I said somewhere earlier it should work.

Btw, since bridges are currently connected via single-linked list (just
drm_bridge->next), do you plan to switch to double linked list, to find
upstream bridge, or add logic to discover upstream bridge on the fly?


>
>>> I think it's possible to handle both the MHL notification and the
>>> user-visible HPD notification through the same bridge API, provided that
>>> we offer a way for a bridge to block forwarding of the HPD notification.
>>> This will also require calling the HPD notifiers on bridges in the sink
>>> to source order. Both are doable, the bridge HPD notifier operation
>>> could return a bool that blocks propagation of the notification. Would
>>> that work for you ?
>> It could work, in this case.
>>
>> But it will still have problems with non-linear pipelines - where stream
>> is split to two or more bridges/panels.
> I agree, but that's not supported by the bridge API for now. I'm not
> sure I'm looking forward to dealing with this, but I think it will be
> needed :-)


Currently there are two modes of usage of bridge:

- part of bridge chain,

- private bridge - it can be attached to other components via private
pointer, not drm_encoder->bridge, nor drm_bridge->next.

Non-linear pipelines can be ( and I guess they are ) implemented using
the latter.

Anyway if we want to extend bridge API it would be good to allow usage
of this API also with detached bridges.


Regards

Andrzej


>
>>>>>>>>>>>>>> You seem to have some other idea here.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>>  #ifdef CONFIG_OF
>>>>>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
>>>>>>>>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
>>>>>>>>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
>>>>>>>>>>>>>>>>>>>> @@ -23,8 +23,9 @@
>>>>>>>>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
>>>>>>>>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>> -#include <linux/list.h>
>>>>>>>>>>>>>>>>>>>>  #include <linux/ctype.h>
>>>>>>>>>>>>>>>>>>>> +#include <linux/list.h>
>>>>>>>>>>>>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
>>>>>>>>>>>>>>>>>>>>  #include <drm/drm_modes.h>
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
>>>>>>>>>>>>>>>>>>>>  	 */
>>>>>>>>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @detect:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
>>>>>>>>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
>>>>>>>>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
>>>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @get_modes:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
>>>>>>>>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
>>>>>>>>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
>>>>>>>>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
>>>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>> +			 struct drm_connector *connector);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @get_edid:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
>>>>>>>>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
>>>>>>>>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
>>>>>>>>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
>>>>>>>>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
>>>>>>>>>>>>>>>>>>>> +	 * output.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
>>>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * RETURNS:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
>>>>>>>>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
>>>>>>>>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>> +				 struct drm_connector *connector);
>>>>>>>>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
>>>>>>>>>>>>>>>>>>> presence of another one?
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
>>>>>>>>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
>>>>>>>>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
>>>>>>>>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
>>>>>>>>>>>>>>>>>> and connector->edid correctly.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
>>>>>>>>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
>>>>>>>>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
>>>>>>>>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
>>>>>>>>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
>>>>>>>>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
>>>>>>>>>>>>>>>>>> correctly updated.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
>>>>>>>>>>>>>>>>>> edid, which is not so awesome :-)
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @lost_hotplug:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
>>>>>>>>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
>>>>>>>>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
>>>>>>>>>>>>>>>>>>>> +	 * HDMI bridges.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_enable:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
>>>>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
>>>>>>>>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_disable.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
>>>>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_disable:
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
>>>>>>>>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
>>>>>>>>>>>>>>>>>>>> +	 * connection status occurs.
>>>>>>>>>>>>>>>>>>>> +	 *
>>>>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
>>>>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
>>>>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
>>>>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
>>>>>>>>>>>>>>>>>>>>  	bool dual_link;
>>>>>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>> +/**
>>>>>>>>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
>>>>>>>>>>>>>>>>>>>> + */
>>>>>>>>>>>>>>>>>>>> +enum drm_bridge_ops {
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
>>>>>>>>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
>>>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
>>>>>>>>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
>>>>>>>>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
>>>>>>>>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
>>>>>>>>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
>>>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
>>>>>>>>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
>>>>>>>>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
>>>>>>>>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
>>>>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>>  /**
>>>>>>>>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
>>>>>>>>>>>>>>>>>>>>   */
>>>>>>>>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
>>>>>>>>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
>>>>>>>>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
>>>>>>>>>>>>>>>>>>>>  	void *driver_private;
>>>>>>>>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
>>>>>>>>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
>>>>>>>>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
>>>>>>>>>>>>>>>>>>>> +	 * identifies the type of connected display.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	int type;
>>>>>>>>>>>>>>>>>>>> +	/** private: */
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	struct mutex hpd_mutex;
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
>>>>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
>>>>>>>>>>>>>>>>>>>> +	/**
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
>>>>>>>>>>>>>>>>>>>> +	 * @hpd_cb.
>>>>>>>>>>>>>>>>>>>> +	 */
>>>>>>>>>>>>>>>>>>>> +	void *hpd_data;
>>>>>>>>>>>>>>>>>>>>  };
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
>>>>>>>>>>>>>>>>>>>>  
>>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
>>>>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
>>>>>>>>>>>>>>>>>>>> +			   void *data);
>>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
>>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
>>>>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
>>>>>>>>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
>>>>>>>>>>>>>>>>>>>>  					u32 connector_type);


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 10/60] drm/bridge: Add bridge driver for display connectors
  2019-07-07 18:18   ` [PATCH 10/60] drm/bridge: Add bridge driver for display connectors Laurent Pinchart
  2019-07-16  9:28     ` Sam Ravnborg
@ 2019-09-30 11:15     ` Tomi Valkeinen
  2019-10-01 20:08       ` Laurent Pinchart
  1 sibling, 1 reply; 166+ messages in thread
From: Tomi Valkeinen @ 2019-09-30 11:15 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: Maxime Ripard, Sebastian Reichel, Sean Paul

Hi Laurent,

On 07/07/2019 21:18, Laurent Pinchart wrote:
> Display connectors are modelled in DT as a device node, but have so far
> been handled manually in several bridge drivers. This resulted in
> duplicate code in several bridge drivers, with slightly different (and
> thus confusing) logics.
> 
> In order to fix this, implement a bridge driver for display connectors.
> The driver centralises logic for the DVI, HDMI, VGAn composite and
> S-video connectors and exposes corresponding bridge operations.
> 
> This driver in itself doesn't solve the issue completely, changes in
> bridge and display controller drivers are needed to make use of the new
> connector driver.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>   drivers/gpu/drm/bridge/Kconfig             |  11 +
>   drivers/gpu/drm/bridge/Makefile            |   1 +
>   drivers/gpu/drm/bridge/display-connector.c | 327 +++++++++++++++++++++
>   3 files changed, 339 insertions(+)
>   create mode 100644 drivers/gpu/drm/bridge/display-connector.c


> +	dev_info(&pdev->dev,
> +		 "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
> +		 display_connector_type_name(conn),
> +		 conn->label ? conn->label : "<unlabelled>",
> +		 conn->ddc ? "with" : "without",
> +		 conn->hpd_gpio ? "with" : "without",
> +		 conn->bridge.ops);

dev_dbg()?

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 10/60] drm/bridge: Add bridge driver for display connectors
  2019-09-30 11:15     ` Tomi Valkeinen
@ 2019-10-01 20:08       ` Laurent Pinchart
  2019-10-02 12:38         ` Tomi Valkeinen
  0 siblings, 1 reply; 166+ messages in thread
From: Laurent Pinchart @ 2019-10-01 20:08 UTC (permalink / raw)
  To: Tomi Valkeinen; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

Hi Tomi,

On Mon, Sep 30, 2019 at 02:15:20PM +0300, Tomi Valkeinen wrote:
> On 07/07/2019 21:18, Laurent Pinchart wrote:
> > Display connectors are modelled in DT as a device node, but have so far
> > been handled manually in several bridge drivers. This resulted in
> > duplicate code in several bridge drivers, with slightly different (and
> > thus confusing) logics.
> > 
> > In order to fix this, implement a bridge driver for display connectors.
> > The driver centralises logic for the DVI, HDMI, VGAn composite and
> > S-video connectors and exposes corresponding bridge operations.
> > 
> > This driver in itself doesn't solve the issue completely, changes in
> > bridge and display controller drivers are needed to make use of the new
> > connector driver.
> > 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >   drivers/gpu/drm/bridge/Kconfig             |  11 +
> >   drivers/gpu/drm/bridge/Makefile            |   1 +
> >   drivers/gpu/drm/bridge/display-connector.c | 327 +++++++++++++++++++++
> >   3 files changed, 339 insertions(+)
> >   create mode 100644 drivers/gpu/drm/bridge/display-connector.c
> 
> 
> > +	dev_info(&pdev->dev,
> > +		 "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
> > +		 display_connector_type_name(conn),
> > +		 conn->label ? conn->label : "<unlabelled>",
> > +		 conn->ddc ? "with" : "without",
> > +		 conn->hpd_gpio ? "with" : "without",
> > +		 conn->bridge.ops);
> 
> dev_dbg()?

Many drivers print an info message at probe time when everything goes
fine, to inform about the device that has been succesfully probed. Do
you think this is overkill and a dev_dbg() would be better ?

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 10/60] drm/bridge: Add bridge driver for display connectors
  2019-10-01 20:08       ` Laurent Pinchart
@ 2019-10-02 12:38         ` Tomi Valkeinen
  0 siblings, 0 replies; 166+ messages in thread
From: Tomi Valkeinen @ 2019-10-02 12:38 UTC (permalink / raw)
  To: Laurent Pinchart; +Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Sean Paul

On 01/10/2019 23:08, Laurent Pinchart wrote:

> Many drivers print an info message at probe time when everything goes
> fine, to inform about the device that has been succesfully probed. Do
> you think this is overkill and a dev_dbg() would be better ?

Ah, I didn't realize this is a "probed" message. Yep, those prints 
sometimes help. I don't have strong feelings here, but usually I like 
the drivers to be quiet if everything is ok.

  Tomi

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

* Re: [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data
  2019-08-29 16:48                                           ` Andrzej Hajda
@ 2019-12-02 15:40                                             ` Laurent Pinchart
  0 siblings, 0 replies; 166+ messages in thread
From: Laurent Pinchart @ 2019-12-02 15:40 UTC (permalink / raw)
  To: Andrzej Hajda
  Cc: Maxime Ripard, Sebastian Reichel, dri-devel, Tomi Valkeinen, Sean Paul

Hi Andrzej,

On Thu, Aug 29, 2019 at 06:48:42PM +0200, Andrzej Hajda wrote:
> On 26.08.2019 18:27, Laurent Pinchart wrote:
> > On Thu, Aug 22, 2019 at 02:17:16PM +0200, Andrzej Hajda wrote:
> >> On 20.08.2019 00:45, Laurent Pinchart wrote:
> >>> On Mon, Aug 19, 2019 at 10:38:35AM +0200, Andrzej Hajda wrote:
> >>>> On 14.08.2019 14:40, Daniel Vetter wrote:
> >>>>> On Wed, Aug 14, 2019 at 01:04:03PM +0300, Laurent Pinchart wrote:
> >>>>>> On Wed, Aug 14, 2019 at 08:23:12AM +0200, Andrzej Hajda wrote:
> >>>>>>> On 11.08.2019 00:43, Laurent Pinchart wrote:
> >>>>>>>> On Fri, Aug 09, 2019 at 01:55:53PM +0200, Andrzej Hajda wrote:
> >>>>>>>>> On 08.08.2019 21:32, Laurent Pinchart wrote:
> >>>>>>>>>> On Tue, Jul 16, 2019 at 03:57:21PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>> On 16.07.2019 11:00, Daniel Vetter wrote:
> >>>>>>>>>>>> On Fri, Jul 12, 2019 at 11:01:38AM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>> On 11.07.2019 17:50, Daniel Vetter wrote:
> >>>>>>>>>>>>>> On Thu, Jul 11, 2019 at 05:12:26PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>>>> On 11.07.2019 15:18, Daniel Vetter wrote:
> >>>>>>>>>>>>>>>> On Thu, Jul 11, 2019 at 02:41:01PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>>>>>> On 11.07.2019 09:35, Daniel Vetter wrote:
> >>>>>>>>>>>>>>>>>> On Wed, Jul 10, 2019 at 02:12:14PM +0200, Andrzej Hajda wrote:
> >>>>>>>>>>>>>>>>>>> Hi Laurent,
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> I like the approach, current practice when almost every bridge should
> >>>>>>>>>>>>>>>>>>> optionally implement connector, or alternatively downstream bridge or
> >>>>>>>>>>>>>>>>>>> panel is very painful.
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Yeah I think this looks mostly reasonable. Some api design comments on top
> >>>>>>>>>>>>>>>>>> of Andrzej', with the fair warning that I didn't bother to read up on how
> >>>>>>>>>>>>>>>>>> it's all used in the end. I probably should go and do that, at least to
> >>>>>>>>>>>>>>>>>> get a feeling for what your hpd_cb usually does.
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> More comments inlined.
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> On 07.07.2019 20:18, Laurent Pinchart wrote:
> >>>>>>>>>>>>>>>>>>>> To support implementation of DRM connectors on top of DRM bridges
> >>>>>>>>>>>>>>>>>>>> instead of by bridges, the drm_bridge needs to expose new operations and
> >>>>>>>>>>>>>>>>>>>> data:
> >>>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> - Output detection, hot-plug notification, mode retrieval and EDID
> >>>>>>>>>>>>>>>>>>>>   retrieval operations
> >>>>>>>>>>>>>>>>>>>> - Bitmask of supported operations
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> Why do we need these bitmask at all? Why cannot we rely on presence of
> >>>>>>>>>>>>>>>>>>> operation's callback?
> >>>>>>>>>>>>>>>>>> Yeah also not a huge fan of these bitmasks. Smells like
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> DRIVER_GEM|DRIVER_MODESET, and I personally really hate those. Easy to
> >>>>>>>>>>>>>>>>>> add, generally good excuse to not have to think through the design between
> >>>>>>>>>>>>>>>>>> different parts of drivers - "just" add another flag.
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> - Bridge output type
> >>>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> Add and document these.
> >>>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> Three new bridge helper functions are also added to handle hot plug
> >>>>>>>>>>>>>>>>>>>> notification in a way that is as transparent as possible for the
> >>>>>>>>>>>>>>>>>>>> bridges.
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> Documentation of new opses does not explain how it should cooperate with
> >>>>>>>>>>>>>>>>>>> bridge chaining, I suppose they should be chained explicitly, am I
> >>>>>>>>>>>>>>>>>>> right? More comments about it later.
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >>>>>>>>>>>>>>>>>>>> ---
> >>>>>>>>>>>>>>>>>>>>  drivers/gpu/drm/drm_bridge.c |  92 +++++++++++++++++++
> >>>>>>>>>>>>>>>>>>>>  include/drm/drm_bridge.h     | 170 ++++++++++++++++++++++++++++++++++-
> >>>>>>>>>>>>>>>>>>>>  2 files changed, 261 insertions(+), 1 deletion(-)
> >>>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>>>>>> index 519577f363e3..3c2a255df7af 100644
> >>>>>>>>>>>>>>>>>>>> --- a/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>>>>>> +++ b/drivers/gpu/drm/drm_bridge.c
> >>>>>>>>>>>>>>>>>>>> @@ -70,6 +70,8 @@ static LIST_HEAD(bridge_list);
> >>>>>>>>>>>>>>>>>>>>   */
> >>>>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>>>>>  {
> >>>>>>>>>>>>>>>>>>>> +	mutex_init(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>>>>>>>>  	list_add_tail(&bridge->list, &bridge_list);
> >>>>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>>>>>>>>> @@ -86,6 +88,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>>>>>  	mutex_lock(&bridge_lock);
> >>>>>>>>>>>>>>>>>>>>  	list_del_init(&bridge->list);
> >>>>>>>>>>>>>>>>>>>>  	mutex_unlock(&bridge_lock);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	mutex_destroy(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>>  }
> >>>>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_bridge_remove);
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>> @@ -463,6 +467,94 @@ void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>>  }
> >>>>>>>>>>>>>>>>>>>>  EXPORT_SYMBOL(drm_atomic_bridge_enable);
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_enable - enable hot plug detection for the bridge
> >>>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>>>>>> + * @cb: hot-plug detection callback
> >>>>>>>>>>>>>>>>>>>> + * @data: data to be passed to the hot-plug detection callback
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_enable and register the given @cb and @data as
> >>>>>>>>>>>>>>>>>>>> + * hot plug notification callback. From now on the @cb will be called with
> >>>>>>>>>>>>>>>>>>>> + * @data when an output status change is detected by the bridge, until hot plug
> >>>>>>>>>>>>>>>>>>>> + * notification gets disabled with drm_bridge_hpd_disable().
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * Only one hot plug detection callback can be registered at a time, it is an
> >>>>>>>>>>>>>>>>>>>> + * error to call this function when hot plug detection is already enabled for
> >>>>>>>>>>>>>>>>>>>> + * the bridge.
> >>>>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> To simplify architecture maybe would be better to enable hpd just on
> >>>>>>>>>>>>>>>>>>> bridge attach:
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> bridge->hpd_cb = cb;
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> bridge->hpd_data = data;
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> ret = drm_bridge_attach(...);
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Yeah I like this more. The other problem here is, what if you need more
> >>>>>>>>>>>>>>>>>> than 1 callback registers on the same bridge hdp signal?
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> This way we could avoid adding new callbacks hpd_(enable|disable)
> >>>>>>>>>>>>>>>>>>> without big sacrifices.
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> One more thing: HPD in DisplayPort/HDMI beside signalling plug/unplug,
> >>>>>>>>>>>>>>>>>>> notifies about sink status change, how it translates to this cb?
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>>>>>>>>> +			   void *data)
> >>>>>>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_enable)
> >>>>>>>>>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
> >>>>>>>>>>>>>>>>>>>> +		goto unlock;
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = cb;
> >>>>>>>>>>>>>>>>>>>> +	bridge->hpd_data = data;
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_enable(bridge);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +unlock:
> >>>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_disable - disable hot plug detection for the bridge
> >>>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * Call &drm_bridge_funcs.hpd_disable and unregister the hot plug detection
> >>>>>>>>>>>>>>>>>>>> + * callback previously registered with drm_bridge_hpd_enable(). Once this
> >>>>>>>>>>>>>>>>>>>> + * function returns the callback will not be called by the bridge when an
> >>>>>>>>>>>>>>>>>>>> + * output status change occurs.
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
> >>>>>>>>>>>>>>>>>>>> + * bridge->ops. This function shall not be called when the flag is not set.
> >>>>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge)
> >>>>>>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>>>>>> +	if (!bridge || !bridge->funcs->hpd_disable)
> >>>>>>>>>>>>>>>>>>>> +		return;
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +	bridge->funcs->hpd_disable(bridge);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	bridge->hpd_cb = NULL;
> >>>>>>>>>>>>>>>>>>>> +	bridge->hpd_data = NULL;
> >>>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>>>> + * drm_bridge_hpd_notify - notify hot plug detection events
> >>>>>>>>>>>>>>>>>>>> + * @bridge: bridge control structure
> >>>>>>>>>>>>>>>>>>>> + * @status: output connection status
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * Bridge drivers shall call this function to report hot plug events when they
> >>>>>>>>>>>>>>>>>>>> + * detect a change in the output status, when hot plug detection has been
> >>>>>>>>>>>>>>>>>>>> + * enabled by the &drm_bridge_funcs.hpd_enable callback.
> >>>>>>>>>>>>>>>>>>>> + *
> >>>>>>>>>>>>>>>>>>>> + * This function shall be called in a context that can sleep.
> >>>>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status)
> >>>>>>>>>>>>>>>>>>>> +{
> >>>>>>>>>>>>>>>>>>>> +	mutex_lock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +	if (bridge->hpd_cb)
> >>>>>>>>>>>>>>>>>>>> +		bridge->hpd_cb(bridge->hpd_data, status);
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> So this isn't quite what I had in mind. Instead something like this:
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> 	/* iterates over all bridges in the chain containing @bridge */
> >>>>>>>>>>>>>>>>>> 	for_each_bridge(tmp_bridge, bridge) {
> >>>>>>>>>>>>>>>>>> 		if (tmp_bridge == bridge)
> >>>>>>>>>>>>>>>>>> 			continue;
> >>>>>>>>>>>>>>>>>> 		if (bridge->hpd_notify);
> >>>>>>>>>>>>>>>>>> 			bridge->hpd_notify(tmp_bridge, bridge, status);
> >>>>>>>>>>>>>>>>>> 	}
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> 	encoder = encoder_for_bridge(bridge);
> >>>>>>>>>>>>>>>>>> 	if (encoder->helper_private->bridge_hpd_notify)
> >>>>>>>>>>>>>>>>>> 		encoder->helper_private->bridge_hpd_notify(encoder, bridge, status);
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> 	dev = bridge->dev
> >>>>>>>>>>>>>>>>>> 	if (dev->mode_config.helper_private->bridge_hpd_notify)
> >>>>>>>>>>>>>>>>>> 		dev->mode_config.helper_private->bridge_hpd_notify(dev, bridge, status)
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> No register callback needed, no locking needed, everyone gets exactly the
> >>>>>>>>>>>>>>>>>> hpd they want/need.
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> As I understand you want to notify every member of the pipeline.
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> I think it should be enough to notify only the source, and then source
> >>>>>>>>>>>>>>>>> should decide if/when the hpd should be propagated upstream.
> >>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>> It looks more generic for me.
> >>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>> I'm not parsing ... do you think my idea is more generic and useful, or
> >>>>>>>>>>>>>>>> the one from Laurent? Kinda confused here.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> Regarding general idea:
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> 1. Laurent's approach is to notify only consumer, I guess usually video
> >>>>>>>>>>>>>>> source.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> 2. Your is to notify all other bridges and encoder.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> And I prefer 1st approach, why:
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> - the source can decide if/when and to who propagate the signal,
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> - is more generic, for example if bridge send signal to two
> >>>>>>>>>>>>>>> monitors/panels, it can delay hpd propagation till both sinks are present,
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> With Laurent's approach the bridge cannot send the hpd to more than one
> >>>>>>>>>>>>>> consumer. There's only 1 callback. So you're example doesn't work.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> If there will be two consumers, there will be two bridge attachments,
> >>>>>>>>>>>>> thus there will be two notifications, it should work.
> >>>>>>>>>>>>
> >>>>>>>>>>>> 2 consumers, 1 producer. There's only _one_ callback in the producer. The
> >>>>>>>>>>>> callback is registered on the produce bridge, not on the consumer bridge
> >>>>>>>>>>>> (or I'm totallly misreading what Laurent does here).
> >>>>>>>>>>>
> >>>>>>>>>>> I have assumed that if devices exposes two hardware sink interfaces it
> >>>>>>>>>>> will expose two separate bridges - of course it will not work with
> >>>>>>>>>>> "bridge chaining" thing, but this is a different story.
> >>>>>>>>>>
> >>>>>>>>>> Daniel is right that the current implementation only allows one
> >>>>>>>>>> consumer. This is however not a limitation of the API, but of its
> >>>>>>>>>> implementation, as I only needed a single consumer. The helpers in this
> >>>>>>>>>> series ensure that neither the consumer nor the producer poke in the
> >>>>>>>>>> drm_bridge structure to call back to the HPD handler:
> >>>>>>>>>>
> >>>>>>>>>> - The consumer calls drm_bridge_hpd_enable() and
> >>>>>>>>>>   drm_bridge_hpd_disable(), which could offer a reference-counted
> >>>>>>>>>>   behaviour if desired without changes to the consumer.
> >>>>>>>>>>
> >>>>>>>>>> - The producer gets configured by .hpd_enable() and .hpd_disable(),
> >>>>>>>>>>   which could also easily accommodate reference-counting in the drm
> >>>>>>>>>>   bridge core without changes to the producer.
> >>>>>>>>>>
> >>>>>>>>>> - The producer notifies HPD with drm_bridge_hpd_notify(), which could
> >>>>>>>>>>   easily be extended to support multiple consumers without changes to
> >>>>>>>>>>   the producer.
> >>>>>>>>>>
> >>>>>>>>>> This is actually my second version of the HPD mechanism. The first
> >>>>>>>>>> version was never posted, poked into drm_bridge, and required the
> >>>>>>>>>> producer to be aware of the callbacks. After discussing this privately
> >>>>>>>>>> with Daniel, I came up with the implementation in this series that,
> >>>>>>>>>> while not supporting multiple consumers now, makes it easy to extend
> >>>>>>>>>> later without minimal effort.
> >>>>>>>>>>
> >>>>>>>>>> Daniel's proposed implementation above looks reasonable to me, provided
> >>>>>>>>>> we can iterate over the bridges in an order that don't depend on the
> >>>>>>>>>> position of the producer in the chain (should be easy to solve by
> >>>>>>>>>> starting at the encoder for instance). It however looks a bit like a
> >>>>>>>>>> midlayer to me :-) That's why I have a similar implementation in the
> >>>>>>>>>> connector-bridge helper, which could be extended to call
> >>>>>>>>>> encoder->helper_private->bridge_hpd_notify() and
> >>>>>>>>>> dev->mode_config.helper_private->bridge_hpd_notify() instead of
> >>>>>>>>>> hardcoding drm_kms_helper_hotplug_event(). Moving the code to
> >>>>>>>>>> drm_bridge_hpd_notify() would on the other hand set the notification
> >>>>>>>>>> sequence towards the encoder and driver in stone. Daniel, do you think
> >>>>>>>>>> that would be better ?
> >>>>>>>>>>
> >>>>>>>>>> I would like to remind everybody that this series isn't the last I will
> >>>>>>>>>> ever submit, and I plan to do more work on drm_bridge and drm_panel. I'm
> >>>>>>>>>> open to suggestions, and can address problems on top of these patches,
> >>>>>>>>>> provided obviously that this series doesn't go in the wrong direction.
> >>>>>>>>>> I'm of course also willing to rework this series, but given the amount
> >>>>>>>>>> of work we have in the drm_bridge realm, I can't fix everything in one
> >>>>>>>>>> go :-)
> >>>>>>>>>>
> >>>>>>>>>>>>>>> - it resembles hardware wires :)
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> This isn't for the hw wires afaiui. The hw hpd terminates in the source
> >>>>>>>>>>>>>> bridge, which then calls drm_bridge_hpd_notify() to inform anyone else
> >>>>>>>>>>>>>> interested in that hpd singal. This includes:
> >>>>>>>>>>>>>> - Other bridges, e.g. if they provide CEC support.
> >>>>>>>>>>>>>> - Other bridges, maybe they need to re-run the HDCP state engine
> >>>>>>>>>>>>>> - Overall driver, so it can update the modes/connector status and send the
> >>>>>>>>>>>>>>   uevent to the driver.
> >>>>>>>>>>>>>> - Overall display pipeline for this specific bridge, maybe you need to
> >>>>>>>>>>>>>>   shut down/re-enable the pipe because $reasons.
> >>>>>>>>>>>>>>  
> >>>>>>>>>>>>>> That's at least my understanding from lots of chats with Laurent about
> >>>>>>>>>>>>>> what he wants to do here.
> >>>>>>>>>>
> >>>>>>>>>> That's correct, and that's what I was trying to implement :-) The
> >>>>>>>>>> notification, in this patch series, goes from the producer bridge to a
> >>>>>>>>>> central place (namely the connector, with a helper implementation
> >>>>>>>>>> available as part of this series, but custom implementations in display
> >>>>>>>>>> drivers are fine if needed) that then dispatches the notification to all
> >>>>>>>>>> bridges (through the .lost_hotplug() operation, which we could replace
> >>>>>>>>>> by an .hpd_notify() operation) for the first two purposes listed above,
> >>>>>>>>>> and then to the overall driver. The only thing I don't support yet is
> >>>>>>>>>> dispatching to the display pipeline (item 4 in the list above) as I had
> >>>>>>>>>> no need for that, and didn't want to develop an API with no user. This
> >>>>>>>>>> would however not be difficult to do when needed, the need is taken into
> >>>>>>>>>> account in the proposed implementation.
> >>>>>>>>>>
> >>>>>>>>>>>>> I do not know the full picture, but the solution where particular bridge
> >>>>>>>>>>>>> notifies everything unconditionally seems to me much less flexible.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> If HPD signals is received by the consumer, if there are no obstacles it
> >>>>>>>>>>>>> can propagate it further, upstream bridge/encoder or to drm core - it
> >>>>>>>>>>>>> will mimic your scenario.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> But there are also other scenarios where bridge does not want to
> >>>>>>>>>>>>> propagate signal, because for example:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - it wants to wait for other sinks to wake up,
> >>>>>>>>>>>>
> >>>>>>>>>>>> The other sink can just do that in their hpd callback.
> >>>>>>>>>>>>
> >>>>>>>>>>>>> - it propagates HPD signal via hardware wire,
> >>>>>>>>>>>>
> >>>>>>>>>>>> Again, the other sink can just not listen to sw hpd in that case, and use
> >>>>>>>>>>>> the wire/hw hpd interrupt.
> >>>>>>>>>>>
> >>>>>>>>>>> If it should ignore HPD, why it should receive it at all - it is
> >>>>>>>>>>> unnecessary noise. And I am afraid with more complicated pipelines it
> >>>>>>>>>>> will be impossible for particular component (bridge/encoder/whatever) to
> >>>>>>>>>>> distinguish if HPD notification which came from non-directly connected
> >>>>>>>>>>> component should be ignored or not.
> >>>>>>>>>>>
> >>>>>>>>>>>>> - first it wants to verify if the sink is valid/compatible/authorized
> >>>>>>>>>>>>> device.
> >>>>>>>>>>>>
> >>>>>>>>>>>> Now you lost me. Why would someone glue incompatible IP into a SoC or
> >>>>>>>>>>>> board?
> >>>>>>>>>>>
> >>>>>>>>>>> Bridge can have external connectors, and the user can connect there
> >>>>>>>>>>> anything.
> >>>>>>>>>>>
> >>>>>>>>>>>>> In general HPD is input signal for notify of state changes on particular
> >>>>>>>>>>>>> bus, in case of typical video bridge on its output video bus.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> In case of bridges they have also input video buses, and they can send
> >>>>>>>>>>>>> HPD signal via this bus, but this is indeed different HPD signal, even
> >>>>>>>>>>>>> if for most cases they looks similar.
> >>>>>>>>>>>>
> >>>>>>>>>>>> Ah, I think this is a problem we will eventually have. But it's not
> >>>>>>>>>>>> something we're currently solving here at all I think.
> >>>>>>>>>>>
> >>>>>>>>>>> Currently sii8620 device in tm2 sends hpd signal upstream via hardware
> >>>>>>>>>>> line, so this is not something from far future. And I guess with HPD
> >>>>>>>>>>> broadcasting it could be racy/error prone, for example EDID reading can
> >>>>>>>>>>> fail due to bridge being not ready (ddc of sii8620 is connected to i2c
> >>>>>>>>>>> controller via hw wires also).
> >>>>>>>>>>>
> >>>>>>>>>>>>>>> And regarding implementation:
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> 1. Laurent proposes to register callback drm_bridge_hpd_enable.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> 2. You propose to add ops hpd_notify in bridges and encoders.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> Your proposition is more straightforward, but if we want to notify only
> >>>>>>>>>>>>>>> source we should locate it by parsing notification chain (what about
> >>>>>>>>>>>>>>> unchained bridges), or store pointer somewhere during attachment.
> >>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>> It still leaves us with this ugly dualism - source is encoder or bridge,
> >>>>>>>>>>>>>>> similarly to sink as bridge or panel, but fixing it can be done later.
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>> Uh I think we're not talking about the same thing really. My understanding
> >>>>>>>>>>>>>> is that this callback is if someone (outside of this bridge) is interested
> >>>>>>>>>>>>>> in a hpd signal _from_ this bridge. Which means you can only ever have 1
> >>>>>>>>>>>>>> listener.
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> Do we have real life examples?
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> I want to distinguish two situations:
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - another device wants to know if input bus of the bridge has changed state,
> >>>>>>>>>>>>>
> >>>>>>>>>>>>> - another device wants to know if output bus of the bridge has changed
> >>>>>>>>>>>>> state.
> >>>>>>>>>>>>
> >>>>>>>>>>>> Uh, that's what drm_bridge_state is for (if it ever happens). That's how
> >>>>>>>>>>>> bridges can exchange state and information about each another. hpd is
> >>>>>>>>>>>> about the physical world, i.e. "is there a cable plugged into the port
> >>>>>>>>>>>> I'm driving?". We're not going to use fake hpd to update bridge state and
> >>>>>>>>>>>> fun stuff like that, we have the atomic_check machinery for this.
> >>>>>>>>>>>
> >>>>>>>>>>> My question was if we have real examples that upstream device requires
> >>>>>>>>>>> knowledge about state of output line of the bridge?
> >>>>>>>>>>>
> >>>>>>>>>>> To be more precise, we have following display pipeline:
> >>>>>>>>>>>
> >>>>>>>>>>> A-->B-->C
> >>>>>>>>>>>
> >>>>>>>>>>> And C sends HPD to B (ie signal that state of line between B and C
> >>>>>>>>>>> changed). Does A really wants to know this information? or it should
> >>>>>>>>>>> just need to know if state of line A-->B changed?
> >>>>>>>>>>
> >>>>>>>>>> There's one real life example, where A is an HDMI encoder, B is an HDMI
> >>>>>>>>>> ESD protector and level shifter, and C is the physical HDMI connector.
> >>>>>>>>>> When the HDMI cable is unplugged, the CEC controller part of A needs to
> >>>>>>>>>> be notified in order to reset the CEC state machine. One could however
> >>>>>>>>>> argue that in that case the A-B link state changes too, but the
> >>>>>>>>>> important part is that HPD detection is not performed by A, while A
> >>>>>>>>>> needs to be informed of lost hotplug.
> >>>>>>>>>
> >>>>>>>>> I have no full picture but I guess in this case C sends HPD to B using
> >>>>>>>>> hardware wire, and then B sends HPD to A also via wire, so I wouldn't
> >>>>>>>>> say that B does not participate in HPD transmission/forwarding,
> >>>>>>>>
> >>>>>>>> No, in this case A doesn't receive any hardware HPD signal, it requires
> >>>>>>>> HPD notification through software.
> >>>>>>>>
> >>>>>>>>> some shifters with 'advanced power saving' can even perform wake-up of
> >>>>>>>>> upstream pin logic after receiving HPD on downstream, so HPD sent from B
> >>>>>>>>> to A is indeed different than HPD sent from C to B.
> >>>>>>>>>
> >>>>>>>>> Btw, with the above logic of propagation of HPD callback (proposed by
> >>>>>>>>> Daniel) I guess it will work this way:
> >>>>>>>>>
> >>>>>>>>> - A will receive HPD signal via HW,
> >>>>>>>>>
> >>>>>>>>> - then B and C will receive HPD callback via framework.
> >>>>>>>>>
> >>>>>>>>> Am I right?
> >>>>>>>>
> >>>>>>>> It's the other way around.
> >>>>>>>>
> >>>>>>>> In this case the HPD signal from the connector (C) is routed to an input
> >>>>>>>> of the ESD chip (B). The ESD chip outputs a shifted HPD hardware signal
> >>>>>>>> connected to a GPIO of the SoC. The driver for (B) thus registers a GPIO
> >>>>>>>> IRQ and receive the hardware HPD notification. The driver for the HDMI
> >>>>>>>> encoder (A) needs to receive HPD notification in software, through the
> >>>>>>>> framework.
> >>>>>>>
> >>>>>>> If this is GPIO I wonder why do not query this gpio by encoder directly,
> >>>>>>> rules of ownership of such gpios seems to be grey area, so in such case
> >>>>>>> I would advise to put it in the driver who really needs it.
> >>>>>>>
> >>>>>>> This way it will be much simpler.
> >>>>>>
> >>>>>> First to fall, multiple drivers may need to be informed of HPD events
> >>>>>> coming from a GPIO, so we would need to duplicate it in multiple places,
> >>>>>> and I don't think the GPIO framework allows acquiring a GPIO multiple
> >>>>>> times.
> >>>>>>
> >>>>>> Then, the GPIO is described in DT, and DT doesn't care about which
> >>>>>> driver needs HPD events. DT specifies the GPIO in the node of the device
> >>>>>> it belongs to, this is defined in DT bindings, and must be the same on
> >>>>>> all boards, while depending on the board different devices may need to
> >>>>>> be informed of HPD events.
> >>>>>>
> >>>>>> For those two reasons HPD GPIO handling and consumption of HPD events
> >>>>>> can't always be grouped in the same driver.
> >>>>>>
> >>>>>>> Going back to HPD notifications, as I said earlier broadcasting HPD
> >>>>>>> notification unconditionally to every member of the chain with hope that
> >>>>>>> the member will be able to filter-out undesired notification seems to me
> >>>>>>> incorrect - maybe it can solve some problems but is not flexible enough
> >>>>>>> to be usable in other scenarios.
> >>>>>>>
> >>>>>>> If my arguments do not convince you please just continue with your
> >>>>>>> ideas, we can always add NO_HPD_BROADCAST somewhere :)
> >>>>>>
> >>>>>> :-) I would like to understand the problems you're referring to though,
> >>>>>> and hopefully solve them. If you could describe one of the scenarios
> >>>>>> where you think this mechanism wouldn't be usable that would help. In
> >>>>>> the meantime I will post a new version of the series with these
> >>>>>> operations kept as-is to get the rest of the patches reviewed.
> >>>>>
> >>>>> See my little thing about midlayers, I think midlayers with lots of flags
> >>>>> for everything aren't a good idea. They should be more opinionated about
> >>>>> how things work.
> >>>>>
> >>>>> So if there's a case where this broadcasting of various things doesn't
> >>>>> work, let's dig into it.
> >>>>
> >>>> OK, almost real life example:
> >>>>
> >>>> A -> B -> C
> >>>>
> >>>> A - RGB/HDMI converter,
> >>>>
> >>>> B - HDMI/MHL converter,
> >>>>
> >>>> C - uUSB controller (MUIC).
> >>>>
> >>>>
> >>>> C - detects presence of MHL sink and routes MHL lines to B in such case.
> >>>>
> >>>> B - has no hardware logic to detect HPD, but it's firmware can read EDID
> >>>> from downstream component via HW lines and it has hardware lines to
> >>>> upstream component to send EDID,
> >>>>
> >>>> A - can read EDID from B via hardware lines, but does not have hardware HPD.
> >>>
> >>> It probably doesn't matter much for the overall discussion, but out of
> >>> curiosity, does B have a CBUS interface towards C and a DDC (I2C)
> >>> interface towards A ?
> >>
> >> Yes, but C (MUIC) is not MHL aware, beside initial 1K resistance
> >> detection on ID pin, AFAIK.
> >>
> >>>  And does A read the EDID on DDC and expose it
> >>> towards the SoC through a custom protocol (for instance as the ADV7511
> >>> does), or does it forward the DDC lines to the SoC ?
> >>>
> >>>> So how it should work (according to specification):
> >>>>
> >>>> 1. C detects MHL sink.
> >>>>
> >>>> 2. C switches his mux to route lines to B.
> >>>>
> >>>> 3. C sends HPD notification to B.
> >>>>
> >>>> 4. B powers on, its firmware reads EDID from downstream lines (possibly
> >>>> adjusting it) and makes it available to upstream component A.
> >>>>
> >>>> 5. B sends HPD notification to A.
> >>>>
> >>>>
> >>>> I do not know how it could work with HPD broadcasting.
> >>>>
> >>>> I guess C should be HPD provider, but in case of HPD broadcasting A and
> >>>> B would receive notification in the same time, as a result A would start
> >>>> reading EDID too early - fail.
> >>>
> >>> That's an interesting case indeed. Now I understand what you meant
> >>> earlier.
> >>>
> >>> The HPD notification from C to B is purely internal, and should not be
> >>> visible from a DRM/KMS point of view. It just happens that this hardware
> >>> setup has a more complex HPD sequence that requires software
> >>> intervention in the middle of the sequence. As such, if we forget about
> >>> this patch series for a minute, C would need a custom API to send MHL
> >>> notification to B, and the HPD for DRM/KMS would be notified by B, right
> >>> ?
> >>
> >> I just want to convince you that maybe all HPD signals
> >> (hardware/software) are purely internal (ie they should be handled only
> >> by upstream devices), and the hotplug event should be sent to
> >> userspace/drm_core only when WHOLE pipeline is ready to query modes
> >> (i2c/sideband channels/whatever is functional).
> >
> > I think that most HPD events are not internal, and that the above case
> > is more an exception than a rule :-) It should however be supported, and
> > I agree that HPD should be notified to the DRM core only when it has
> > traversed the whole pipeline, yes.
> >
> > I'd like to keep bridge drivers simple though, and avoid requiring
> > manual HPD propagation as I think that's the common case. That's why I
> > proposed blocking the propagation below. What do you think ?
> >
> > This also means that, if we switch to a model where propagation can be
> > disabled, a bridge will only notify upstream (closer to the CRTC)
> > bridges. If, in a A-B-C chain, bridge B receives the external HPD event,
> > then bridge C would never be notified. Do you think that could be an
> > issue ?
> 
> As I said somewhere earlier it should work.

I'll give it a try then.

> Btw, since bridges are currently connected via single-linked list (just
> drm_bridge->next), do you plan to switch to double linked list, to find
> upstream bridge, or add logic to discover upstream bridge on the fly?

Boris has submitted a patch series ([1]) to switch to a double-linked
list, it will be useful here.

> >>> I think it's possible to handle both the MHL notification and the
> >>> user-visible HPD notification through the same bridge API, provided that
> >>> we offer a way for a bridge to block forwarding of the HPD notification.
> >>> This will also require calling the HPD notifiers on bridges in the sink
> >>> to source order. Both are doable, the bridge HPD notifier operation
> >>> could return a bool that blocks propagation of the notification. Would
> >>> that work for you ?
> >>
> >> It could work, in this case.
> >>
> >> But it will still have problems with non-linear pipelines - where stream
> >> is split to two or more bridges/panels.
> >
> > I agree, but that's not supported by the bridge API for now. I'm not
> > sure I'm looking forward to dealing with this, but I think it will be
> > needed :-)
> 
> Currently there are two modes of usage of bridge:
> 
> - part of bridge chain,
> 
> - private bridge - it can be attached to other components via private
> pointer, not drm_encoder->bridge, nor drm_bridge->next.
> 
> Non-linear pipelines can be ( and I guess they are ) implemented using
> the latter.
>
> Anyway if we want to extend bridge API it would be good to allow usage
> of this API also with detached bridges.

Do you have any pointer to such cases ? Boris' series deals with Exynos
and VC4 that both use bridges privately, but as far as I understand they
still have linear pipelines.

[1] https://patchwork.kernel.org/cover/11207085/

> >>>>>>>>>>>>>> You seem to have some other idea here.
> >>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> +	mutex_unlock(&bridge->hpd_mutex);
> >>>>>>>>>>>>>>>>>>>> +}
> >>>>>>>>>>>>>>>>>>>> +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>>  #ifdef CONFIG_OF
> >>>>>>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>>>>>   * of_drm_find_bridge - find the bridge corresponding to the device node in
> >>>>>>>>>>>>>>>>>>>> diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>>>>>> index 08dc15f93ded..b9445aa5b1ef 100644
> >>>>>>>>>>>>>>>>>>>> --- a/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>>>>>> +++ b/include/drm/drm_bridge.h
> >>>>>>>>>>>>>>>>>>>> @@ -23,8 +23,9 @@
> >>>>>>>>>>>>>>>>>>>>  #ifndef __DRM_BRIDGE_H__
> >>>>>>>>>>>>>>>>>>>>  #define __DRM_BRIDGE_H__
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>> -#include <linux/list.h>
> >>>>>>>>>>>>>>>>>>>>  #include <linux/ctype.h>
> >>>>>>>>>>>>>>>>>>>> +#include <linux/list.h>
> >>>>>>>>>>>>>>>>>>>> +#include <linux/mutex.h>
> >>>>>>>>>>>>>>>>>>>>  #include <drm/drm_mode_object.h>
> >>>>>>>>>>>>>>>>>>>>  #include <drm/drm_modes.h>
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>> @@ -334,6 +335,110 @@ struct drm_bridge_funcs {
> >>>>>>>>>>>>>>>>>>>>  	 */
> >>>>>>>>>>>>>>>>>>>>  	void (*atomic_post_disable)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>>  				    struct drm_atomic_state *state);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @detect:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * Check if anything is attached to the bridge output.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * This callback is optional, if not implemented the bridge will be
> >>>>>>>>>>>>>>>>>>>> +	 * considered as always having a component attached to its output.
> >>>>>>>>>>>>>>>>>>>> +	 * Bridges that implement this callback shall set the
> >>>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_DETECT flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * drm_connector_status indicating the bridge output status.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	enum drm_connector_status (*detect)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @get_modes:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * Fill all modes currently valid for the sink into the &drm_connector
> >>>>>>>>>>>>>>>>>>>> +	 * with drm_mode_probed_add().
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * The @get_modes callback is mostly intended to support non-probable
> >>>>>>>>>>>>>>>>>>>> +	 * displays such as many fixed panels. Bridges that support reading
> >>>>>>>>>>>>>>>>>>>> +	 * EDID shall leave @get_modes unimplemented and implement the
> >>>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->get_edid callback instead.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_MODES flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * The number of modes added by calling drm_mode_probed_add().
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	int (*get_modes)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>> +			 struct drm_connector *connector);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @get_edid:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * Read and parse the EDID data of the connected display.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * The @get_edid callback is the preferred way of reporting mode
> >>>>>>>>>>>>>>>>>>>> +	 * information for a display connected to the bridge output. Bridges
> >>>>>>>>>>>>>>>>>>>> +	 * that support readind EDID shall implement this callback and leave
> >>>>>>>>>>>>>>>>>>>> +	 * the @get_modes callback unimplemented.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * The caller of this operation shall first verify the output
> >>>>>>>>>>>>>>>>>>>> +	 * connection status and refrain from reading EDID from a disconnected
> >>>>>>>>>>>>>>>>>>>> +	 * output.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * This callback is optional. Bridges that implement it shall set the
> >>>>>>>>>>>>>>>>>>>> +	 * DRM_BRIDGE_OP_EDID flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * RETURNS:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * An edid structure newly allocated with kmalloc() (or similar) on
> >>>>>>>>>>>>>>>>>>>> +	 * success, or NULL otherwise. The caller is responsible for freeing
> >>>>>>>>>>>>>>>>>>>> +	 * the returned edid structure with kfree().
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	struct edid *(*get_edid)(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>> +				 struct drm_connector *connector);
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> It overlaps with get_modes, I guess presence of one ops should disallow
> >>>>>>>>>>>>>>>>>>> presence of another one?
> >>>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>> I am not really convinced we need this op at all, cannot we just assign
> >>>>>>>>>>>>>>>>>>> some helper function to .get_modes cb, which will do the same?
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Plan B): ditch ->get_edid, require that the driver has ->get_modes in that
> >>>>>>>>>>>>>>>>>> case, and require that if it has an edid it must fill out connector->info
> >>>>>>>>>>>>>>>>>> and connector->edid correctly.
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Btw if a hpd happens, who's responible for making sure the edid/mode list
> >>>>>>>>>>>>>>>>>> in the connector is up-to-date? With your current callback design that's
> >>>>>>>>>>>>>>>>>> up to the callback, which doesn't feel great. Maybe  drm_bridge_hpd_notify
> >>>>>>>>>>>>>>>>>> should guarantee that it'll first walk the connectors to update status and
> >>>>>>>>>>>>>>>>>> edid/mode list for the final drm_connector. And then instead of just
> >>>>>>>>>>>>>>>>>> passing the simple "status", it'll pass the connector, with everything
> >>>>>>>>>>>>>>>>>> correctly updated.
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>> Otherwise everyone interested in that hpd signal will go and re-fetch the
> >>>>>>>>>>>>>>>>>> edid, which is not so awesome :-)
> >>>>>>>>>>>>>>>>>>
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @lost_hotplug:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * Notify the bridge of display disconnection.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * This callback is optional, it may be implemented by bridges that
> >>>>>>>>>>>>>>>>>>>> +	 * need to be notified of display disconnection for internal reasons.
> >>>>>>>>>>>>>>>>>>>> +	 * One use case is to reset the internal state of CEC controllers for
> >>>>>>>>>>>>>>>>>>>> +	 * HDMI bridges.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	void (*lost_hotplug)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_enable:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * Enable hot plug detection. From now on the bridge shall call
> >>>>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_notify() each time a change is detected in the output
> >>>>>>>>>>>>>>>>>>>> +	 * connection status, until hot plug detection gets disabled with
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_disable.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_disable callback and set
> >>>>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	void (*hpd_enable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_disable:
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * Disable hot plug detection. Once this function returns the bridge
> >>>>>>>>>>>>>>>>>>>> +	 * shall not call drm_bridge_hpd_notify() when a change in the output
> >>>>>>>>>>>>>>>>>>>> +	 * connection status occurs.
> >>>>>>>>>>>>>>>>>>>> +	 *
> >>>>>>>>>>>>>>>>>>>> +	 * This callback is optional and shall only be implemented by bridges
> >>>>>>>>>>>>>>>>>>>> +	 * that support hot-plug notification without polling. Bridges that
> >>>>>>>>>>>>>>>>>>>> +	 * implement it shall also implement the @hpd_enable callback and set
> >>>>>>>>>>>>>>>>>>>> +	 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	void (*hpd_disable)(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>>>>> @@ -372,6 +477,38 @@ struct drm_bridge_timings {
> >>>>>>>>>>>>>>>>>>>>  	bool dual_link;
> >>>>>>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>> +/**
> >>>>>>>>>>>>>>>>>>>> + * enum drm_bridge_ops - Bitmask of operations supported by the bridge
> >>>>>>>>>>>>>>>>>>>> + */
> >>>>>>>>>>>>>>>>>>>> +enum drm_bridge_ops {
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_DETECT: The bridge can detect displays connected to
> >>>>>>>>>>>>>>>>>>>> +	 * its output. Bridges that set this flag shall implement the
> >>>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->detect callback.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_DETECT = BIT(0),
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_EDID: The bridge can retrieve the EDID of the display
> >>>>>>>>>>>>>>>>>>>> +	 * connected to its output. Bridges that set this flag shall implement
> >>>>>>>>>>>>>>>>>>>> +	 * the &drm_bridge_funcs->get_edid callback.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_EDID = BIT(1),
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_HPD: The bridge can detect hot-plug and hot-unplug
> >>>>>>>>>>>>>>>>>>>> +	 * without requiring polling. Bridges that set this flag shall
> >>>>>>>>>>>>>>>>>>>> +	 * implement the &drm_bridge_funcs->hpd_enable and
> >>>>>>>>>>>>>>>>>>>> +	 * &drm_bridge_funcs->disable_hpd_cb callbacks.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_HPD = BIT(2),
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @DRM_BRIDGE_OP_MODES: The bridge can retrieving the modes supported
> >>>>>>>>>>>>>>>>>>>> +	 * by the display at its output. This does not include readind EDID
> >>>>>>>>>>>>>>>>>>>> +	 * which is separately covered by @DRM_BRIDGE_OP_EDID. Bridges that set
> >>>>>>>>>>>>>>>>>>>> +	 * this flag shall implement the &drm_bridge_funcs->get_modes callback.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	DRM_BRIDGE_OP_MODES = BIT(3),
> >>>>>>>>>>>>>>>>>>>> +};
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>>  /**
> >>>>>>>>>>>>>>>>>>>>   * struct drm_bridge - central DRM bridge control structure
> >>>>>>>>>>>>>>>>>>>>   */
> >>>>>>>>>>>>>>>>>>>> @@ -398,6 +535,29 @@ struct drm_bridge {
> >>>>>>>>>>>>>>>>>>>>  	const struct drm_bridge_funcs *funcs;
> >>>>>>>>>>>>>>>>>>>>  	/** @driver_private: pointer to the bridge driver's internal context */
> >>>>>>>>>>>>>>>>>>>>  	void *driver_private;
> >>>>>>>>>>>>>>>>>>>> +	/** @ops: bitmask of operations supported by the bridge */
> >>>>>>>>>>>>>>>>>>>> +	enum drm_bridge_ops ops;
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @type: Type of the connection at the bridge output
> >>>>>>>>>>>>>>>>>>>> +	 * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
> >>>>>>>>>>>>>>>>>>>> +	 * identifies the type of connected display.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	int type;
> >>>>>>>>>>>>>>>>>>>> +	/** private: */
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_mutex: Protects the @hpd_cb and @hpd_data fields.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	struct mutex hpd_mutex;
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_cb: Hot plug detection callback, registered with
> >>>>>>>>>>>>>>>>>>>> +	 * drm_bridge_hpd_enable().
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	void (*hpd_cb)(void *data, enum drm_connector_status status);
> >>>>>>>>>>>>>>>>>>>> +	/**
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_data: Private data passed to the Hot plug detection callback
> >>>>>>>>>>>>>>>>>>>> +	 * @hpd_cb.
> >>>>>>>>>>>>>>>>>>>> +	 */
> >>>>>>>>>>>>>>>>>>>> +	void *hpd_data;
> >>>>>>>>>>>>>>>>>>>>  };
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>>  void drm_bridge_add(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>>> @@ -428,6 +588,14 @@ void drm_atomic_bridge_pre_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>>  void drm_atomic_bridge_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>>  			      struct drm_atomic_state *state);
> >>>>>>>>>>>>>>>>>>>>  
> >>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_enable(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>> +			   void (*cb)(void *data,
> >>>>>>>>>>>>>>>>>>>> +				      enum drm_connector_status status),
> >>>>>>>>>>>>>>>>>>>> +			   void *data);
> >>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_disable(struct drm_bridge *bridge);
> >>>>>>>>>>>>>>>>>>>> +void drm_bridge_hpd_notify(struct drm_bridge *bridge,
> >>>>>>>>>>>>>>>>>>>> +			   enum drm_connector_status status);
> >>>>>>>>>>>>>>>>>>>> +
> >>>>>>>>>>>>>>>>>>>>  #ifdef CONFIG_DRM_PANEL_BRIDGE
> >>>>>>>>>>>>>>>>>>>>  struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel,
> >>>>>>>>>>>>>>>>>>>>  					u32 connector_type);

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 166+ messages in thread

end of thread, other threads:[~2019-12-02 15:40 UTC | newest]

Thread overview: 166+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-07-07 18:07 [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Laurent Pinchart
2019-07-07 18:07 ` [PATCH 01/60] drm/edid: Add flag to drm_display_info to identify HDMI sinks Laurent Pinchart
2019-07-09 13:20   ` Andrzej Hajda
2019-07-10 15:41     ` Daniel Vetter
2019-07-10 15:59   ` Ville Syrjälä
2019-07-07 18:07 ` [PATCH 02/60] video: hdmi: Change return type of hdmi_avi_infoframe_init() to void Laurent Pinchart
2019-07-07 18:14   ` Laurent Pinchart
2019-07-07 18:21     ` Ilia Mirkin
2019-07-09 13:22   ` Andrzej Hajda
2019-07-07 18:07 ` [PATCH 03/60] drm/bridge: dumb-vga-dac: Rename internal symbols to simple-bridge Laurent Pinchart
2019-07-09 13:34   ` Andrzej Hajda
2019-07-07 18:18 ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver " Laurent Pinchart
2019-07-07 18:18   ` [PATCH 05/60] drm/bridge: simple-bridge: Add support for non-VGA bridges Laurent Pinchart
2019-07-09 14:08     ` Andrzej Hajda
2019-07-26 13:24     ` Stefan Agner
2019-07-07 18:18   ` [PATCH 06/60] drm/bridge: simple-bridge: Add support for enable GPIO Laurent Pinchart
2019-07-09 14:32     ` Andrzej Hajda
2019-07-26 13:19     ` Stefan Agner
2019-07-07 18:18   ` [PATCH 07/60] drm/bridge: simple-bridge: Add support for the TI OP362 Laurent Pinchart
2019-07-09 14:32     ` Andrzej Hajda
2019-08-27  6:16     ` Tomi Valkeinen
2019-07-07 18:18   ` [PATCH 08/60] drm/bridge: Extend bridge API to disable connector creation Laurent Pinchart
2019-07-17  6:39     ` Andrzej Hajda
2019-08-08 14:25       ` Laurent Pinchart
2019-08-08 17:36         ` Andrzej Hajda
2019-08-08 18:50           ` Laurent Pinchart
2019-08-14  8:18           ` Daniel Vetter
2019-08-14  9:55             ` Laurent Pinchart
2019-08-14 12:24               ` Daniel Vetter
2019-07-07 18:18   ` [PATCH 09/60] drm/bridge: Add connector-related bridge operations and data Laurent Pinchart
2019-07-10 12:12     ` Andrzej Hajda
2019-07-11  7:35       ` Daniel Vetter
2019-07-11 12:41         ` Andrzej Hajda
2019-07-11 13:18           ` Daniel Vetter
2019-07-11 15:12             ` Andrzej Hajda
2019-07-11 15:50               ` Daniel Vetter
2019-07-12  9:01                 ` Andrzej Hajda
2019-07-16  9:00                   ` Daniel Vetter
2019-07-16 13:57                     ` Andrzej Hajda
2019-08-08 19:32                       ` Laurent Pinchart
2019-08-09 11:55                         ` Andrzej Hajda
2019-08-10 22:43                           ` Laurent Pinchart
2019-08-14  6:23                             ` Andrzej Hajda
2019-08-14 10:04                               ` Laurent Pinchart
2019-08-14 12:40                                 ` Daniel Vetter
2019-08-19  8:38                                   ` Andrzej Hajda
2019-08-19 22:45                                     ` Laurent Pinchart
2019-08-22 12:17                                       ` Andrzej Hajda
2019-08-26 16:27                                         ` Laurent Pinchart
2019-08-29 16:48                                           ` Andrzej Hajda
2019-12-02 15:40                                             ` Laurent Pinchart
2019-08-14 12:35                         ` Daniel Vetter
2019-08-19 22:32                           ` Laurent Pinchart
2019-08-20  8:30                             ` Daniel Vetter
2019-08-26 15:57                               ` Laurent Pinchart
2019-08-08 18:19         ` Laurent Pinchart
2019-08-08 18:36           ` Laurent Pinchart
2019-08-14 13:03             ` Daniel Vetter
2019-08-14 13:30               ` Laurent Pinchart
2019-08-14 17:02                 ` Daniel Vetter
2019-08-16 23:30                   ` Laurent Pinchart
2019-08-17  0:14                     ` Laurent Pinchart
2019-08-19  2:37                       ` Laurent Pinchart
2019-08-20  8:41                         ` Daniel Vetter
2019-08-14 12:43           ` Daniel Vetter
2019-08-14 12:56             ` Laurent Pinchart
2019-07-07 18:18   ` [PATCH 10/60] drm/bridge: Add bridge driver for display connectors Laurent Pinchart
2019-07-16  9:28     ` Sam Ravnborg
2019-08-08 16:41       ` Laurent Pinchart
2019-09-30 11:15     ` Tomi Valkeinen
2019-10-01 20:08       ` Laurent Pinchart
2019-10-02 12:38         ` Tomi Valkeinen
2019-07-07 18:18   ` [PATCH 11/60] drm/bridge: Add driver for the TI TPD12S015 HDMI level shifter Laurent Pinchart
     [not found]     ` <3347b6c8-6f2d-17d6-3dc8-e62a3bac634b@ti.com>
2019-08-27  7:30       ` Laurent Pinchart
2019-07-07 18:18   ` [PATCH 12/60] drm/bridge: panel: Implement bridge connector operations Laurent Pinchart
2019-07-16 11:08     ` drm_panel_get_modes() should take the connector as an argument [Was: drm/bridge: panel: Implement bridge ...] Sam Ravnborg
2019-08-08 16:07       ` Laurent Pinchart
2019-08-08 16:52         ` Sam Ravnborg
2019-08-08 18:37           ` Laurent Pinchart
2019-07-07 18:18   ` [PATCH 13/60] drm/bridge: tfp410: Don't include drmP.h Laurent Pinchart
     [not found]     ` <3bb82dc4-434a-aaac-8ea1-3aff0991e790@ti.com>
2019-08-27  7:47       ` Laurent Pinchart
2019-07-07 18:18   ` [PATCH 14/60] drm/bridge: tfp410: Replace manual connector handling with bridge Laurent Pinchart
2019-07-07 18:18   ` [PATCH 15/60] drm/bridge: tfp410: Allow operation without drm_connector Laurent Pinchart
2019-07-07 18:18   ` [PATCH 16/60] dt-bindings: Add vendor prefix for LG Display Laurent Pinchart
2019-07-24 16:22     ` Rob Herring
2019-07-07 18:18   ` [PATCH 17/60] dt-bindings: Add legacy 'toppoly' vendor prefix Laurent Pinchart
2019-07-08 19:00     ` Rob Herring
2019-07-09  1:00       ` Laurent Pinchart
2019-07-09  1:35         ` Rob Herring
2019-07-07 18:18   ` [PATCH 18/60] dt-bindings: display: panel: Add bindings for NEC NL8048HL11 panel Laurent Pinchart
2019-07-07 18:18   ` [PATCH 19/60] drm/panel: Add driver for the LG Philips LB035Q02 panel Laurent Pinchart
2019-07-08 18:51     ` Sam Ravnborg
2019-07-09  0:56       ` Laurent Pinchart
2019-07-09  5:47         ` Sam Ravnborg
2019-07-07 18:18   ` [PATCH 20/60] drm/panel: Add driver for the NEC NL8048HL11 panel Laurent Pinchart
2019-07-08 19:10     ` Sam Ravnborg
2019-07-08 19:26       ` Sam Ravnborg
2019-08-08 15:17       ` Laurent Pinchart
2019-07-07 18:18   ` [PATCH 21/60] drm/panel: Add driver for the Sharp LS037V7DW01 panel Laurent Pinchart
2019-07-08 19:44     ` Sam Ravnborg
2019-07-08 19:47       ` Sam Ravnborg
2019-08-08 15:31       ` Laurent Pinchart
2019-07-07 18:18   ` [PATCH 22/60] drm/panel: Add driver for the Sony ACX565AKM panel Laurent Pinchart
2019-07-07 18:19   ` [PATCH 23/60] drm/panel: Add driver for the Toppology TD028TTEC1 panel Laurent Pinchart
2019-07-10  7:48     ` Sam Ravnborg
2019-08-08 15:43       ` Laurent Pinchart
2019-07-07 18:19   ` [PATCH 24/60] drm/panel: Add driver for the Toppology TD043MTEA1 panel Laurent Pinchart
2019-07-10 13:09     ` Sam Ravnborg
2019-08-08 15:54       ` Laurent Pinchart
2019-08-09 13:33         ` Sam Ravnborg
2019-08-09 17:51           ` Laurent Pinchart
2019-07-07 18:19   ` [PATCH 25/60] drm: Add helper to create a connector for a chain of bridges Laurent Pinchart
2019-07-18 17:01     ` Daniel Vetter
2019-08-08 19:48       ` Laurent Pinchart
2019-08-14 15:01         ` Daniel Vetter
2019-08-19 22:16           ` Laurent Pinchart
2019-07-07 18:19   ` [PATCH 26/60] drm/omap: Detach from panels at remove time Laurent Pinchart
2019-08-13  7:28     ` Tomi Valkeinen
2019-08-13 13:50       ` Laurent Pinchart
2019-07-07 18:19   ` [PATCH 27/60] drm/omap: Simplify HDMI mode and infoframe configuration Laurent Pinchart
2019-08-13  7:29     ` Tomi Valkeinen
2019-07-07 18:19   ` [PATCH 28/60] drm/omap: Factor out display type to connector type conversion Laurent Pinchart
2019-08-13  7:32     ` Tomi Valkeinen
2019-08-13 15:18       ` Laurent Pinchart
2019-07-07 18:19   ` [PATCH 29/60] drm/omap: Use the drm_panel_bridge API Laurent Pinchart
2019-08-13  7:36     ` Tomi Valkeinen
2019-07-07 18:19   ` [PATCH 30/60] drm/omap: dss: Fix output next device lookup in DT Laurent Pinchart
2019-08-13  7:38     ` Tomi Valkeinen
2019-07-07 18:19   ` [PATCH 31/60] drm/omap: Add infrastructure to support drm_bridge local to DSS outputs Laurent Pinchart
2019-07-07 18:19   ` [PATCH 32/60] drm/omap: dss: Make omap_dss_device_ops optional Laurent Pinchart
2019-08-13  7:48     ` Tomi Valkeinen
2019-08-13 13:55       ` Laurent Pinchart
2019-07-07 18:19   ` [PATCH 33/60] drm/omap: hdmi: Allocate EDID in the .read_edid() operation Laurent Pinchart
2019-08-13  7:52     ` Tomi Valkeinen
2019-07-07 18:19   ` [PATCH 34/60] drm/omap: hdmi4: Rework EDID read to isolate data read Laurent Pinchart
2019-07-07 18:19   ` [PATCH 35/60] drm/omap: hdmi5: " Laurent Pinchart
2019-07-07 18:19   ` [PATCH 36/60] drm/omap: hdmi4: Register a drm_bridge for EDID read Laurent Pinchart
2019-07-07 18:19   ` [PATCH 37/60] drm/omap: hdmi5: " Laurent Pinchart
2019-07-07 18:19   ` [PATCH 38/60] drm/omap: hdmi4: Move mode set, enable and disable operations to bridge Laurent Pinchart
2019-07-07 18:19   ` [PATCH 39/60] drm/omap: hdmi5: " Laurent Pinchart
2019-07-07 18:19   ` [PATCH 40/60] drm/omap: hdmi4: Implement drm_bridge .lost_hotplug() operation Laurent Pinchart
2019-07-07 18:19   ` [PATCH 41/60] drm/omap: dss: Remove .set_hdmi_mode() and .set_infoframe() operations Laurent Pinchart
2019-07-07 18:19   ` [PATCH 42/60] drm/omap: venc: Register a drm_bridge Laurent Pinchart
2019-07-07 18:19   ` [PATCH 43/60] drm/omap: Create connector for bridges Laurent Pinchart
2019-07-07 18:19   ` [PATCH 44/60] drm/omap: Switch the HDMI and VENC outputs to drm_bridge Laurent Pinchart
2019-07-07 18:19   ` [PATCH 45/60] drm/omap: Remove HPD, detect and EDID omapdss operations Laurent Pinchart
2019-07-07 18:19   ` [PATCH 46/60] drm/omap: hdmi: Remove omap_dss_device operations Laurent Pinchart
2019-07-07 18:19   ` [PATCH 47/60] drm/omap: venc: " Laurent Pinchart
2019-07-07 18:19   ` [PATCH 48/60] drm/omap: hdmi4: Simplify EDID read Laurent Pinchart
2019-07-07 18:19   ` [PATCH 49/60] drm/omap: hdmi5: " Laurent Pinchart
2019-07-07 18:19   ` [PATCH 50/60] drm/omap: displays: Remove unused panel drivers Laurent Pinchart
2019-07-07 18:19   ` [PATCH 51/60] drm/omap: dpi: Sort includes alphabetically Laurent Pinchart
2019-07-07 18:19   ` [PATCH 52/60] drm/omap: dpi: Reorder functions in sections Laurent Pinchart
2019-07-07 18:19   ` [PATCH 53/60] drm/omap: dpi: Simplify clock setting API Laurent Pinchart
2019-07-07 18:19   ` [PATCH 54/60] drm/omap: dpi: Register a drm_bridge Laurent Pinchart
2019-07-07 18:19   ` [PATCH 55/60] drm/omap: sdi: Sort includes alphabetically Laurent Pinchart
2019-07-07 18:19   ` [PATCH 56/60] drm/omap: sdi: Register a drm_bridge Laurent Pinchart
2019-07-07 18:19   ` [PATCH 57/60] drm/omap: Simplify connector implementation Laurent Pinchart
2019-07-07 18:19   ` [PATCH 58/60] drm/omap: dss: Remove unused omap_dss_device operations Laurent Pinchart
2019-07-07 18:19   ` [PATCH 59/60] drm/omap: dss: Inline the omapdss_display_get() function Laurent Pinchart
2019-07-07 18:19   ` [PATCH 60/60] drm/omap: dss: Remove unused omapdss_of_find_connected_device() function Laurent Pinchart
2019-07-09 13:35   ` [PATCH 04/60] drm/bridge: dumb-vga-dac: Rename driver to simple-bridge Andrzej Hajda
2019-07-11  7:37 ` [PATCH 00/60] drm/omap: Replace custom display drivers with drm_bridge and drm_panel Daniel Vetter
2019-07-11 11:59   ` Sebastian Reichel
2019-08-08 14:26     ` Laurent Pinchart
2019-08-08 14:31   ` Laurent Pinchart

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.