From 833cad8c373f31805d5bf44929f1822ddf7a6b48 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 13:59:45 +0300 Subject: [PATCH 01/37] dt-bindings: display: xlnx: zynqmp-dpsub: Add OF graph ports The DPSUB doesn't live in isolation, but is connected to the programmable logic for live inputs and outputs, and also has a DisplayPort output. Model all those using OF graph. Signed-off-by: Laurent Pinchart Reviewed-by: Rob Herring --- .../display/xlnx/xlnx,zynqmp-dpsub.yaml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.yaml b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.yaml index 10ec78ca1c653..554f9d5809d49 100644 --- a/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.yaml +++ b/Documentation/devicetree/bindings/display/xlnx/xlnx,zynqmp-dpsub.yaml @@ -117,6 +117,45 @@ properties: - const: dp-phy0 - const: dp-phy1 + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: | + Connections to the programmable logic and the DisplayPort PHYs. Each port + shall have a single endpoint. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: The live video input from the programmable logic + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: The live graphics input from the programmable logic + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: The live audio input from the programmable logic + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: The blended video output to the programmable logic + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: The mixed audio output to the programmable logic + + port@5: + $ref: /schemas/graph.yaml#/properties/port + description: The DisplayPort output + + required: + - port@0 + - port@1 + - port@2 + - port@3 + - port@4 + - port@5 + required: - compatible - reg @@ -130,6 +169,7 @@ required: - dma-names - phys - phy-names + - ports additionalProperties: false @@ -164,6 +204,33 @@ examples: <&psgtr 0 PHY_TYPE_DP 1 3>; phy-names = "dp-phy0", "dp-phy1"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + }; + port@1 { + reg = <1>; + }; + port@2 { + reg = <2>; + }; + port@3 { + reg = <3>; + }; + port@4 { + reg = <4>; + }; + port@5 { + reg = <5>; + dpsub_dp_out: endpoint { + remote-endpoint = <&dp_connector>; + }; + }; + }; }; ... From ccce29ea4bb73fec9effcca5bafa1c9fe48e2870 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 02/37] drm: xlnx: zynqmp_dpsub: Switch to atomic encoder enable/disable To prepare for the transition to the DRM bridge API, switch the encoder operations to the atomic versions of .enable() and .disable(). This doesn't cause any functional change by itself. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index d14612b347964..10e469c92e737 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1400,7 +1400,8 @@ zynqmp_dp_connector_helper_funcs = { * DRM Encoder */ -static void zynqmp_dp_encoder_enable(struct drm_encoder *encoder) +static void zynqmp_dp_encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) { struct zynqmp_dp *dp = encoder_to_dp(encoder); unsigned int i; @@ -1432,7 +1433,8 @@ static void zynqmp_dp_encoder_enable(struct drm_encoder *encoder) zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1); } -static void zynqmp_dp_encoder_disable(struct drm_encoder *encoder) +static void zynqmp_dp_encoder_atomic_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) { struct zynqmp_dp *dp = encoder_to_dp(encoder); @@ -1509,8 +1511,8 @@ zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder, } static const struct drm_encoder_helper_funcs zynqmp_dp_encoder_helper_funcs = { - .enable = zynqmp_dp_encoder_enable, - .disable = zynqmp_dp_encoder_disable, + .atomic_enable = zynqmp_dp_encoder_atomic_enable, + .atomic_disable = zynqmp_dp_encoder_atomic_disable, .atomic_mode_set = zynqmp_dp_encoder_atomic_mode_set, .atomic_check = zynqmp_dp_encoder_atomic_check, }; From d693bd3b5b640fadc1d2d310b9bc7256cfdc557c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 03/37] drm: xlnx: zynqmp_dpsub: Constify mode argument to function The zynqmp_dp_encoder_mode_set_transfer_unit() function takes a mode pointer argument that it doesn't need to modify. Make it const. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 10e469c92e737..a2d87543d637d 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1195,7 +1195,7 @@ static int zynqmp_dp_set_format(struct zynqmp_dp *dp, */ static void zynqmp_dp_encoder_mode_set_transfer_unit(struct zynqmp_dp *dp, - struct drm_display_mode *mode) + const struct drm_display_mode *mode) { u32 tu = ZYNQMP_DP_MSA_TRANSFER_UNIT_SIZE_TU_SIZE_DEF; u32 bw, vid_kbytes, avg_bytes_per_tu, init_wait; From 47e801bd0749f0691a137255e2db35f5348f4f4f Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 04/37] drm: xlnx: zynqmp_dpsub: Create DRM bridge to model DP encoder The DP encoder is currently modelled as a DRM encoder and DRM connector. This doesn't support system configurations where the DP encoder is driven by the FPGA programmable logic, using the live video input to the DP subsystem. To enable such use cases, we need to model the encoder as a DRM bridge. As a first step, create a DRM bridge in the DP encoder driver. Move and delegate the implementation of the DRM encoder and connector operations to the bridge to prepare for the transition. The bridge will be registered with the DRM core as a separate change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 333 +++++++++++++++++++++---------- 1 file changed, 225 insertions(+), 108 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index a2d87543d637d..8acd5cfbbd1da 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -285,6 +285,7 @@ struct zynqmp_dp_config { * @iomem: device I/O memory for register access * @reset: reset controller * @irq: irq + * @bridge: DRM bridge for the DP encoder * @config: IP core configuration from DTS * @aux: aux channel * @phy: PHY handles for DP lanes @@ -307,6 +308,8 @@ struct zynqmp_dp { struct reset_control *reset; int irq; + struct drm_bridge bridge; + struct zynqmp_dp_config config; struct drm_dp_aux aux; struct phy *phy[ZYNQMP_DP_MAX_LANES]; @@ -331,6 +334,11 @@ static inline struct zynqmp_dp *connector_to_dp(struct drm_connector *connector) return container_of(connector, struct zynqmp_dp, connector); } +static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge) +{ + return container_of(bridge, struct zynqmp_dp, bridge); +} + static void zynqmp_dp_write(struct zynqmp_dp *dp, int offset, u32 val) { writel(val, dp->iomem + offset); @@ -1255,7 +1263,7 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_VSTART, mode->vtotal - mode->vsync_start); - /* In synchronous mode, set the diviers */ + /* In synchronous mode, set the dividers */ if (dp->config.misc0 & ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK) { reg = drm_dp_bw_code_to_link_rate(dp->mode.bw_code); zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_N_VID, reg); @@ -1281,13 +1289,167 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, } /* ----------------------------------------------------------------------------- - * DRM Connector + * DRM Bridge */ -static enum drm_connector_status -zynqmp_dp_connector_detect(struct drm_connector *connector, bool force) +static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) { - struct zynqmp_dp *dp = connector_to_dp(connector); + return 0; +} + +static int zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + int rate; + + if (mode->clock > ZYNQMP_MAX_FREQ) { + dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n", + mode->name); + drm_mode_debug_printmodeline(mode); + return MODE_CLOCK_HIGH; + } + + /* Check with link rate and lane count */ + rate = zynqmp_dp_max_rate(dp->link_config.max_rate, + dp->link_config.max_lanes, dp->config.bpp); + if (mode->clock > rate) { + dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n", + mode->name); + drm_mode_debug_printmodeline(mode); + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + struct drm_atomic_state *state = old_bridge_state->base.state; + const struct drm_crtc_state *crtc_state; + const struct drm_display_mode *adjusted_mode; + const struct drm_display_mode *mode; + struct drm_connector *connector; + struct drm_crtc *crtc; + unsigned int i; + int rate; + int ret; + + pm_runtime_get_sync(dp->dev); + + /* + * Retrieve the CRTC mode and adjusted mode. This requires a little + * dance to go from the bridge to the encoder, to the connector and to + * the CRTC. + */ + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + adjusted_mode = &crtc_state->adjusted_mode; + mode = &crtc_state->mode; + + zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); + + /* Check again as bpp or format might have been changed */ + rate = zynqmp_dp_max_rate(dp->link_config.max_rate, + dp->link_config.max_lanes, dp->config.bpp); + if (mode->clock > rate) { + dev_err(dp->dev, "mode %s has too high pixel rate\n", + mode->name); + drm_mode_debug_printmodeline(mode); + } + + /* Configure the mode */ + ret = zynqmp_dp_mode_configure(dp, adjusted_mode->clock, 0); + if (ret < 0) { + pm_runtime_put_sync(dp->dev); + return; + } + + zynqmp_dp_encoder_mode_set_transfer_unit(dp, adjusted_mode); + zynqmp_dp_encoder_mode_set_stream(dp, adjusted_mode); + + /* Enable the encoder */ + dp->enabled = true; + zynqmp_dp_update_misc(dp); + if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) + zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 1); + zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, 0); + if (dp->status == connector_status_connected) { + for (i = 0; i < 3; i++) { + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, + DP_SET_POWER_D0); + if (ret == 1) + break; + usleep_range(300, 500); + } + /* Some monitors take time to wake up properly */ + msleep(zynqmp_dp_power_on_delay_ms); + } + if (ret != 1) + dev_dbg(dp->dev, "DP aux failed\n"); + else + zynqmp_dp_train_loop(dp); + zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET, + ZYNQMP_DP_SOFTWARE_RESET_ALL); + zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1); +} + +static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + + dp->enabled = false; + cancel_delayed_work(&dp->hpd_work); + zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0); + drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D3); + zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, + ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); + if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) + zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0); + pm_runtime_put_sync(dp->dev); +} + +#define ZYNQMP_DP_MIN_H_BACKPORCH 20 + +static int zynqmp_dp_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + struct drm_display_mode *mode = &crtc_state->mode; + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + int diff = mode->htotal - mode->hsync_end; + + /* + * ZynqMP DP requires horizontal backporch to be greater than 12. + * This limitation may not be compatible with the sink device. + */ + if (diff < ZYNQMP_DP_MIN_H_BACKPORCH) { + int vrefresh = (adjusted_mode->clock * 1000) / + (adjusted_mode->vtotal * adjusted_mode->htotal); + + dev_dbg(dp->dev, "hbackporch adjusted: %d to %d", + diff, ZYNQMP_DP_MIN_H_BACKPORCH - diff); + diff = ZYNQMP_DP_MIN_H_BACKPORCH - diff; + adjusted_mode->htotal += diff; + adjusted_mode->clock = adjusted_mode->vtotal * + adjusted_mode->htotal * vrefresh / 1000; + } + + return 0; +} + +static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge *bridge) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); struct zynqmp_dp_link_config *link_config = &dp->link_config; u32 state, i; int ret; @@ -1327,13 +1489,46 @@ zynqmp_dp_connector_detect(struct drm_connector *connector, bool force) return connector_status_disconnected; } +static struct edid *zynqmp_dp_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + + return drm_get_edid(connector, &dp->aux.ddc); +} + +static const struct drm_bridge_funcs zynqmp_dp_bridge_funcs = { + .attach = zynqmp_dp_bridge_attach, + .mode_valid = zynqmp_dp_bridge_mode_valid, + .atomic_enable = zynqmp_dp_bridge_atomic_enable, + .atomic_disable = zynqmp_dp_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_check = zynqmp_dp_bridge_atomic_check, + .detect = zynqmp_dp_bridge_detect, + .get_edid = zynqmp_dp_bridge_get_edid, +}; + +/* ----------------------------------------------------------------------------- + * DRM Connector + */ + +static enum drm_connector_status +zynqmp_dp_connector_detect(struct drm_connector *connector, bool force) +{ + struct zynqmp_dp *dp = connector_to_dp(connector); + + return zynqmp_dp_bridge_detect(&dp->bridge); +} + static int zynqmp_dp_connector_get_modes(struct drm_connector *connector) { struct zynqmp_dp *dp = connector_to_dp(connector); struct edid *edid; int ret; - edid = drm_get_edid(connector, &dp->aux.ddc); + edid = zynqmp_dp_bridge_get_edid(&dp->bridge, connector); if (!edid) return 0; @@ -1356,28 +1551,9 @@ static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { struct zynqmp_dp *dp = connector_to_dp(connector); - u8 max_lanes = dp->link_config.max_lanes; - u8 bpp = dp->config.bpp; - int max_rate = dp->link_config.max_rate; - int rate; - - if (mode->clock > ZYNQMP_MAX_FREQ) { - dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n", - mode->name); - drm_mode_debug_printmodeline(mode); - return MODE_CLOCK_HIGH; - } - /* Check with link rate and lane count */ - rate = zynqmp_dp_max_rate(max_rate, max_lanes, bpp); - if (mode->clock > rate) { - dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n", - mode->name); - drm_mode_debug_printmodeline(mode); - return MODE_CLOCK_HIGH; - } - - return MODE_OK; + return zynqmp_dp_bridge_mode_valid(&dp->bridge, &connector->display_info, + mode); } static const struct drm_connector_funcs zynqmp_dp_connector_funcs = { @@ -1404,49 +1580,20 @@ static void zynqmp_dp_encoder_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct zynqmp_dp *dp = encoder_to_dp(encoder); - unsigned int i; - int ret = 0; + struct drm_bridge_state bridge_state; - pm_runtime_get_sync(dp->dev); - dp->enabled = true; - zynqmp_dp_update_misc(dp); - if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) - zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 1); - zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, 0); - if (dp->status == connector_status_connected) { - for (i = 0; i < 3; i++) { - ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, - DP_SET_POWER_D0); - if (ret == 1) - break; - usleep_range(300, 500); - } - /* Some monitors take time to wake up properly */ - msleep(zynqmp_dp_power_on_delay_ms); - } - if (ret != 1) - dev_dbg(dp->dev, "DP aux failed\n"); - else - zynqmp_dp_train_loop(dp); - zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET, - ZYNQMP_DP_SOFTWARE_RESET_ALL); - zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1); + bridge_state.base.state = state; + zynqmp_dp_bridge_atomic_enable(&dp->bridge, &bridge_state); } static void zynqmp_dp_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct zynqmp_dp *dp = encoder_to_dp(encoder); + struct drm_bridge_state bridge_state; - dp->enabled = false; - cancel_delayed_work(&dp->hpd_work); - zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0); - drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D3); - zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, - ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); - if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) - zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0); - pm_runtime_put_sync(dp->dev); + bridge_state.base.state = state; + zynqmp_dp_bridge_atomic_disable(&dp->bridge, &bridge_state); } static void @@ -1454,60 +1601,17 @@ zynqmp_dp_encoder_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *connector_state) { - struct zynqmp_dp *dp = encoder_to_dp(encoder); - struct drm_display_mode *mode = &crtc_state->mode; - struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; - u8 max_lanes = dp->link_config.max_lanes; - u8 bpp = dp->config.bpp; - int rate, max_rate = dp->link_config.max_rate; - int ret; - - zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); - - /* Check again as bpp or format might have been chagned */ - rate = zynqmp_dp_max_rate(max_rate, max_lanes, bpp); - if (mode->clock > rate) { - dev_err(dp->dev, "the mode, %s,has too high pixel rate\n", - mode->name); - drm_mode_debug_printmodeline(mode); - } - - ret = zynqmp_dp_mode_configure(dp, adjusted_mode->clock, 0); - if (ret < 0) - return; - - zynqmp_dp_encoder_mode_set_transfer_unit(dp, adjusted_mode); - zynqmp_dp_encoder_mode_set_stream(dp, adjusted_mode); } -#define ZYNQMP_DP_MIN_H_BACKPORCH 20 - static int zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { - struct drm_display_mode *mode = &crtc_state->mode; - struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; - int diff = mode->htotal - mode->hsync_end; - - /* - * ZynqMP DP requires horizontal backporch to be greater than 12. - * This limitation may not be compatible with the sink device. - */ - if (diff < ZYNQMP_DP_MIN_H_BACKPORCH) { - int vrefresh = (adjusted_mode->clock * 1000) / - (adjusted_mode->vtotal * adjusted_mode->htotal); - - dev_dbg(encoder->dev->dev, "hbackporch adjusted: %d to %d", - diff, ZYNQMP_DP_MIN_H_BACKPORCH - diff); - diff = ZYNQMP_DP_MIN_H_BACKPORCH - diff; - adjusted_mode->htotal += diff; - adjusted_mode->clock = adjusted_mode->vtotal * - adjusted_mode->htotal * vrefresh / 1000; - } + struct zynqmp_dp *dp = encoder_to_dp(encoder); - return 0; + return zynqmp_dp_bridge_atomic_check(&dp->bridge, NULL, crtc_state, + conn_state); } static const struct drm_encoder_helper_funcs zynqmp_dp_encoder_helper_funcs = { @@ -1604,6 +1708,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) { struct zynqmp_dp *dp = dpsub->dp; + struct drm_bridge *bridge = &dp->bridge; struct drm_encoder *encoder = &dp->encoder; struct drm_connector *connector = &dp->connector; int ret; @@ -1611,6 +1716,18 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); + /* + * Initialize the bridge. Setting the device and encoder manually is a + * hack, to be removed once the bridge will get attached to the encoder + * using the bridge API. + */ + bridge->dev = dp->drm; + bridge->encoder = &dp->encoder; + bridge->funcs = &zynqmp_dp_bridge_funcs; + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_HPD; + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; + /* Create the DRM encoder and connector. */ encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); drm_simple_encoder_init(dp->drm, encoder, DRM_MODE_ENCODER_TMDS); From 5827398b0e765324bdd89df96b3e948847dc7663 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 05/37] drm: xlnx: zynqmp_dpsub: Don't access connector in zynqmp_dp_set_format() To prepare for the removal of the connector from the DP encoder, pass the display info pointer to the zynqmp_dp_set_format() function instead of accessing the connector internally. The display info is NULL when the function is called at initialization time, as we have no display info at that point. This doesn't change the existing behaviour, given that the zynqmp_dp_set_format() was already handling this as a special case (the display info isn't initialized at init time and is all zeroes). Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 8acd5cfbbd1da..ca566553e5473 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1109,6 +1109,7 @@ static void zynqmp_dp_update_misc(struct zynqmp_dp *dp) /** * zynqmp_dp_set_format - Set the input format * @dp: DisplayPort IP core structure + * @info: Display info * @format: input format * @bpc: bits per component * @@ -1117,10 +1118,10 @@ static void zynqmp_dp_update_misc(struct zynqmp_dp *dp) * Return: 0 on success, or -EINVAL. */ static int zynqmp_dp_set_format(struct zynqmp_dp *dp, + const struct drm_display_info *info, enum zynqmp_dpsub_format format, unsigned int bpc) { - static const struct drm_display_info *display; struct zynqmp_dp_config *config = &dp->config; unsigned int num_colors; @@ -1153,12 +1154,11 @@ static int zynqmp_dp_set_format(struct zynqmp_dp *dp, return -EINVAL; } - display = &dp->connector.display_info; - if (display->bpc && bpc > display->bpc) { + if (info && info->bpc && bpc > info->bpc) { dev_warn(dp->dev, "downgrading requested %ubpc to display limit %ubpc\n", - bpc, display->bpc); - bpc = display->bpc; + bpc, info->bpc); + bpc = info->bpc; } config->misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_BPC_MASK; @@ -1353,7 +1353,8 @@ static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge, adjusted_mode = &crtc_state->adjusted_mode; mode = &crtc_state->mode; - zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); + zynqmp_dp_set_format(dp, &connector->display_info, + ZYNQMP_DPSUB_FORMAT_RGB, 8); /* Check again as bpp or format might have been changed */ rate = zynqmp_dp_max_rate(dp->link_config.max_rate, @@ -1714,7 +1715,7 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) int ret; dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; - zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); + zynqmp_dp_set_format(dp, NULL, ZYNQMP_DPSUB_FORMAT_RGB, 8); /* * Initialize the bridge. Setting the device and encoder manually is a From 2374b6ea30384f23a78555c3b962bce358a8869e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 06/37] drm: xlnx: zynqmp_dpsub: Move connector registration to bridge attach Connector creation requires the DRM encoder, and it thus typically performed in the bridge attach operation. Move it there, to prepare for registration of the DRM bridge. For now the zynqmp_dp_bridge_attach() is called manually at initialization time. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 37 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index ca566553e5473..65b1ab4e4d2dd 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1292,9 +1292,30 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, * DRM Bridge */ +static const struct drm_connector_funcs zynqmp_dp_connector_funcs; +static const struct drm_connector_helper_funcs zynqmp_dp_connector_helper_funcs; + static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { + struct zynqmp_dp *dp = bridge_to_dp(bridge); + struct drm_connector *connector = &dp->connector; + int ret; + + /* Create the DRM connector. */ + connector->polled = DRM_CONNECTOR_POLL_HPD; + ret = drm_connector_init(dp->drm, connector, + &zynqmp_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + dev_err(dp->dev, "failed to create the DRM connector\n"); + return ret; + } + + drm_connector_helper_add(connector, &zynqmp_dp_connector_helper_funcs); + drm_connector_register(connector); + drm_connector_attach_encoder(connector, bridge->encoder); + return 0; } @@ -1711,7 +1732,6 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) struct zynqmp_dp *dp = dpsub->dp; struct drm_bridge *bridge = &dp->bridge; struct drm_encoder *encoder = &dp->encoder; - struct drm_connector *connector = &dp->connector; int ret; dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; @@ -1729,23 +1749,14 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) | DRM_BRIDGE_OP_HPD; bridge->type = DRM_MODE_CONNECTOR_DisplayPort; - /* Create the DRM encoder and connector. */ + /* Create the DRM encoder and attach the bridge. */ encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); drm_simple_encoder_init(dp->drm, encoder, DRM_MODE_ENCODER_TMDS); drm_encoder_helper_add(encoder, &zynqmp_dp_encoder_helper_funcs); - connector->polled = DRM_CONNECTOR_POLL_HPD; - ret = drm_connector_init(encoder->dev, connector, - &zynqmp_dp_connector_funcs, - DRM_MODE_CONNECTOR_DisplayPort); - if (ret) { - dev_err(dp->dev, "failed to create the DRM connector\n"); + ret = zynqmp_dp_bridge_attach(bridge, 0); + if (ret < 0) return ret; - } - - drm_connector_helper_add(connector, &zynqmp_dp_connector_helper_funcs); - drm_connector_register(connector); - drm_connector_attach_encoder(connector, encoder); /* Initialize and register the AUX adapter. */ ret = zynqmp_dp_aux_init(dp); From e8e357337dc91f6c2c28a06f783f0219b77ba07a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 07/37] drm: xlnx: zynqmp_dpsub: Move encoder to DPSUB core As part of the transitition of the DP encoder to a DRM bridge, turn the DRM encoder into a dummy encoder and move it out of the DP code, to the DPSUB core. DP encoder operations are handled by the DP bridge, which is now attached to the encoder. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 79 ++--------------------------- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 16 +++++- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 8 +++ 3 files changed, 25 insertions(+), 78 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 65b1ab4e4d2dd..28f92b5d8385d 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -15,12 +15,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include @@ -277,7 +275,6 @@ struct zynqmp_dp_config { /** * struct zynqmp_dp - Xilinx DisplayPort core - * @encoder: the drm encoder structure * @connector: the drm connector structure * @dev: device structure * @dpsub: Display subsystem @@ -299,7 +296,6 @@ struct zynqmp_dp_config { * @train_set: set of training data */ struct zynqmp_dp { - struct drm_encoder encoder; struct drm_connector connector; struct device *dev; struct zynqmp_dpsub *dpsub; @@ -324,11 +320,6 @@ struct zynqmp_dp { u8 train_set[ZYNQMP_DP_MAX_LANES]; }; -static inline struct zynqmp_dp *encoder_to_dp(struct drm_encoder *encoder) -{ - return container_of(encoder, struct zynqmp_dp, encoder); -} - static inline struct zynqmp_dp *connector_to_dp(struct drm_connector *connector) { return container_of(connector, struct zynqmp_dp, connector); @@ -1566,7 +1557,7 @@ zynqmp_dp_connector_best_encoder(struct drm_connector *connector) { struct zynqmp_dp *dp = connector_to_dp(connector); - return &dp->encoder; + return &dp->dpsub->encoder; } static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector, @@ -1594,55 +1585,6 @@ zynqmp_dp_connector_helper_funcs = { .mode_valid = zynqmp_dp_connector_mode_valid, }; -/* ----------------------------------------------------------------------------- - * DRM Encoder - */ - -static void zynqmp_dp_encoder_atomic_enable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct zynqmp_dp *dp = encoder_to_dp(encoder); - struct drm_bridge_state bridge_state; - - bridge_state.base.state = state; - zynqmp_dp_bridge_atomic_enable(&dp->bridge, &bridge_state); -} - -static void zynqmp_dp_encoder_atomic_disable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct zynqmp_dp *dp = encoder_to_dp(encoder); - struct drm_bridge_state bridge_state; - - bridge_state.base.state = state; - zynqmp_dp_bridge_atomic_disable(&dp->bridge, &bridge_state); -} - -static void -zynqmp_dp_encoder_atomic_mode_set(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *connector_state) -{ -} - -static int -zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - struct zynqmp_dp *dp = encoder_to_dp(encoder); - - return zynqmp_dp_bridge_atomic_check(&dp->bridge, NULL, crtc_state, - conn_state); -} - -static const struct drm_encoder_helper_funcs zynqmp_dp_encoder_helper_funcs = { - .atomic_enable = zynqmp_dp_encoder_atomic_enable, - .atomic_disable = zynqmp_dp_encoder_atomic_disable, - .atomic_mode_set = zynqmp_dp_encoder_atomic_mode_set, - .atomic_check = zynqmp_dp_encoder_atomic_check, -}; - /* ----------------------------------------------------------------------------- * Interrupt Handling */ @@ -1731,32 +1673,17 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) { struct zynqmp_dp *dp = dpsub->dp; struct drm_bridge *bridge = &dp->bridge; - struct drm_encoder *encoder = &dp->encoder; int ret; dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; zynqmp_dp_set_format(dp, NULL, ZYNQMP_DPSUB_FORMAT_RGB, 8); - /* - * Initialize the bridge. Setting the device and encoder manually is a - * hack, to be removed once the bridge will get attached to the encoder - * using the bridge API. - */ - bridge->dev = dp->drm; - bridge->encoder = &dp->encoder; + /* Initialize the bridge. */ bridge->funcs = &zynqmp_dp_bridge_funcs; bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; bridge->type = DRM_MODE_CONNECTOR_DisplayPort; - - /* Create the DRM encoder and attach the bridge. */ - encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); - drm_simple_encoder_init(dp->drm, encoder, DRM_MODE_ENCODER_TMDS); - drm_encoder_helper_add(encoder, &zynqmp_dp_encoder_helper_funcs); - - ret = zynqmp_dp_bridge_attach(bridge, 0); - if (ret < 0) - return ret; + dpsub->bridge = bridge; /* Initialize and register the AUX adapter. */ ret = zynqmp_dp_aux_init(dp); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 1de2d927c32b0..bb7c74a73dc21 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include "zynqmp_disp.h" @@ -94,6 +96,7 @@ static const struct drm_driver zynqmp_dpsub_drm_driver = { static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) { + struct drm_encoder *encoder = &dpsub->encoder; struct drm_device *drm = &dpsub->drm; int ret; @@ -116,8 +119,7 @@ static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) /* * Initialize the DISP and DP components. This will creates planes, - * CRTC, encoder and connector. The DISP should be initialized first as - * the DP encoder needs the CRTC. + * CRTC, and a bridge for the DP encoder. */ ret = zynqmp_disp_drm_init(dpsub); if (ret) @@ -127,6 +129,16 @@ static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) if (ret) goto err_poll_fini; + /* Create the encoder and attach the bridge. */ + encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE); + + ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, 0); + if (ret) { + dev_err(dpsub->dev, "failed to attach bridge to encoder\n"); + goto err_poll_fini; + } + /* Reset all components and register the DRM device. */ drm_mode_config_reset(drm); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index c04026d82639c..66820bbc4507f 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -12,8 +12,11 @@ #ifndef _ZYNQMP_DPSUB_H_ #define _ZYNQMP_DPSUB_H_ +#include + struct clk; struct device; +struct drm_bridge; struct drm_device; struct zynqmp_disp; struct zynqmp_dp; @@ -30,6 +33,8 @@ enum zynqmp_dpsub_format { * @drm: The DRM/KMS device * @dev: The physical device * @apb_clk: The APB clock + * @encoder: The dummy DRM encoder + * @bridge: The DP encoder bridge * @disp: The display controller * @dp: The DisplayPort controller * @dma_align: DMA alignment constraint (must be a power of 2) @@ -40,6 +45,9 @@ struct zynqmp_dpsub { struct clk *apb_clk; + struct drm_encoder encoder; + struct drm_bridge *bridge; + struct zynqmp_disp *disp; struct zynqmp_dp *dp; From bd68b9b3cb2e0d48a6adb773fa11393f869a7f9a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 4 Aug 2021 19:21:41 +0300 Subject: [PATCH 08/37] drm: xlnx: zynqmp_dpsub: Attach to the next bridge The next component in the display chain, after the DP encoder, is most likely a DP connector. The display connector driver registers a bridge for it. That bridge doesn't need to be controlled, but is needed in order to use the DRM connector bridge helper. Retrieve it at init time, and attach to it in the DP bridge attach handler. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 28f92b5d8385d..0aa810ebdecaf 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -283,6 +283,7 @@ struct zynqmp_dp_config { * @reset: reset controller * @irq: irq * @bridge: DRM bridge for the DP encoder + * @next_bridge: The downstream bridge * @config: IP core configuration from DTS * @aux: aux channel * @phy: PHY handles for DP lanes @@ -305,6 +306,7 @@ struct zynqmp_dp { int irq; struct drm_bridge bridge; + struct drm_bridge *next_bridge; struct zynqmp_dp_config config; struct drm_dp_aux aux; @@ -1307,6 +1309,13 @@ static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, drm_connector_register(connector); drm_connector_attach_encoder(connector, bridge->encoder); + if (dp->next_bridge) { + ret = drm_bridge_attach(bridge->encoder, dp->next_bridge, + bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret < 0) + return ret; + } + return 0; } @@ -1744,6 +1753,15 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) if (ret) goto err_reset; + /* + * Acquire the next bridge in the chain. Ignore errors caused by port@5 + * not being connected for backward-compatibility with older DTs. + */ + ret = drm_of_find_panel_or_bridge(dp->dev->of_node, 5, 0, NULL, + &dp->next_bridge); + if (ret < 0 && ret != -ENODEV) + goto err_reset; + /* Initialize the hardware. */ zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); From cbb11ef9871956244c38ab8520dc9abe87ccdb3b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 09/37] drm: xlnx: zynqmp_dpsub: Use DRM connector bridge helper Replace the manual connector implementation and registration in the DP encoder with the DRM connector bridge helper. This removes boilerplate code and simplifies the driver. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 90 +---------------------------- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 20 ++++++- 2 files changed, 20 insertions(+), 90 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 0aa810ebdecaf..a18cb979be52a 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -275,7 +274,6 @@ struct zynqmp_dp_config { /** * struct zynqmp_dp - Xilinx DisplayPort core - * @connector: the drm connector structure * @dev: device structure * @dpsub: Display subsystem * @drm: DRM core @@ -297,7 +295,6 @@ struct zynqmp_dp_config { * @train_set: set of training data */ struct zynqmp_dp { - struct drm_connector connector; struct device *dev; struct zynqmp_dpsub *dpsub; struct drm_device *drm; @@ -322,11 +319,6 @@ struct zynqmp_dp { u8 train_set[ZYNQMP_DP_MAX_LANES]; }; -static inline struct zynqmp_dp *connector_to_dp(struct drm_connector *connector) -{ - return container_of(connector, struct zynqmp_dp, connector); -} - static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge) { return container_of(bridge, struct zynqmp_dp, bridge); @@ -1285,33 +1277,15 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, * DRM Bridge */ -static const struct drm_connector_funcs zynqmp_dp_connector_funcs; -static const struct drm_connector_helper_funcs zynqmp_dp_connector_helper_funcs; - static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct zynqmp_dp *dp = bridge_to_dp(bridge); - struct drm_connector *connector = &dp->connector; int ret; - /* Create the DRM connector. */ - connector->polled = DRM_CONNECTOR_POLL_HPD; - ret = drm_connector_init(dp->drm, connector, - &zynqmp_dp_connector_funcs, - DRM_MODE_CONNECTOR_DisplayPort); - if (ret) { - dev_err(dp->dev, "failed to create the DRM connector\n"); - return ret; - } - - drm_connector_helper_add(connector, &zynqmp_dp_connector_helper_funcs); - drm_connector_register(connector); - drm_connector_attach_encoder(connector, bridge->encoder); - if (dp->next_bridge) { ret = drm_bridge_attach(bridge->encoder, dp->next_bridge, - bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + bridge, flags); if (ret < 0) return ret; } @@ -1532,68 +1506,6 @@ static const struct drm_bridge_funcs zynqmp_dp_bridge_funcs = { .get_edid = zynqmp_dp_bridge_get_edid, }; -/* ----------------------------------------------------------------------------- - * DRM Connector - */ - -static enum drm_connector_status -zynqmp_dp_connector_detect(struct drm_connector *connector, bool force) -{ - struct zynqmp_dp *dp = connector_to_dp(connector); - - return zynqmp_dp_bridge_detect(&dp->bridge); -} - -static int zynqmp_dp_connector_get_modes(struct drm_connector *connector) -{ - struct zynqmp_dp *dp = connector_to_dp(connector); - struct edid *edid; - int ret; - - edid = zynqmp_dp_bridge_get_edid(&dp->bridge, connector); - if (!edid) - return 0; - - drm_connector_update_edid_property(connector, edid); - ret = drm_add_edid_modes(connector, edid); - kfree(edid); - - return ret; -} - -static struct drm_encoder * -zynqmp_dp_connector_best_encoder(struct drm_connector *connector) -{ - struct zynqmp_dp *dp = connector_to_dp(connector); - - return &dp->dpsub->encoder; -} - -static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct zynqmp_dp *dp = connector_to_dp(connector); - - return zynqmp_dp_bridge_mode_valid(&dp->bridge, &connector->display_info, - mode); -} - -static const struct drm_connector_funcs zynqmp_dp_connector_funcs = { - .detect = zynqmp_dp_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = drm_connector_cleanup, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, - .reset = drm_atomic_helper_connector_reset, -}; - -static const struct drm_connector_helper_funcs -zynqmp_dp_connector_helper_funcs = { - .get_modes = zynqmp_dp_connector_get_modes, - .best_encoder = zynqmp_dp_connector_best_encoder, - .mode_valid = zynqmp_dp_connector_mode_valid, -}; - /* ----------------------------------------------------------------------------- * Interrupt Handling */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index bb7c74a73dc21..6f6e7c67d3700 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -18,6 +18,8 @@ #include #include +#include +#include #include #include #include @@ -97,6 +99,7 @@ static const struct drm_driver zynqmp_dpsub_drm_driver = { static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) { struct drm_encoder *encoder = &dpsub->encoder; + struct drm_connector *connector; struct drm_device *drm = &dpsub->drm; int ret; @@ -133,12 +136,27 @@ static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE); - ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, 0); + ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret) { dev_err(dpsub->dev, "failed to attach bridge to encoder\n"); goto err_poll_fini; } + /* Create the connector for the chain of bridges. */ + connector = drm_bridge_connector_init(drm, encoder); + if (IS_ERR(connector)) { + dev_err(dpsub->dev, "failed to created connector\n"); + ret = PTR_ERR(connector); + goto err_poll_fini; + } + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret < 0) { + dev_err(dpsub->dev, "failed to attach connector to encoder\n"); + goto err_poll_fini; + } + /* Reset all components and register the DRM device. */ drm_mode_config_reset(drm); From eb2d64bfcc174919a921295a5327b99a3b8f4166 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 10/37] drm: xlnx: zynqmp_dpsub: Report HPD through the bridge Now that the driver uses the connector bridge helper, HPD can be reported directly for the connector through the drm_bridge_hpd_notify() function. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index a18cb979be52a..33fd69ed75503 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -1534,12 +1533,12 @@ void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp) static void zynqmp_dp_hpd_work_func(struct work_struct *work) { - struct zynqmp_dp *dp; - - dp = container_of(work, struct zynqmp_dp, hpd_work.work); + struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, + hpd_work.work); + enum drm_connector_status status; - if (dp->drm) - drm_helper_hpd_irq_event(dp->drm); + status = zynqmp_dp_bridge_detect(&dp->bridge); + drm_bridge_hpd_notify(&dp->bridge, status); } static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) From c7bfa73cdfeb66f3c9170e05cab3aa160c93a95b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 11/37] drm: xlnx: zynqmp_dpsub: Drop unused zynqmp_disp.event field The event field of the zynqmp_disp structure is unused. Drop it. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index bbb365f2d0874..a168433456f74 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -171,7 +171,6 @@ struct zynqmp_disp_layer { * @audio.clk: Audio clock * @audio.clk_from_ps: True of the audio clock comes from PS, false from PL * @layers: Layers (planes) - * @event: Pending vblank event request * @pclk: Pixel clock * @pclk_from_ps: True of the video clock comes from PS, false from PL */ @@ -196,8 +195,6 @@ struct zynqmp_disp { struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS]; - struct drm_pending_vblank_event *event; - struct clk *pclk; bool pclk_from_ps; }; From c91d2d383537302105c139dfb9f34d12855bb369 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 12/37] drm: xlnx: zynqmp_dpsub: Drop unused zynqmp_disp_format.bus_fmt field The bus_fmt field of the zynqmp_disp_format structure is unused. Drop it. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index a168433456f74..e6fe6203fce7a 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -79,14 +79,12 @@ * struct zynqmp_disp_format - Display subsystem format information * @drm_fmt: DRM format (4CC) * @buf_fmt: AV buffer format - * @bus_fmt: Media bus formats (live formats) * @swap: Flag to swap R & B for RGB formats, and U & V for YUV formats * @sf: Scaling factors for color components */ struct zynqmp_disp_format { u32 drm_fmt; u32 buf_fmt; - u32 bus_fmt; bool swap; const u32 *sf; }; From a7727b3771084eadc0e5b832756e1a493949a5fd Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 13/37] drm: xlnx: zynqmp_dpsub: Don't pass CRTC to zynqmp_disp_setup_clock() To prepare for usage of the clock setup function outside of the CRTC code, replace the DRM-specific structures passed as parameters with a pointer to the zynqmp_disp and the requested clock rate. This doesn't introduce any functional change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index e6fe6203fce7a..380540fd08ff3 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -1409,16 +1409,9 @@ static void zynqmp_disp_disable(struct zynqmp_disp *disp) zynqmp_disp_avbuf_disable(disp); } -static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc) -{ - return container_of(crtc, struct zynqmp_disp, crtc); -} - -static int zynqmp_disp_crtc_setup_clock(struct drm_crtc *crtc, - struct drm_display_mode *adjusted_mode) +static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, + unsigned long mode_clock) { - struct zynqmp_disp *disp = crtc_to_disp(crtc); - unsigned long mode_clock = adjusted_mode->clock * 1000; unsigned long rate; long diff; int ret; @@ -1443,6 +1436,11 @@ static int zynqmp_disp_crtc_setup_clock(struct drm_crtc *crtc, return 0; } +static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc) +{ + return container_of(crtc, struct zynqmp_disp, crtc); +} + static void zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -1453,7 +1451,7 @@ zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc, pm_runtime_get_sync(disp->dev); - zynqmp_disp_crtc_setup_clock(crtc, adjusted_mode); + zynqmp_disp_setup_clock(disp, adjusted_mode->clock * 1000); ret = clk_prepare_enable(disp->pclk); if (ret) { From 36d1b456c5fe33c054e2d5ff0033d05e36fbf8a6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 14/37] drm: xlnx: zynqmp_dpsub: Configure blender in zynqmp_disp_enable() To prepare for control of the blender outside of the CRTC code, move the setup of the blender to the zynqmp_disp_enable() function. This doesn't introduce any functional change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index 380540fd08ff3..212f5210e129c 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -1386,6 +1386,9 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) */ static void zynqmp_disp_enable(struct zynqmp_disp *disp) { + zynqmp_disp_blend_set_output_format(disp, ZYNQMP_DPSUB_FORMAT_RGB); + zynqmp_disp_blend_set_bg_color(disp, 0, 0, 0); + zynqmp_disp_avbuf_enable(disp); /* Choose clock source based on the DT clock handle. */ zynqmp_disp_avbuf_set_clocks_sources(disp, disp->pclk_from_ps, @@ -1460,9 +1463,6 @@ zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc, return; } - zynqmp_disp_blend_set_output_format(disp, ZYNQMP_DPSUB_FORMAT_RGB); - zynqmp_disp_blend_set_bg_color(disp, 0, 0, 0); - zynqmp_disp_enable(disp); /* Delay of 3 vblank intervals for timing gen to be stable */ From 457d7180d628a303cd3c579e95f24746d915591e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 15/37] drm: xlnx: zynqmp_dpsub: Use local variable in zynqmp_disp_layer_update() Reuse the local info variable instead of going through the layer pointer in zynqmp_disp_layer_update(). This doesn't introduce any functional change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index 212f5210e129c..e3f1d9e2982b0 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -1088,7 +1088,7 @@ static int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, const struct drm_format_info *info = layer->drm_fmt; unsigned int i; - for (i = 0; i < layer->drm_fmt->num_planes; i++) { + for (i = 0; i < info->num_planes; i++) { unsigned int width = state->crtc_w / (i ? info->hsub : 1); unsigned int height = state->crtc_h / (i ? info->vsub : 1); struct zynqmp_disp_layer_dma *dma = &layer->dmas[i]; From 98c4ecec55d3bd2a8a79eba0adee0285724ea50f Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 16/37] drm: xlnx: zynqmp_dpsub: Pass format info to zynqmp_disp_layer_set_format() The zynqmp_disp_layer_set_format() function only needs format information, not a full plane state. Get the necessary info from the plane state in the caller and pass it to zynqmp_disp_layer_set_format(). This prepares for calling the function from non-DRM code. This doesn't introduce any functional change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index e3f1d9e2982b0..c0d27f5e28b5b 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -1037,15 +1037,13 @@ static void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) /** * zynqmp_disp_layer_set_format - Set the layer format * @layer: The layer - * @state: The plane state + * @info: The format info * - * Set the format for @layer based on @state->fb->format. The layer must be - * disabled. + * Set the format for @layer to @info. The layer must be disabled. */ static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, - struct drm_plane_state *state) + const struct drm_format_info *info) { - const struct drm_format_info *info = state->fb->format; unsigned int i; layer->disp_fmt = zynqmp_disp_layer_find_format(layer, info->format); @@ -1190,7 +1188,7 @@ zynqmp_disp_plane_atomic_update(struct drm_plane *plane, if (old_state->fb) zynqmp_disp_layer_disable(layer); - zynqmp_disp_layer_set_format(layer, new_state); + zynqmp_disp_layer_set_format(layer, new_state->fb->format); } zynqmp_disp_layer_update(layer, new_state); From 531306f54e84b9502f18db66f81729df7bc5fbe3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 17/37] drm: xlnx: zynqmp_dpsub: Remplace hardcoded values with ARRAY_SIZE() Use the ARRAY_SIZE() macro to iterate over arrays, instead of hardcoding their size. This makes the code less error-prone should the array size change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index c0d27f5e28b5b..e722af0e59a99 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -1222,7 +1222,7 @@ static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) unsigned int i, j; int ret; - for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) { + for (i = 0; i < ARRAY_SIZE(disp->layers); i++) { struct zynqmp_disp_layer *layer = &disp->layers[i]; enum drm_plane_type type; u32 *drm_formats; @@ -1293,7 +1293,7 @@ static void zynqmp_disp_destroy_layers(struct zynqmp_disp *disp) { unsigned int i; - for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) + for (i = 0; i < ARRAY_SIZE(disp->layers); i++) zynqmp_disp_layer_release_dma(disp, &disp->layers[i]); } @@ -1355,7 +1355,7 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) unsigned int i; int ret; - for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) { + for (i = 0; i < ARRAY_SIZE(disp->layers); i++) { struct zynqmp_disp_layer *layer = &disp->layers[i]; layer->id = i; @@ -1592,7 +1592,7 @@ static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp) u32 possible_crtcs = drm_crtc_mask(&disp->crtc); unsigned int i; - for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) + for (i = 0; i < ARRAY_SIZE(disp->layers); i++) disp->layers[i].plane.possible_crtcs = possible_crtcs; } From 96e0e3e3a210ab9c4894da2250bb583dd61ab3a2 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 18/37] drm: xlnx: zynqmp_dpsub: Don't use drmm_kcalloc() for temporary data The array of formats passed to drm_universal_plane_init() doesn't need to outlive the function call, as it's copied internally. Use kcalloc() instead of drmm_kcalloc() to allocate it, and free it right after usage. While at it, move the allocation and initialization of the formats array to a separate function, to prepare for splitting the DRM plane handling to a separate file. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 45 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index e722af0e59a99..4f6c9af799e9f 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -1000,6 +1000,33 @@ zynqmp_disp_layer_find_format(struct zynqmp_disp_layer *layer, return NULL; } +/** + * zynqmp_disp_layer_drm_formats - Return the DRM formats supported by the layer + * @layer: The layer + * @num_formats: Pointer to the returned number of formats + * + * Return: A newly allocated u32 array that stores all the DRM formats + * supported by the layer. The number of formats in the array is returned + * through the num_formats argument. + */ +static u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, + unsigned int *num_formats) +{ + unsigned int i; + u32 *formats; + + formats = kcalloc(layer->info->num_formats, sizeof(*formats), + GFP_KERNEL); + if (!formats) + return NULL; + + for (i = 0; i < layer->info->num_formats; ++i) + formats[i] = layer->info->formats[i].drm_fmt; + + *num_formats = layer->info->num_formats; + return formats; +} + /** * zynqmp_disp_layer_enable - Enable a layer * @layer: The layer @@ -1219,31 +1246,27 @@ static const struct drm_plane_funcs zynqmp_disp_plane_funcs = { static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) { - unsigned int i, j; + unsigned int i; int ret; for (i = 0; i < ARRAY_SIZE(disp->layers); i++) { struct zynqmp_disp_layer *layer = &disp->layers[i]; enum drm_plane_type type; - u32 *drm_formats; + unsigned int num_formats; + u32 *formats; - drm_formats = drmm_kcalloc(disp->drm, sizeof(*drm_formats), - layer->info->num_formats, - GFP_KERNEL); - if (!drm_formats) + formats = zynqmp_disp_layer_drm_formats(layer, &num_formats); + if (!formats) return -ENOMEM; - for (j = 0; j < layer->info->num_formats; ++j) - drm_formats[j] = layer->info->formats[j].drm_fmt; - /* Graphics layer is primary, and video layer is overlay. */ type = zynqmp_disp_layer_is_video(layer) ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; ret = drm_universal_plane_init(disp->drm, &layer->plane, 0, &zynqmp_disp_plane_funcs, - drm_formats, - layer->info->num_formats, + formats, num_formats, NULL, type, NULL); + kfree(formats); if (ret) return ret; From 1682ade6630823dd186723b44991d9933503dfc3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 19/37] drm: xlnx: zynqmp_dpsub: Move pclk from zynqmp_disp to zynqmp_dpsub The video clock is an external resource from the DPSUB point of view, not a resource internal to the display controller. Move it to the zynqmp_dpsub structure, to allow accessing it from outside the disp code. While at it, rename the fields from pclk and pclk_from_ps to vid_clk and vid_clk_from_ps, to better reflect their purpose and match the documentation. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 36 ++++++----------------------- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 17 ++++++++++++++ drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 4 ++++ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index 4f6c9af799e9f..d03066577b103 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -169,8 +169,6 @@ struct zynqmp_disp_layer { * @audio.clk: Audio clock * @audio.clk_from_ps: True of the audio clock comes from PS, false from PL * @layers: Layers (planes) - * @pclk: Pixel clock - * @pclk_from_ps: True of the video clock comes from PS, false from PL */ struct zynqmp_disp { struct device *dev; @@ -192,9 +190,6 @@ struct zynqmp_disp { } audio; struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS]; - - struct clk *pclk; - bool pclk_from_ps; }; /* ----------------------------------------------------------------------------- @@ -1412,7 +1407,7 @@ static void zynqmp_disp_enable(struct zynqmp_disp *disp) zynqmp_disp_avbuf_enable(disp); /* Choose clock source based on the DT clock handle. */ - zynqmp_disp_avbuf_set_clocks_sources(disp, disp->pclk_from_ps, + zynqmp_disp_avbuf_set_clocks_sources(disp, disp->dpsub->vid_clk_from_ps, disp->audio.clk_from_ps, true); zynqmp_disp_avbuf_enable_channels(disp); zynqmp_disp_avbuf_enable_audio(disp); @@ -1440,13 +1435,13 @@ static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, long diff; int ret; - ret = clk_set_rate(disp->pclk, mode_clock); + ret = clk_set_rate(disp->dpsub->vid_clk, mode_clock); if (ret) { - dev_err(disp->dev, "failed to set a pixel clock\n"); + dev_err(disp->dev, "failed to set the video clock\n"); return ret; } - rate = clk_get_rate(disp->pclk); + rate = clk_get_rate(disp->dpsub->vid_clk); diff = rate - mode_clock; if (abs(diff) > mode_clock / 20) dev_info(disp->dev, @@ -1477,9 +1472,9 @@ zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc, zynqmp_disp_setup_clock(disp, adjusted_mode->clock * 1000); - ret = clk_prepare_enable(disp->pclk); + ret = clk_prepare_enable(disp->dpsub->vid_clk); if (ret) { - dev_err(disp->dev, "failed to enable a pixel clock\n"); + dev_err(disp->dev, "failed to enable the video clock\n"); pm_runtime_put_sync(disp->dev); return; } @@ -1519,7 +1514,7 @@ zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc, } spin_unlock_irq(&crtc->dev->event_lock); - clk_disable_unprepare(disp->pclk); + clk_disable_unprepare(disp->dpsub->vid_clk); pm_runtime_put_sync(disp->dev); } @@ -1674,23 +1669,6 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) if (IS_ERR(disp->audio.base)) return PTR_ERR(disp->audio.base); - /* Try the live PL video clock */ - disp->pclk = devm_clk_get(disp->dev, "dp_live_video_in_clk"); - if (!IS_ERR(disp->pclk)) - disp->pclk_from_ps = false; - else if (PTR_ERR(disp->pclk) == -EPROBE_DEFER) - return PTR_ERR(disp->pclk); - - /* If the live PL video clock is not valid, fall back to PS clock */ - if (IS_ERR_OR_NULL(disp->pclk)) { - disp->pclk = devm_clk_get(disp->dev, "dp_vtc_pixel_clk_in"); - if (IS_ERR(disp->pclk)) { - dev_err(disp->dev, "failed to init any video clock\n"); - return PTR_ERR(disp->pclk); - } - disp->pclk_from_ps = true; - } - zynqmp_disp_audio_init(disp); ret = zynqmp_disp_create_layers(disp); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 6f6e7c67d3700..7d6c30a599d9e 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -214,6 +214,23 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) return ret; } + /* Try the live PL video clock */ + dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_live_video_in_clk"); + if (!IS_ERR(dpsub->vid_clk)) + dpsub->vid_clk_from_ps = false; + else if (PTR_ERR(dpsub->vid_clk) == -EPROBE_DEFER) + return PTR_ERR(dpsub->vid_clk); + + /* If the live PL video clock is not valid, fall back to PS clock */ + if (IS_ERR_OR_NULL(dpsub->vid_clk)) { + dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_vtc_pixel_clk_in"); + if (IS_ERR(dpsub->vid_clk)) { + dev_err(dpsub->dev, "failed to init any video clock\n"); + return PTR_ERR(dpsub->vid_clk); + } + dpsub->vid_clk_from_ps = true; + } + return 0; } diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 66820bbc4507f..bcb119bb97e15 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -33,6 +33,8 @@ enum zynqmp_dpsub_format { * @drm: The DRM/KMS device * @dev: The physical device * @apb_clk: The APB clock + * @vid_clk: Video clock + * @vid_clk_from_ps: True of the video clock comes from PS, false from PL * @encoder: The dummy DRM encoder * @bridge: The DP encoder bridge * @disp: The display controller @@ -44,6 +46,8 @@ struct zynqmp_dpsub { struct device *dev; struct clk *apb_clk; + struct clk *vid_clk; + bool vid_clk_from_ps; struct drm_encoder encoder; struct drm_bridge *bridge; From c979296ef60cdd37b4354c9507a624ae3c5e4bd6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 20/37] drm: xlnx: zynqmp_dpsub: Move audio clk from zynqmp_disp to zynqmp_dpsub The audio clock is an external resource from the DPSUB point of view, not a resource internal to the display controller. Move it to the zynqmp_dpsub structure, to allow accessing it from outside the disp code. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 54 ++--------------------------- drivers/gpu/drm/xlnx/zynqmp_disp.h | 2 -- drivers/gpu/drm/xlnx/zynqmp_dp.c | 8 ++--- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 54 +++++++++++++++++++++++++++-- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 7 ++++ 5 files changed, 65 insertions(+), 60 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index d03066577b103..a6e7ccf605256 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -166,8 +166,6 @@ struct zynqmp_disp_layer { * @blend.base: Register I/O base address for the blender * @avbuf.base: Register I/O base address for the audio/video buffer manager * @audio.base: Registers I/O base address for the audio mixer - * @audio.clk: Audio clock - * @audio.clk_from_ps: True of the audio clock comes from PS, false from PL * @layers: Layers (planes) */ struct zynqmp_disp { @@ -185,8 +183,6 @@ struct zynqmp_disp { } avbuf; struct { void __iomem *base; - struct clk *clk; - bool clk_from_ps; } audio; struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS]; @@ -892,25 +888,6 @@ static void zynqmp_disp_audio_disable(struct zynqmp_disp *disp) ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST); } -static void zynqmp_disp_audio_init(struct zynqmp_disp *disp) -{ - /* Try the live PL audio clock. */ - disp->audio.clk = devm_clk_get(disp->dev, "dp_live_audio_aclk"); - if (!IS_ERR(disp->audio.clk)) { - disp->audio.clk_from_ps = false; - return; - } - - /* If the live PL audio clock is not valid, fall back to PS clock. */ - disp->audio.clk = devm_clk_get(disp->dev, "dp_aud_clk"); - if (!IS_ERR(disp->audio.clk)) { - disp->audio.clk_from_ps = true; - return; - } - - dev_err(disp->dev, "audio disabled due to missing clock\n"); -} - /* ----------------------------------------------------------------------------- * ZynqMP Display external functions for zynqmp_dp */ @@ -929,32 +906,6 @@ void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp) drm_crtc_handle_vblank(crtc); } -/** - * zynqmp_disp_audio_enabled - If the audio is enabled - * @disp: Display controller - * - * Return if the audio is enabled depending on the audio clock. - * - * Return: true if audio is enabled, or false. - */ -bool zynqmp_disp_audio_enabled(struct zynqmp_disp *disp) -{ - return !!disp->audio.clk; -} - -/** - * zynqmp_disp_get_audio_clk_rate - Get the current audio clock rate - * @disp: Display controller - * - * Return: the current audio clock rate. - */ -unsigned int zynqmp_disp_get_audio_clk_rate(struct zynqmp_disp *disp) -{ - if (zynqmp_disp_audio_enabled(disp)) - return 0; - return clk_get_rate(disp->audio.clk); -} - /** * zynqmp_disp_get_crtc_mask - Return the CRTC bit mask * @disp: Display controller @@ -1408,7 +1359,8 @@ static void zynqmp_disp_enable(struct zynqmp_disp *disp) zynqmp_disp_avbuf_enable(disp); /* Choose clock source based on the DT clock handle. */ zynqmp_disp_avbuf_set_clocks_sources(disp, disp->dpsub->vid_clk_from_ps, - disp->audio.clk_from_ps, true); + disp->dpsub->aud_clk_from_ps, + true); zynqmp_disp_avbuf_enable_channels(disp); zynqmp_disp_avbuf_enable_audio(disp); @@ -1669,8 +1621,6 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) if (IS_ERR(disp->audio.base)) return PTR_ERR(disp->audio.base); - zynqmp_disp_audio_init(disp); - ret = zynqmp_disp_create_layers(disp); if (ret) return ret; diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h index f402901afb230..1b7f90a81857f 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h @@ -31,8 +31,6 @@ struct zynqmp_disp; struct zynqmp_dpsub; void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp); -bool zynqmp_disp_audio_enabled(struct zynqmp_disp *disp); -unsigned int zynqmp_disp_get_audio_clk_rate(struct zynqmp_disp *disp); uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp); int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 33fd69ed75503..3e4d164d40fef 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1252,7 +1252,7 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, reg = drm_dp_bw_code_to_link_rate(dp->mode.bw_code); zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_N_VID, reg); zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_M_VID, mode->clock); - rate = zynqmp_disp_get_audio_clk_rate(dp->dpsub->disp); + rate = zynqmp_dpsub_get_audio_clk_rate(dp->dpsub); if (rate) { dev_dbg(dp->dev, "Audio rate: %d\n", rate / 512); zynqmp_dp_write(dp, ZYNQMP_DP_TX_N_AUD, reg); @@ -1261,7 +1261,7 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, } /* Only 2 channel audio is supported now */ - if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) + if (zynqmp_dpsub_audio_enabled(dp->dpsub)) zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CHANNELS, 1); zynqmp_dp_write(dp, ZYNQMP_DP_USER_PIX_WIDTH, 1); @@ -1372,7 +1372,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge, /* Enable the encoder */ dp->enabled = true; zynqmp_dp_update_misc(dp); - if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) + if (zynqmp_dpsub_audio_enabled(dp->dpsub)) zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 1); zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, 0); if (dp->status == connector_status_connected) { @@ -1406,7 +1406,7 @@ static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge, drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D3); zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); - if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) + if (zynqmp_dpsub_audio_enabled(dp->dpsub)) zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0); pm_runtime_put_sync(dp->dev); } diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 7d6c30a599d9e..6d60efbe1f39f 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -196,6 +196,36 @@ static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume) }; +/* ----------------------------------------------------------------------------- + * DPSUB Configuration + */ + +/** + * zynqmp_dpsub_audio_enabled - If the audio is enabled + * @dpsub: DisplayPort subsystem + * + * Return if the audio is enabled depending on the audio clock. + * + * Return: true if audio is enabled, or false. + */ +bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub) +{ + return !!dpsub->aud_clk; +} + +/** + * zynqmp_dpsub_get_audio_clk_rate - Get the current audio clock rate + * @dpsub: DisplayPort subsystem + * + * Return: the current audio clock rate. + */ +unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub) +{ + if (zynqmp_dpsub_audio_enabled(dpsub)) + return 0; + return clk_get_rate(dpsub->aud_clk); +} + /* ----------------------------------------------------------------------------- * Probe & Remove */ @@ -214,14 +244,16 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) return ret; } - /* Try the live PL video clock */ + /* + * Try the live PL video clock, and fall back to the PS clock if the + * live PL video clock isn't valid. + */ dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_live_video_in_clk"); if (!IS_ERR(dpsub->vid_clk)) dpsub->vid_clk_from_ps = false; else if (PTR_ERR(dpsub->vid_clk) == -EPROBE_DEFER) return PTR_ERR(dpsub->vid_clk); - /* If the live PL video clock is not valid, fall back to PS clock */ if (IS_ERR_OR_NULL(dpsub->vid_clk)) { dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_vtc_pixel_clk_in"); if (IS_ERR(dpsub->vid_clk)) { @@ -231,6 +263,24 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) dpsub->vid_clk_from_ps = true; } + /* + * Try the live PL audio clock, and fall back to the PS clock if the + * live PL audio clock isn't valid. Missing audio clock disables audio + * but isn't an error. + */ + dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_live_audio_aclk"); + if (!IS_ERR(dpsub->aud_clk)) { + dpsub->aud_clk_from_ps = false; + return 0; + } + + dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_aud_clk"); + if (!IS_ERR(dpsub->aud_clk)) { + dpsub->aud_clk_from_ps = true; + return 0; + } + + dev_info(dpsub->dev, "audio disabled due to missing clock\n"); return 0; } diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index bcb119bb97e15..5bd42e192e170 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -35,6 +35,8 @@ enum zynqmp_dpsub_format { * @apb_clk: The APB clock * @vid_clk: Video clock * @vid_clk_from_ps: True of the video clock comes from PS, false from PL + * @aud_clk: Audio clock + * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL * @encoder: The dummy DRM encoder * @bridge: The DP encoder bridge * @disp: The display controller @@ -48,6 +50,8 @@ struct zynqmp_dpsub { struct clk *apb_clk; struct clk *vid_clk; bool vid_clk_from_ps; + struct clk *aud_clk; + bool aud_clk_from_ps; struct drm_encoder encoder; struct drm_bridge *bridge; @@ -63,4 +67,7 @@ static inline struct zynqmp_dpsub *to_zynqmp_dpsub(struct drm_device *drm) return container_of(drm, struct zynqmp_dpsub, drm); } +bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub); +unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub); + #endif /* _ZYNQMP_DPSUB_H_ */ From 68dcffea19dd3cb4528886b315f84b376f0c8f5d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 21/37] drm: xlnx: zynqmp_dpsub: Move CRTC to zynqmp_dpsub structure Decouple the zynqmp_disp, which handles the hardware configuration, from the DRM CRTC by moving the CRTC to the zynqmp_dpsub structure. The CRTC handling code will be moved to a separate file in a subsequent step. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 20 +++++++++----------- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 3 +++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index a6e7ccf605256..85d5475a6ecf7 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -162,7 +162,6 @@ struct zynqmp_disp_layer { * @dev: Device structure * @drm: DRM core * @dpsub: Display subsystem - * @crtc: DRM CRTC * @blend.base: Register I/O base address for the blender * @avbuf.base: Register I/O base address for the audio/video buffer manager * @audio.base: Registers I/O base address for the audio mixer @@ -173,8 +172,6 @@ struct zynqmp_disp { struct drm_device *drm; struct zynqmp_dpsub *dpsub; - struct drm_crtc crtc; - struct { void __iomem *base; } blend; @@ -901,7 +898,7 @@ static void zynqmp_disp_audio_disable(struct zynqmp_disp *disp) */ void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp) { - struct drm_crtc *crtc = &disp->crtc; + struct drm_crtc *crtc = &disp->dpsub->crtc; drm_crtc_handle_vblank(crtc); } @@ -914,7 +911,7 @@ void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp) */ uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp) { - return drm_crtc_mask(&disp->crtc); + return drm_crtc_mask(&disp->dpsub->crtc); } /* ----------------------------------------------------------------------------- @@ -1409,7 +1406,7 @@ static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc) { - return container_of(crtc, struct zynqmp_disp, crtc); + return container_of(crtc, struct zynqmp_dpsub, crtc)->disp; } static void @@ -1457,7 +1454,7 @@ zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc, zynqmp_disp_disable(disp); - drm_crtc_vblank_off(&disp->crtc); + drm_crtc_vblank_off(crtc); spin_lock_irq(&crtc->dev->event_lock); if (crtc->state->event) { @@ -1542,24 +1539,25 @@ static const struct drm_crtc_funcs zynqmp_disp_crtc_funcs = { static int zynqmp_disp_create_crtc(struct zynqmp_disp *disp) { struct drm_plane *plane = &disp->layers[ZYNQMP_DISP_LAYER_GFX].plane; + struct drm_crtc *crtc = &disp->dpsub->crtc; int ret; - ret = drm_crtc_init_with_planes(disp->drm, &disp->crtc, plane, + ret = drm_crtc_init_with_planes(disp->drm, crtc, plane, NULL, &zynqmp_disp_crtc_funcs, NULL); if (ret < 0) return ret; - drm_crtc_helper_add(&disp->crtc, &zynqmp_disp_crtc_helper_funcs); + drm_crtc_helper_add(crtc, &zynqmp_disp_crtc_helper_funcs); /* Start with vertical blanking interrupt reporting disabled. */ - drm_crtc_vblank_off(&disp->crtc); + drm_crtc_vblank_off(crtc); return 0; } static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp) { - u32 possible_crtcs = drm_crtc_mask(&disp->crtc); + u32 possible_crtcs = drm_crtc_mask(&disp->dpsub->crtc); unsigned int i; for (i = 0; i < ARRAY_SIZE(disp->layers); i++) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 5bd42e192e170..4ee0dc69ebee8 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -12,6 +12,7 @@ #ifndef _ZYNQMP_DPSUB_H_ #define _ZYNQMP_DPSUB_H_ +#include #include struct clk; @@ -37,6 +38,7 @@ enum zynqmp_dpsub_format { * @vid_clk_from_ps: True of the video clock comes from PS, false from PL * @aud_clk: Audio clock * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL + * @crtc: The DRM CRTC * @encoder: The dummy DRM encoder * @bridge: The DP encoder bridge * @disp: The display controller @@ -53,6 +55,7 @@ struct zynqmp_dpsub { struct clk *aud_clk; bool aud_clk_from_ps; + struct drm_crtc crtc; struct drm_encoder encoder; struct drm_bridge *bridge; From 88beb8ccc03223dd2849f8c37cb8afd2e49a0239 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 22/37] drm: xlnx: zynqmp_dpsub: Move planes to zynqmp_dpsub structure Decouple the zynqmp_disp, which handles the hardware configuration, from the DRM planes by moving the planes to the zynqmp_dpsub structure. The planes handling code will be moved to a separate file in a subsequent step. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 32 ++++++++++++----------------- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 5 +++++ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index 85d5475a6ecf7..4d68a73a59906 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -72,7 +72,6 @@ #define ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS 4 #define ZYNQMP_DISP_AV_BUF_NUM_BUFFERS 6 -#define ZYNQMP_DISP_NUM_LAYERS 2 #define ZYNQMP_DISP_MAX_NUM_SUB_PLANES 3 /** @@ -134,8 +133,7 @@ struct zynqmp_disp_layer_info { }; /** - * struct zynqmp_disp_layer - Display layer (DRM plane) - * @plane: DRM plane + * struct zynqmp_disp_layer - Display layer * @id: Layer ID * @disp: Back pointer to struct zynqmp_disp * @info: Static layer information @@ -145,7 +143,6 @@ struct zynqmp_disp_layer_info { * @mode: Current operation mode */ struct zynqmp_disp_layer { - struct drm_plane plane; enum zynqmp_disp_layer_id id; struct zynqmp_disp *disp; const struct zynqmp_disp_layer_info *info; @@ -182,7 +179,7 @@ struct zynqmp_disp { void __iomem *base; } audio; - struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS]; + struct zynqmp_disp_layer layers[ZYNQMP_DPSUB_NUM_LAYERS]; }; /* ----------------------------------------------------------------------------- @@ -1091,11 +1088,6 @@ static int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, return 0; } -static inline struct zynqmp_disp_layer *plane_to_layer(struct drm_plane *plane) -{ - return container_of(plane, struct zynqmp_disp_layer, plane); -} - static int zynqmp_disp_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) @@ -1124,7 +1116,8 @@ zynqmp_disp_plane_atomic_disable(struct drm_plane *plane, { struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); - struct zynqmp_disp_layer *layer = plane_to_layer(plane); + struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); + struct zynqmp_disp_layer *layer = &dpsub->disp->layers[plane->index]; if (!old_state->fb) return; @@ -1142,7 +1135,8 @@ zynqmp_disp_plane_atomic_update(struct drm_plane *plane, { struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); - struct zynqmp_disp_layer *layer = plane_to_layer(plane); + struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); + struct zynqmp_disp_layer *layer = &dpsub->disp->layers[plane->index]; bool format_changed = false; if (!old_state->fb || @@ -1194,6 +1188,7 @@ static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) for (i = 0; i < ARRAY_SIZE(disp->layers); i++) { struct zynqmp_disp_layer *layer = &disp->layers[i]; + struct drm_plane *plane = &disp->dpsub->planes[i]; enum drm_plane_type type; unsigned int num_formats; u32 *formats; @@ -1205,7 +1200,7 @@ static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) /* Graphics layer is primary, and video layer is overlay. */ type = zynqmp_disp_layer_is_video(layer) ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; - ret = drm_universal_plane_init(disp->drm, &layer->plane, 0, + ret = drm_universal_plane_init(disp->drm, plane, 0, &zynqmp_disp_plane_funcs, formats, num_formats, NULL, type, NULL); @@ -1213,12 +1208,11 @@ static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) if (ret) return ret; - drm_plane_helper_add(&layer->plane, - &zynqmp_disp_plane_helper_funcs); + drm_plane_helper_add(plane, &zynqmp_disp_plane_helper_funcs); - drm_plane_create_zpos_immutable_property(&layer->plane, i); + drm_plane_create_zpos_immutable_property(plane, i); if (zynqmp_disp_layer_is_gfx(layer)) - drm_plane_create_alpha_property(&layer->plane); + drm_plane_create_alpha_property(plane); } return 0; @@ -1538,7 +1532,7 @@ static const struct drm_crtc_funcs zynqmp_disp_crtc_funcs = { static int zynqmp_disp_create_crtc(struct zynqmp_disp *disp) { - struct drm_plane *plane = &disp->layers[ZYNQMP_DISP_LAYER_GFX].plane; + struct drm_plane *plane = &disp->dpsub->planes[ZYNQMP_DISP_LAYER_GFX]; struct drm_crtc *crtc = &disp->dpsub->crtc; int ret; @@ -1561,7 +1555,7 @@ static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp) unsigned int i; for (i = 0; i < ARRAY_SIZE(disp->layers); i++) - disp->layers[i].plane.possible_crtcs = possible_crtcs; + disp->dpsub->planes[i].possible_crtcs = possible_crtcs; } /* ----------------------------------------------------------------------------- diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 4ee0dc69ebee8..2a955895404fe 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -14,6 +14,7 @@ #include #include +#include struct clk; struct device; @@ -22,6 +23,8 @@ struct drm_device; struct zynqmp_disp; struct zynqmp_dp; +#define ZYNQMP_DPSUB_NUM_LAYERS 2 + enum zynqmp_dpsub_format { ZYNQMP_DPSUB_FORMAT_RGB, ZYNQMP_DPSUB_FORMAT_YCRCB444, @@ -38,6 +41,7 @@ enum zynqmp_dpsub_format { * @vid_clk_from_ps: True of the video clock comes from PS, false from PL * @aud_clk: Audio clock * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL + * @planes: The DRM planes * @crtc: The DRM CRTC * @encoder: The dummy DRM encoder * @bridge: The DP encoder bridge @@ -55,6 +59,7 @@ struct zynqmp_dpsub { struct clk *aud_clk; bool aud_clk_from_ps; + struct drm_plane planes[ZYNQMP_DPSUB_NUM_LAYERS]; struct drm_crtc crtc; struct drm_encoder encoder; struct drm_bridge *bridge; From 76c8eeb72dbbe31770c645e4039846f6f4019475 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 29 Sep 2022 00:25:56 +0300 Subject: [PATCH 23/37] drm: xlnx: zynqmp_dpsub: Move DRM/KMS initialization to separate file Start preparation for using the DPSUB as a standalone DisplayPort encoder without a display controller by moving the DRM/KMS initialization to a new zynqmp_kms.c file. No functional change intended. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/Makefile | 2 +- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 43 ++---------------- drivers/gpu/drm/xlnx/zynqmp_kms.c | 70 +++++++++++++++++++++++++++++ drivers/gpu/drm/xlnx/zynqmp_kms.h | 19 ++++++++ 4 files changed, 93 insertions(+), 41 deletions(-) create mode 100644 drivers/gpu/drm/xlnx/zynqmp_kms.c create mode 100644 drivers/gpu/drm/xlnx/zynqmp_kms.h diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile index 51c24b72217b8..ea1422a39502a 100644 --- a/drivers/gpu/drm/xlnx/Makefile +++ b/drivers/gpu/drm/xlnx/Makefile @@ -1,2 +1,2 @@ -zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o +zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o zynqmp_kms.o obj-$(CONFIG_DRM_ZYNQMP_DPSUB) += zynqmp-dpsub.o diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 6d60efbe1f39f..5d61f6c7aee56 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -17,9 +17,7 @@ #include #include -#include #include -#include #include #include #include @@ -30,12 +28,12 @@ #include #include #include -#include #include #include "zynqmp_disp.h" #include "zynqmp_dp.h" #include "zynqmp_dpsub.h" +#include "zynqmp_kms.h" /* ----------------------------------------------------------------------------- * Dumb Buffer & Framebuffer Allocation @@ -98,8 +96,6 @@ static const struct drm_driver zynqmp_dpsub_drm_driver = { static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) { - struct drm_encoder *encoder = &dpsub->encoder; - struct drm_connector *connector; struct drm_device *drm = &dpsub->drm; int ret; @@ -120,42 +116,9 @@ static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) drm_kms_helper_poll_init(drm); - /* - * Initialize the DISP and DP components. This will creates planes, - * CRTC, and a bridge for the DP encoder. - */ - ret = zynqmp_disp_drm_init(dpsub); - if (ret) - goto err_poll_fini; - - ret = zynqmp_dp_drm_init(dpsub); - if (ret) - goto err_poll_fini; - - /* Create the encoder and attach the bridge. */ - encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE); - - ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, - DRM_BRIDGE_ATTACH_NO_CONNECTOR); - if (ret) { - dev_err(dpsub->dev, "failed to attach bridge to encoder\n"); - goto err_poll_fini; - } - - /* Create the connector for the chain of bridges. */ - connector = drm_bridge_connector_init(drm, encoder); - if (IS_ERR(connector)) { - dev_err(dpsub->dev, "failed to created connector\n"); - ret = PTR_ERR(connector); - goto err_poll_fini; - } - - ret = drm_connector_attach_encoder(connector, encoder); - if (ret < 0) { - dev_err(dpsub->dev, "failed to attach connector to encoder\n"); + ret = zynqmp_dpsub_kms_init(dpsub); + if (ret < 0) goto err_poll_fini; - } /* Reset all components and register the DRM device. */ drm_mode_config_reset(drm); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c new file mode 100644 index 0000000000000..e4e7f8fd96d22 --- /dev/null +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ZynqMP DisplayPort Subsystem - KMS API + * + * Copyright (C) 2017 - 2021 Xilinx, Inc. + * + * Authors: + * - Hyun Woo Kwon + * - Laurent Pinchart + */ + +#include +#include +#include +#include +#include + +#include "zynqmp_disp.h" +#include "zynqmp_dp.h" +#include "zynqmp_dpsub.h" +#include "zynqmp_kms.h" + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) +{ + struct drm_encoder *encoder = &dpsub->encoder; + struct drm_connector *connector; + int ret; + + /* + * Initialize the DISP and DP components. This will creates planes, + * CRTC, and a bridge for the DP encoder. + */ + ret = zynqmp_disp_drm_init(dpsub); + if (ret) + return ret; + + ret = zynqmp_dp_drm_init(dpsub); + if (ret) + return ret; + + /* Create the encoder and attach the bridge. */ + encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); + drm_simple_encoder_init(&dpsub->drm, encoder, DRM_MODE_ENCODER_NONE); + + ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err(dpsub->dev, "failed to attach bridge to encoder\n"); + return ret; + } + + /* Create the connector for the chain of bridges. */ + connector = drm_bridge_connector_init(&dpsub->drm, encoder); + if (IS_ERR(connector)) { + dev_err(dpsub->dev, "failed to created connector\n"); + return PTR_ERR(connector); + } + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret < 0) { + dev_err(dpsub->dev, "failed to attach connector to encoder\n"); + return ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h b/drivers/gpu/drm/xlnx/zynqmp_kms.h new file mode 100644 index 0000000000000..a6729d9d82cce --- /dev/null +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ZynqMP DisplayPort Subsystem - KMS API + * + * Copyright (C) 2017 - 2021 Xilinx, Inc. + * + * Authors: + * - Hyun Woo Kwon + * - Laurent Pinchart + */ + +#ifndef _ZYNQMP_KMS_H_ +#define _ZYNQMP_KMS_H_ + +struct zynqmp_dpsub; + +int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub); + +#endif /* _ZYNQMP_KMS_H_ */ From 83a956d3c316ca63cc4cc931afce474f9e0199a1 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 24/37] drm: xlnx: zynqmp_dpsub: Move CRTC handling to zynqmp_kms.c Decouple the CRTC handling from the display controller programming by moving the corresponding code from zynqmp_disp.c to zynqmp_kms.c. This prepares for using the DPSUB with a live video input, without creating a DRM CRTC in the DPSUB driver. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 251 +++-------------------------- drivers/gpu/drm/xlnx/zynqmp_disp.h | 21 ++- drivers/gpu/drm/xlnx/zynqmp_dp.c | 3 +- drivers/gpu/drm/xlnx/zynqmp_kms.c | 190 +++++++++++++++++++++- drivers/gpu/drm/xlnx/zynqmp_kms.h | 2 + 5 files changed, 232 insertions(+), 235 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index 4d68a73a59906..c7d95f3d2a845 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -11,27 +11,21 @@ #include #include -#include #include -#include #include #include #include #include #include #include -#include #include -#include #include #include #include #include #include #include -#include -#include #include "zynqmp_disp.h" #include "zynqmp_disp_regs.h" @@ -88,16 +82,6 @@ struct zynqmp_disp_format { const u32 *sf; }; -/** - * enum zynqmp_disp_layer_id - Layer identifier - * @ZYNQMP_DISP_LAYER_VID: Video layer - * @ZYNQMP_DISP_LAYER_GFX: Graphics layer - */ -enum zynqmp_disp_layer_id { - ZYNQMP_DISP_LAYER_VID, - ZYNQMP_DISP_LAYER_GFX -}; - /** * enum zynqmp_disp_layer_mode - Layer mode * @ZYNQMP_DISP_LAYER_NONLIVE: non-live (memory) mode @@ -143,7 +127,7 @@ struct zynqmp_disp_layer_info { * @mode: Current operation mode */ struct zynqmp_disp_layer { - enum zynqmp_disp_layer_id id; + enum zynqmp_dpsub_layer_id id; struct zynqmp_disp *disp; const struct zynqmp_disp_layer_info *info; @@ -398,12 +382,12 @@ static void zynqmp_disp_avbuf_write(struct zynqmp_disp *disp, int reg, u32 val) static bool zynqmp_disp_layer_is_gfx(const struct zynqmp_disp_layer *layer) { - return layer->id == ZYNQMP_DISP_LAYER_GFX; + return layer->id == ZYNQMP_DPSUB_LAYER_GFX; } static bool zynqmp_disp_layer_is_video(const struct zynqmp_disp_layer *layer) { - return layer->id == ZYNQMP_DISP_LAYER_VID; + return layer->id == ZYNQMP_DPSUB_LAYER_VID; } /** @@ -882,35 +866,6 @@ static void zynqmp_disp_audio_disable(struct zynqmp_disp *disp) ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST); } -/* ----------------------------------------------------------------------------- - * ZynqMP Display external functions for zynqmp_dp - */ - -/** - * zynqmp_disp_handle_vblank - Handle the vblank event - * @disp: Display controller - * - * This function handles the vblank interrupt, and sends an event to - * CRTC object. This will be called by the DP vblank interrupt handler. - */ -void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp) -{ - struct drm_crtc *crtc = &disp->dpsub->crtc; - - drm_crtc_handle_vblank(crtc); -} - -/** - * zynqmp_disp_get_crtc_mask - Return the CRTC bit mask - * @disp: Display controller - * - * Return: the crtc mask of the zyqnmp_disp CRTC. - */ -uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp) -{ - return drm_crtc_mask(&disp->dpsub->crtc); -} - /* ----------------------------------------------------------------------------- * ZynqMP Display Layer & DRM Plane */ @@ -1110,7 +1065,7 @@ zynqmp_disp_plane_atomic_check(struct drm_plane *plane, false, false); } -static void +void zynqmp_disp_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_state *state) { @@ -1300,12 +1255,12 @@ static int zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp, static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) { static const struct zynqmp_disp_layer_info layer_info[] = { - [ZYNQMP_DISP_LAYER_VID] = { + [ZYNQMP_DPSUB_LAYER_VID] = { .formats = avbuf_vid_fmts, .num_formats = ARRAY_SIZE(avbuf_vid_fmts), .num_channels = 3, }, - [ZYNQMP_DISP_LAYER_GFX] = { + [ZYNQMP_DPSUB_LAYER_GFX] = { .formats = avbuf_gfx_fmts, .num_formats = ARRAY_SIZE(avbuf_gfx_fmts), .num_channels = 1, @@ -1335,14 +1290,14 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) } /* ----------------------------------------------------------------------------- - * ZynqMP Display & DRM CRTC + * ZynqMP Display */ /** * zynqmp_disp_enable - Enable the display controller * @disp: Display controller */ -static void zynqmp_disp_enable(struct zynqmp_disp *disp) +void zynqmp_disp_enable(struct zynqmp_disp *disp) { zynqmp_disp_blend_set_output_format(disp, ZYNQMP_DPSUB_FORMAT_RGB); zynqmp_disp_blend_set_bg_color(disp, 0, 0, 0); @@ -1362,7 +1317,7 @@ static void zynqmp_disp_enable(struct zynqmp_disp *disp) * zynqmp_disp_disable - Disable the display controller * @disp: Display controller */ -static void zynqmp_disp_disable(struct zynqmp_disp *disp) +void zynqmp_disp_disable(struct zynqmp_disp *disp) { zynqmp_disp_audio_disable(disp); @@ -1371,8 +1326,15 @@ static void zynqmp_disp_disable(struct zynqmp_disp *disp) zynqmp_disp_avbuf_disable(disp); } -static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, - unsigned long mode_clock) +/** + * zynqmp_disp_setup_clock - Configure the display controller pixel clock rate + * @disp: Display controller + * @mode_clock: The pixel clock rate, in Hz + * + * Return: 0 on success, or a negative error clock otherwise + */ +int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, + unsigned long mode_clock) { unsigned long rate; long diff; @@ -1398,186 +1360,13 @@ static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, return 0; } -static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc) -{ - return container_of(crtc, struct zynqmp_dpsub, crtc)->disp; -} - -static void -zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc, - struct drm_atomic_state *state) -{ - struct zynqmp_disp *disp = crtc_to_disp(crtc); - struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; - int ret, vrefresh; - - pm_runtime_get_sync(disp->dev); - - zynqmp_disp_setup_clock(disp, adjusted_mode->clock * 1000); - - ret = clk_prepare_enable(disp->dpsub->vid_clk); - if (ret) { - dev_err(disp->dev, "failed to enable the video clock\n"); - pm_runtime_put_sync(disp->dev); - return; - } - - zynqmp_disp_enable(disp); - - /* Delay of 3 vblank intervals for timing gen to be stable */ - vrefresh = (adjusted_mode->clock * 1000) / - (adjusted_mode->vtotal * adjusted_mode->htotal); - msleep(3 * 1000 / vrefresh); -} - -static void -zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc, - struct drm_atomic_state *state) -{ - struct zynqmp_disp *disp = crtc_to_disp(crtc); - struct drm_plane_state *old_plane_state; - - /* - * Disable the plane if active. The old plane state can be NULL in the - * .shutdown() path if the plane is already disabled, skip - * zynqmp_disp_plane_atomic_disable() in that case. - */ - old_plane_state = drm_atomic_get_old_plane_state(state, crtc->primary); - if (old_plane_state) - zynqmp_disp_plane_atomic_disable(crtc->primary, state); - - zynqmp_disp_disable(disp); - - drm_crtc_vblank_off(crtc); - - spin_lock_irq(&crtc->dev->event_lock); - if (crtc->state->event) { - drm_crtc_send_vblank_event(crtc, crtc->state->event); - crtc->state->event = NULL; - } - spin_unlock_irq(&crtc->dev->event_lock); - - clk_disable_unprepare(disp->dpsub->vid_clk); - pm_runtime_put_sync(disp->dev); -} - -static int zynqmp_disp_crtc_atomic_check(struct drm_crtc *crtc, - struct drm_atomic_state *state) -{ - return drm_atomic_add_affected_planes(state, crtc); -} - -static void -zynqmp_disp_crtc_atomic_begin(struct drm_crtc *crtc, - struct drm_atomic_state *state) -{ - drm_crtc_vblank_on(crtc); -} - -static void -zynqmp_disp_crtc_atomic_flush(struct drm_crtc *crtc, - struct drm_atomic_state *state) -{ - if (crtc->state->event) { - struct drm_pending_vblank_event *event; - - /* Consume the flip_done event from atomic helper. */ - event = crtc->state->event; - crtc->state->event = NULL; - - event->pipe = drm_crtc_index(crtc); - - WARN_ON(drm_crtc_vblank_get(crtc) != 0); - - spin_lock_irq(&crtc->dev->event_lock); - drm_crtc_arm_vblank_event(crtc, event); - spin_unlock_irq(&crtc->dev->event_lock); - } -} - -static const struct drm_crtc_helper_funcs zynqmp_disp_crtc_helper_funcs = { - .atomic_enable = zynqmp_disp_crtc_atomic_enable, - .atomic_disable = zynqmp_disp_crtc_atomic_disable, - .atomic_check = zynqmp_disp_crtc_atomic_check, - .atomic_begin = zynqmp_disp_crtc_atomic_begin, - .atomic_flush = zynqmp_disp_crtc_atomic_flush, -}; - -static int zynqmp_disp_crtc_enable_vblank(struct drm_crtc *crtc) -{ - struct zynqmp_disp *disp = crtc_to_disp(crtc); - - zynqmp_dp_enable_vblank(disp->dpsub->dp); - - return 0; -} - -static void zynqmp_disp_crtc_disable_vblank(struct drm_crtc *crtc) -{ - struct zynqmp_disp *disp = crtc_to_disp(crtc); - - zynqmp_dp_disable_vblank(disp->dpsub->dp); -} - -static const struct drm_crtc_funcs zynqmp_disp_crtc_funcs = { - .destroy = drm_crtc_cleanup, - .set_config = drm_atomic_helper_set_config, - .page_flip = drm_atomic_helper_page_flip, - .reset = drm_atomic_helper_crtc_reset, - .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, - .enable_vblank = zynqmp_disp_crtc_enable_vblank, - .disable_vblank = zynqmp_disp_crtc_disable_vblank, -}; - -static int zynqmp_disp_create_crtc(struct zynqmp_disp *disp) -{ - struct drm_plane *plane = &disp->dpsub->planes[ZYNQMP_DISP_LAYER_GFX]; - struct drm_crtc *crtc = &disp->dpsub->crtc; - int ret; - - ret = drm_crtc_init_with_planes(disp->drm, crtc, plane, - NULL, &zynqmp_disp_crtc_funcs, NULL); - if (ret < 0) - return ret; - - drm_crtc_helper_add(crtc, &zynqmp_disp_crtc_helper_funcs); - - /* Start with vertical blanking interrupt reporting disabled. */ - drm_crtc_vblank_off(crtc); - - return 0; -} - -static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp) -{ - u32 possible_crtcs = drm_crtc_mask(&disp->dpsub->crtc); - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(disp->layers); i++) - disp->dpsub->planes[i].possible_crtcs = possible_crtcs; -} - /* ----------------------------------------------------------------------------- * Initialization & Cleanup */ int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub) { - struct zynqmp_disp *disp = dpsub->disp; - int ret; - - ret = zynqmp_disp_create_planes(disp); - if (ret) - return ret; - - ret = zynqmp_disp_create_crtc(disp); - if (ret < 0) - return ret; - - zynqmp_disp_map_crtc_to_plane(disp); - - return 0; + return zynqmp_disp_create_planes(dpsub->disp); } int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) @@ -1617,7 +1406,7 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) if (ret) return ret; - layer = &disp->layers[ZYNQMP_DISP_LAYER_VID]; + layer = &disp->layers[ZYNQMP_DPSUB_LAYER_VID]; dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; return 0; diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h index 1b7f90a81857f..57cd540f550fe 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h @@ -25,13 +25,30 @@ #define ZYNQMP_DISP_MAX_DMA_BIT 44 struct device; +struct drm_atomic_state; struct drm_device; +struct drm_plane; struct platform_device; struct zynqmp_disp; struct zynqmp_dpsub; -void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp); -uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp); +/** + * enum zynqmp_dpsub_layer_id - Layer identifier + * @ZYNQMP_DPSUB_LAYER_VID: Video layer + * @ZYNQMP_DPSUB_LAYER_GFX: Graphics layer + */ +enum zynqmp_dpsub_layer_id { + ZYNQMP_DPSUB_LAYER_VID, + ZYNQMP_DPSUB_LAYER_GFX, +}; + +void zynqmp_disp_enable(struct zynqmp_disp *disp); +void zynqmp_disp_disable(struct zynqmp_disp *disp); +int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, + unsigned long mode_clock); + +void zynqmp_disp_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state); int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub); int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 3e4d164d40fef..7ad6a5d96c5f3 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -31,6 +31,7 @@ #include "zynqmp_disp.h" #include "zynqmp_dp.h" #include "zynqmp_dpsub.h" +#include "zynqmp_kms.h" static uint zynqmp_dp_aux_timeout_ms = 50; module_param_named(aux_timeout_ms, zynqmp_dp_aux_timeout_ms, uint, 0444); @@ -1560,7 +1561,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) zynqmp_dp_write(dp, ZYNQMP_DP_INT_STATUS, status); if (status & ZYNQMP_DP_INT_VBLANK_START) - zynqmp_disp_handle_vblank(dp->dpsub->disp); + zynqmp_dpsub_handle_vblank(dp->dpsub); if (status & ZYNQMP_DP_INT_HPD_EVENT) schedule_delayed_work(&dp->hpd_work, 0); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index e4e7f8fd96d22..8162bf18537b1 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -9,17 +9,199 @@ * - Laurent Pinchart */ +#include +#include #include #include #include +#include #include +#include #include +#include + +#include +#include +#include +#include #include "zynqmp_disp.h" #include "zynqmp_dp.h" #include "zynqmp_dpsub.h" #include "zynqmp_kms.h" +/* ----------------------------------------------------------------------------- + * DRM CRTC + */ + +static inline struct zynqmp_dpsub *crtc_to_dpsub(struct drm_crtc *crtc) +{ + return container_of(crtc, struct zynqmp_dpsub, crtc); +} + +static void zynqmp_dpsub_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); + struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; + int ret, vrefresh; + + pm_runtime_get_sync(dpsub->dev); + + zynqmp_disp_setup_clock(dpsub->disp, adjusted_mode->clock * 1000); + + ret = clk_prepare_enable(dpsub->vid_clk); + if (ret) { + dev_err(dpsub->dev, "failed to enable a pixel clock\n"); + pm_runtime_put_sync(dpsub->dev); + return; + } + + zynqmp_disp_enable(dpsub->disp); + + /* Delay of 3 vblank intervals for timing gen to be stable */ + vrefresh = (adjusted_mode->clock * 1000) / + (adjusted_mode->vtotal * adjusted_mode->htotal); + msleep(3 * 1000 / vrefresh); +} + +static void zynqmp_dpsub_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); + struct drm_plane_state *old_plane_state; + + /* + * Disable the plane if active. The old plane state can be NULL in the + * .shutdown() path if the plane is already disabled, skip + * zynqmp_disp_plane_atomic_disable() in that case. + */ + old_plane_state = drm_atomic_get_old_plane_state(state, crtc->primary); + if (old_plane_state) + zynqmp_disp_plane_atomic_disable(crtc->primary, state); + + zynqmp_disp_disable(dpsub->disp); + + drm_crtc_vblank_off(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); + + clk_disable_unprepare(dpsub->vid_clk); + pm_runtime_put_sync(dpsub->dev); +} + +static int zynqmp_dpsub_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + return drm_atomic_add_affected_planes(state, crtc); +} + +static void zynqmp_dpsub_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_on(crtc); +} + +static void zynqmp_dpsub_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + if (crtc->state->event) { + struct drm_pending_vblank_event *event; + + /* Consume the flip_done event from atomic helper. */ + event = crtc->state->event; + crtc->state->event = NULL; + + event->pipe = drm_crtc_index(crtc); + + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_arm_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static const struct drm_crtc_helper_funcs zynqmp_dpsub_crtc_helper_funcs = { + .atomic_enable = zynqmp_dpsub_crtc_atomic_enable, + .atomic_disable = zynqmp_dpsub_crtc_atomic_disable, + .atomic_check = zynqmp_dpsub_crtc_atomic_check, + .atomic_begin = zynqmp_dpsub_crtc_atomic_begin, + .atomic_flush = zynqmp_dpsub_crtc_atomic_flush, +}; + +static int zynqmp_dpsub_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); + + zynqmp_dp_enable_vblank(dpsub->dp); + + return 0; +} + +static void zynqmp_dpsub_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); + + zynqmp_dp_disable_vblank(dpsub->dp); +} + +static const struct drm_crtc_funcs zynqmp_dpsub_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = zynqmp_dpsub_crtc_enable_vblank, + .disable_vblank = zynqmp_dpsub_crtc_disable_vblank, +}; + +static int zynqmp_dpsub_create_crtc(struct zynqmp_dpsub *dpsub) +{ + struct drm_plane *plane = &dpsub->planes[ZYNQMP_DPSUB_LAYER_GFX]; + struct drm_crtc *crtc = &dpsub->crtc; + int ret; + + ret = drm_crtc_init_with_planes(&dpsub->drm, crtc, plane, + NULL, &zynqmp_dpsub_crtc_funcs, NULL); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &zynqmp_dpsub_crtc_helper_funcs); + + /* Start with vertical blanking interrupt reporting disabled. */ + drm_crtc_vblank_off(crtc); + + return 0; +} + +static void zynqmp_dpsub_map_crtc_to_plane(struct zynqmp_dpsub *dpsub) +{ + u32 possible_crtcs = drm_crtc_mask(&dpsub->crtc); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dpsub->planes); i++) + dpsub->planes[i].possible_crtcs = possible_crtcs; +} + +/** + * zynqmp_dpsub_handle_vblank - Handle the vblank event + * @dpsub: DisplayPort subsystem + * + * This function handles the vblank interrupt, and sends an event to + * CRTC object. This will be called by the DP vblank interrupt handler. + */ +void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub) +{ + drm_crtc_handle_vblank(&dpsub->crtc); +} + /* ----------------------------------------------------------------------------- * Initialization */ @@ -38,12 +220,18 @@ int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) if (ret) return ret; + ret = zynqmp_dpsub_create_crtc(dpsub); + if (ret < 0) + return ret; + + zynqmp_dpsub_map_crtc_to_plane(dpsub); + ret = zynqmp_dp_drm_init(dpsub); if (ret) return ret; /* Create the encoder and attach the bridge. */ - encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); + encoder->possible_crtcs |= drm_crtc_mask(&dpsub->crtc); drm_simple_encoder_init(&dpsub->drm, encoder, DRM_MODE_ENCODER_NONE); ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h b/drivers/gpu/drm/xlnx/zynqmp_kms.h index a6729d9d82cce..a8934b1abb051 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.h +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h @@ -14,6 +14,8 @@ struct zynqmp_dpsub; +void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub); + int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub); #endif /* _ZYNQMP_KMS_H_ */ From ee1229b35d20d00ed13009d81401032de06ac58e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 25/37] drm: xlnx: zynqmp_dpsub: Move planes handling to zynqmp_kms.c Decouple the planes handling from the display controller programming by moving the corresponding code from zynqmp_disp.c to zynqmp_kms.c. This prepares for using the DPSUB with a live video input, without creating DRM planes in the DPSUB driver. While at it, fix a typo in a comment. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 169 ++-------------------------- drivers/gpu/drm/xlnx/zynqmp_disp.h | 19 +++- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 2 + drivers/gpu/drm/xlnx/zynqmp_kms.c | 145 +++++++++++++++++++++++- 4 files changed, 167 insertions(+), 168 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index c7d95f3d2a845..e1d6bb43d5f16 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -9,10 +9,6 @@ * - Laurent Pinchart */ -#include -#include -#include -#include #include #include #include @@ -141,7 +137,6 @@ struct zynqmp_disp_layer { /** * struct zynqmp_disp - Display controller * @dev: Device structure - * @drm: DRM core * @dpsub: Display subsystem * @blend.base: Register I/O base address for the blender * @avbuf.base: Register I/O base address for the audio/video buffer manager @@ -150,7 +145,6 @@ struct zynqmp_disp_layer { */ struct zynqmp_disp { struct device *dev; - struct drm_device *drm; struct zynqmp_dpsub *dpsub; struct { @@ -380,11 +374,6 @@ static void zynqmp_disp_avbuf_write(struct zynqmp_disp *disp, int reg, u32 val) writel(val, disp->avbuf.base + reg); } -static bool zynqmp_disp_layer_is_gfx(const struct zynqmp_disp_layer *layer) -{ - return layer->id == ZYNQMP_DPSUB_LAYER_GFX; -} - static bool zynqmp_disp_layer_is_video(const struct zynqmp_disp_layer *layer) { return layer->id == ZYNQMP_DPSUB_LAYER_VID; @@ -722,8 +711,8 @@ static void zynqmp_disp_blend_set_bg_color(struct zynqmp_disp *disp, * @enable: True to enable global alpha blending * @alpha: Global alpha value (ignored if @enabled is false) */ -static void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, - bool enable, u32 alpha) +void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, + bool enable, u32 alpha) { zynqmp_disp_blend_write(disp, ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_VALUE(alpha) | @@ -904,8 +893,8 @@ zynqmp_disp_layer_find_format(struct zynqmp_disp_layer *layer, * supported by the layer. The number of formats in the array is returned * through the num_formats argument. */ -static u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, - unsigned int *num_formats) +u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, + unsigned int *num_formats) { unsigned int i; u32 *formats; @@ -929,7 +918,7 @@ static u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, * Enable the @layer in the audio/video buffer manager and the blender. DMA * channels are started separately by zynqmp_disp_layer_update(). */ -static void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer) +void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer) { zynqmp_disp_avbuf_enable_video(layer->disp, layer, ZYNQMP_DISP_LAYER_NONLIVE); @@ -945,7 +934,7 @@ static void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer) * Disable the layer by stopping its DMA channels and disabling it in the * audio/video buffer manager and the blender. */ -static void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) +void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) { unsigned int i; @@ -963,8 +952,8 @@ static void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) * * Set the format for @layer to @info. The layer must be disabled. */ -static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, - const struct drm_format_info *info) +void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, + const struct drm_format_info *info) { unsigned int i; @@ -1002,8 +991,8 @@ static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, * * Return: 0 on success, or the DMA descriptor failure error otherwise */ -static int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, - struct drm_plane_state *state) +int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, + struct drm_plane_state *state) { const struct drm_format_info *info = layer->drm_fmt; unsigned int i; @@ -1043,136 +1032,6 @@ static int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, return 0; } -static int -zynqmp_disp_plane_atomic_check(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, - plane); - struct drm_crtc_state *crtc_state; - - if (!new_plane_state->crtc) - return 0; - - crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); - if (IS_ERR(crtc_state)) - return PTR_ERR(crtc_state); - - return drm_atomic_helper_check_plane_state(new_plane_state, - crtc_state, - DRM_PLANE_NO_SCALING, - DRM_PLANE_NO_SCALING, - false, false); -} - -void -zynqmp_disp_plane_atomic_disable(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, - plane); - struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); - struct zynqmp_disp_layer *layer = &dpsub->disp->layers[plane->index]; - - if (!old_state->fb) - return; - - zynqmp_disp_layer_disable(layer); - - if (zynqmp_disp_layer_is_gfx(layer)) - zynqmp_disp_blend_set_global_alpha(layer->disp, false, - plane->state->alpha >> 8); -} - -static void -zynqmp_disp_plane_atomic_update(struct drm_plane *plane, - struct drm_atomic_state *state) -{ - struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); - struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); - struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); - struct zynqmp_disp_layer *layer = &dpsub->disp->layers[plane->index]; - bool format_changed = false; - - if (!old_state->fb || - old_state->fb->format->format != new_state->fb->format->format) - format_changed = true; - - /* - * If the format has changed (including going from a previously - * disabled state to any format), reconfigure the format. Disable the - * plane first if needed. - */ - if (format_changed) { - if (old_state->fb) - zynqmp_disp_layer_disable(layer); - - zynqmp_disp_layer_set_format(layer, new_state->fb->format); - } - - zynqmp_disp_layer_update(layer, new_state); - - if (zynqmp_disp_layer_is_gfx(layer)) - zynqmp_disp_blend_set_global_alpha(layer->disp, true, - plane->state->alpha >> 8); - - /* Enable or re-enable the plane is the format has changed. */ - if (format_changed) - zynqmp_disp_layer_enable(layer); -} - -static const struct drm_plane_helper_funcs zynqmp_disp_plane_helper_funcs = { - .atomic_check = zynqmp_disp_plane_atomic_check, - .atomic_update = zynqmp_disp_plane_atomic_update, - .atomic_disable = zynqmp_disp_plane_atomic_disable, -}; - -static const struct drm_plane_funcs zynqmp_disp_plane_funcs = { - .update_plane = drm_atomic_helper_update_plane, - .disable_plane = drm_atomic_helper_disable_plane, - .destroy = drm_plane_cleanup, - .reset = drm_atomic_helper_plane_reset, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, -}; - -static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) -{ - unsigned int i; - int ret; - - for (i = 0; i < ARRAY_SIZE(disp->layers); i++) { - struct zynqmp_disp_layer *layer = &disp->layers[i]; - struct drm_plane *plane = &disp->dpsub->planes[i]; - enum drm_plane_type type; - unsigned int num_formats; - u32 *formats; - - formats = zynqmp_disp_layer_drm_formats(layer, &num_formats); - if (!formats) - return -ENOMEM; - - /* Graphics layer is primary, and video layer is overlay. */ - type = zynqmp_disp_layer_is_video(layer) - ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; - ret = drm_universal_plane_init(disp->drm, plane, 0, - &zynqmp_disp_plane_funcs, - formats, num_formats, - NULL, type, NULL); - kfree(formats); - if (ret) - return ret; - - drm_plane_helper_add(plane, &zynqmp_disp_plane_helper_funcs); - - drm_plane_create_zpos_immutable_property(plane, i); - if (zynqmp_disp_layer_is_gfx(layer)) - drm_plane_create_alpha_property(plane); - } - - return 0; -} - /** * zynqmp_disp_layer_release_dma - Release DMA channels for a layer * @disp: Display controller @@ -1280,6 +1139,8 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) ret = zynqmp_disp_layer_request_dma(disp, layer); if (ret) goto err; + + disp->dpsub->layers[i] = layer; } return 0; @@ -1364,11 +1225,6 @@ int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, * Initialization & Cleanup */ -int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub) -{ - return zynqmp_disp_create_planes(dpsub->disp); -} - int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) { struct platform_device *pdev = to_platform_device(dpsub->dev); @@ -1383,7 +1239,6 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) disp->dev = &pdev->dev; disp->dpsub = dpsub; - disp->drm = drm; dpsub->disp = disp; diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h index 57cd540f550fe..663f7d67c78f9 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h @@ -25,11 +25,12 @@ #define ZYNQMP_DISP_MAX_DMA_BIT 44 struct device; -struct drm_atomic_state; struct drm_device; -struct drm_plane; +struct drm_format_info; +struct drm_plane_state; struct platform_device; struct zynqmp_disp; +struct zynqmp_disp_layer; struct zynqmp_dpsub; /** @@ -47,10 +48,18 @@ void zynqmp_disp_disable(struct zynqmp_disp *disp); int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, unsigned long mode_clock); -void zynqmp_disp_plane_atomic_disable(struct drm_plane *plane, - struct drm_atomic_state *state); +void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, + bool enable, u32 alpha); + +u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, + unsigned int *num_formats); +void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer); +void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer); +void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, + const struct drm_format_info *info); +int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, + struct drm_plane_state *state); -int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub); int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); void zynqmp_disp_remove(struct zynqmp_dpsub *dpsub); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 2a955895404fe..bad9148d2d3e5 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -21,6 +21,7 @@ struct device; struct drm_bridge; struct drm_device; struct zynqmp_disp; +struct zynqmp_disp_layer; struct zynqmp_dp; #define ZYNQMP_DPSUB_NUM_LAYERS 2 @@ -65,6 +66,7 @@ struct zynqmp_dpsub { struct drm_bridge *bridge; struct zynqmp_disp *disp; + struct zynqmp_disp_layer *layers[ZYNQMP_DPSUB_NUM_LAYERS]; struct zynqmp_dp *dp; unsigned int dma_align; diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index 8162bf18537b1..a2ad4c9167aba 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -11,12 +11,17 @@ #include #include +#include #include #include #include #include #include +#include +#include +#include #include +#include #include #include @@ -30,6 +35,137 @@ #include "zynqmp_dpsub.h" #include "zynqmp_kms.h" +/* ----------------------------------------------------------------------------- + * DRM Planes + */ + +static int zynqmp_dpsub_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_crtc_state *crtc_state; + + if (!new_plane_state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + return drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static void zynqmp_dpsub_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, + plane); + struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); + struct zynqmp_disp_layer *layer = dpsub->layers[plane->index]; + + if (!old_state->fb) + return; + + zynqmp_disp_layer_disable(layer); + + if (plane->index == ZYNQMP_DPSUB_LAYER_GFX) + zynqmp_disp_blend_set_global_alpha(dpsub->disp, false, + plane->state->alpha >> 8); +} + +static void zynqmp_dpsub_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); + struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); + struct zynqmp_disp_layer *layer = dpsub->layers[plane->index]; + bool format_changed = false; + + if (!old_state->fb || + old_state->fb->format->format != new_state->fb->format->format) + format_changed = true; + + /* + * If the format has changed (including going from a previously + * disabled state to any format), reconfigure the format. Disable the + * plane first if needed. + */ + if (format_changed) { + if (old_state->fb) + zynqmp_disp_layer_disable(layer); + + zynqmp_disp_layer_set_format(layer, new_state->fb->format); + } + + zynqmp_disp_layer_update(layer, new_state); + + if (plane->index == ZYNQMP_DPSUB_LAYER_GFX) + zynqmp_disp_blend_set_global_alpha(dpsub->disp, true, + plane->state->alpha >> 8); + + /* Enable or re-enable the plane if the format has changed. */ + if (format_changed) + zynqmp_disp_layer_enable(layer); +} + +static const struct drm_plane_helper_funcs zynqmp_dpsub_plane_helper_funcs = { + .atomic_check = zynqmp_dpsub_plane_atomic_check, + .atomic_update = zynqmp_dpsub_plane_atomic_update, + .atomic_disable = zynqmp_dpsub_plane_atomic_disable, +}; + +static const struct drm_plane_funcs zynqmp_dpsub_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int zynqmp_dpsub_create_planes(struct zynqmp_dpsub *dpsub) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(dpsub->planes); i++) { + struct zynqmp_disp_layer *layer = dpsub->layers[i]; + struct drm_plane *plane = &dpsub->planes[i]; + enum drm_plane_type type; + unsigned int num_formats; + u32 *formats; + + formats = zynqmp_disp_layer_drm_formats(layer, &num_formats); + if (!formats) + return -ENOMEM; + + /* Graphics layer is primary, and video layer is overlay. */ + type = i == ZYNQMP_DPSUB_LAYER_VID + ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; + ret = drm_universal_plane_init(&dpsub->drm, plane, 0, + &zynqmp_dpsub_plane_funcs, + formats, num_formats, + NULL, type, NULL); + kfree(formats); + if (ret) + return ret; + + drm_plane_helper_add(plane, &zynqmp_dpsub_plane_helper_funcs); + + drm_plane_create_zpos_immutable_property(plane, i); + if (i == ZYNQMP_DPSUB_LAYER_GFX) + drm_plane_create_alpha_property(plane); + } + + return 0; +} + /* ----------------------------------------------------------------------------- * DRM CRTC */ @@ -78,7 +214,7 @@ static void zynqmp_dpsub_crtc_atomic_disable(struct drm_crtc *crtc, */ old_plane_state = drm_atomic_get_old_plane_state(state, crtc->primary); if (old_plane_state) - zynqmp_disp_plane_atomic_disable(crtc->primary, state); + zynqmp_dpsub_plane_atomic_disable(crtc->primary, state); zynqmp_disp_disable(dpsub->disp); @@ -212,11 +348,8 @@ int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) struct drm_connector *connector; int ret; - /* - * Initialize the DISP and DP components. This will creates planes, - * CRTC, and a bridge for the DP encoder. - */ - ret = zynqmp_disp_drm_init(dpsub); + /* Create the planes and the CRTC, and initialize the DP encoder. */ + ret = zynqmp_dpsub_create_planes(dpsub); if (ret) return ret; From 2dfd045c84359e3fefe9967f5a3579f8d8587ead Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 26/37] drm: xlnx: zynqmp_dpsub: Register AUX bus at bridge attach time To prepare for operating as a standalone DP bridge with the DRM device implemented in the PL, move registration of the AUX bus to bridge attach time, as that's the earliest point when a DRM device is available. The DRM device pointer stored in zynqmp_dp isn't used anymore, drop it. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 41 +++++++++++++++++++------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 7ad6a5d96c5f3..544b062ce7236 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -276,7 +276,6 @@ struct zynqmp_dp_config { * struct zynqmp_dp - Xilinx DisplayPort core * @dev: device structure * @dpsub: Display subsystem - * @drm: DRM core * @iomem: device I/O memory for register access * @reset: reset controller * @irq: irq @@ -297,7 +296,6 @@ struct zynqmp_dp_config { struct zynqmp_dp { struct device *dev; struct zynqmp_dpsub *dpsub; - struct drm_device *drm; void __iomem *iomem; struct reset_control *reset; int irq; @@ -1057,7 +1055,7 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp) dp->aux.name = "ZynqMP DP AUX"; dp->aux.dev = dp->dev; - dp->aux.drm_dev = dp->drm; + dp->aux.drm_dev = dp->bridge.dev; dp->aux.transfer = zynqmp_dp_aux_transfer; return drm_dp_aux_register(&dp->aux); @@ -1283,14 +1281,35 @@ static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, struct zynqmp_dp *dp = bridge_to_dp(bridge); int ret; + /* Initialize and register the AUX adapter. */ + ret = zynqmp_dp_aux_init(dp); + if (ret) { + dev_err(dp->dev, "failed to initialize DP aux\n"); + return ret; + } + if (dp->next_bridge) { ret = drm_bridge_attach(bridge->encoder, dp->next_bridge, bridge, flags); if (ret < 0) - return ret; + goto error; } + /* Now that initialisation is complete, enable interrupts. */ + zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_ALL); + return 0; + +error: + zynqmp_dp_aux_cleanup(dp); + return ret; +} + +static void zynqmp_dp_bridge_detach(struct drm_bridge *bridge) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + + zynqmp_dp_aux_cleanup(dp); } static int zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge, @@ -1495,6 +1514,7 @@ static struct edid *zynqmp_dp_bridge_get_edid(struct drm_bridge *bridge, static const struct drm_bridge_funcs zynqmp_dp_bridge_funcs = { .attach = zynqmp_dp_bridge_attach, + .detach = zynqmp_dp_bridge_detach, .mode_valid = zynqmp_dp_bridge_mode_valid, .atomic_enable = zynqmp_dp_bridge_atomic_enable, .atomic_disable = zynqmp_dp_bridge_atomic_disable, @@ -1594,7 +1614,6 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) { struct zynqmp_dp *dp = dpsub->dp; struct drm_bridge *bridge = &dp->bridge; - int ret; dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; zynqmp_dp_set_format(dp, NULL, ZYNQMP_DPSUB_FORMAT_RGB, 8); @@ -1606,16 +1625,6 @@ int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) bridge->type = DRM_MODE_CONNECTOR_DisplayPort; dpsub->bridge = bridge; - /* Initialize and register the AUX adapter. */ - ret = zynqmp_dp_aux_init(dp); - if (ret) { - dev_err(dp->dev, "failed to initialize DP aux\n"); - return ret; - } - - /* Now that initialisation is complete, enable interrupts. */ - zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_ALL); - return 0; } @@ -1633,7 +1642,6 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) dp->dev = &pdev->dev; dp->dpsub = dpsub; dp->status = connector_status_disconnected; - dp->drm = drm; INIT_DELAYED_WORK(&dp->hpd_work, zynqmp_dp_hpd_work_func); @@ -1719,7 +1727,6 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub) disable_irq(dp->irq); cancel_delayed_work_sync(&dp->hpd_work); - zynqmp_dp_aux_cleanup(dp); zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0); zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, 0xffffffff); From 5889ee59031b75a5048870f0644419719a1d108d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 27/37] drm: xlnx: zynqmp_dpsub: Move DP bridge init to zynqmp_dp_probe() There's no need to delay bridge initialization, move it to zynqmp_dp_probe() and drop the zynqmp_dp_drm_init() function. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 30 ++++++++++++------------------ drivers/gpu/drm/xlnx/zynqmp_dp.h | 1 - drivers/gpu/drm/xlnx/zynqmp_kms.c | 6 +----- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 544b062ce7236..0c7add926da3d 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1610,27 +1610,10 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) * Initialization & Cleanup */ -int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) -{ - struct zynqmp_dp *dp = dpsub->dp; - struct drm_bridge *bridge = &dp->bridge; - - dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; - zynqmp_dp_set_format(dp, NULL, ZYNQMP_DPSUB_FORMAT_RGB, 8); - - /* Initialize the bridge. */ - bridge->funcs = &zynqmp_dp_bridge_funcs; - bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID - | DRM_BRIDGE_OP_HPD; - bridge->type = DRM_MODE_CONNECTOR_DisplayPort; - dpsub->bridge = bridge; - - return 0; -} - int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) { struct platform_device *pdev = to_platform_device(dpsub->dev); + struct drm_bridge *bridge; struct zynqmp_dp *dp; struct resource *res; int ret; @@ -1673,6 +1656,14 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) if (ret) goto err_reset; + /* Initialize the bridge. */ + bridge = &dp->bridge; + bridge->funcs = &zynqmp_dp_bridge_funcs; + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_HPD; + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; + dpsub->bridge = bridge; + /* * Acquire the next bridge in the chain. Ignore errors caused by port@5 * not being connected for backward-compatibility with older DTs. @@ -1683,6 +1674,9 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) goto err_reset; /* Initialize the hardware. */ + dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; + zynqmp_dp_set_format(dp, NULL, ZYNQMP_DPSUB_FORMAT_RGB, 8); + zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); zynqmp_dp_set(dp, ZYNQMP_DP_PHY_RESET, ZYNQMP_DP_PHY_RESET_ALL_RESET); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.h b/drivers/gpu/drm/xlnx/zynqmp_dp.h index 4507740093f6c..736d810fa16f4 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.h @@ -20,7 +20,6 @@ struct zynqmp_dpsub; void zynqmp_dp_enable_vblank(struct zynqmp_dp *dp); void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp); -int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub); int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index a2ad4c9167aba..3b89097a2c6e4 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -348,7 +348,7 @@ int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) struct drm_connector *connector; int ret; - /* Create the planes and the CRTC, and initialize the DP encoder. */ + /* Create the planes and the CRTC. */ ret = zynqmp_dpsub_create_planes(dpsub); if (ret) return ret; @@ -359,10 +359,6 @@ int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) zynqmp_dpsub_map_crtc_to_plane(dpsub); - ret = zynqmp_dp_drm_init(dpsub); - if (ret) - return ret; - /* Create the encoder and attach the bridge. */ encoder->possible_crtcs |= drm_crtc_mask(&dpsub->crtc); drm_simple_encoder_init(&dpsub->drm, encoder, DRM_MODE_ENCODER_NONE); From 6ca91bb43a63ad036a05491ad662aa18feb0e4ce Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 28/37] drm: xlnx: zynqmp_dpsub: Manage DP and DISP allocations manually The zynqmp_disp and zynqmp_dp structures are allocated with drmm_kzalloc(). While this simplifies management of memory, it requires a DRM device, which will not be available at probe time when the DP bridge will be used standalone, with a DRM device in the PL. To prepare for this, switch to manual allocation for zynqmp_disp and zynqmp_dp. The cleanup still uses the DRM managed infrastructure, but one level up, at the top level. This will be addressed separately. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 34 +++++++++++++++++++---------- drivers/gpu/drm/xlnx/zynqmp_disp.h | 3 +-- drivers/gpu/drm/xlnx/zynqmp_dp.c | 30 +++++++++++++++---------- drivers/gpu/drm/xlnx/zynqmp_dp.h | 3 +-- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 17 +++++++++++++-- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index e1d6bb43d5f16..e9d4b891fac94 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -22,6 +21,7 @@ #include #include #include +#include #include "zynqmp_disp.h" #include "zynqmp_disp_regs.h" @@ -1225,7 +1225,7 @@ int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, * Initialization & Cleanup */ -int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) +int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub) { struct platform_device *pdev = to_platform_device(dpsub->dev); struct zynqmp_disp *disp; @@ -1233,38 +1233,48 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) struct resource *res; int ret; - disp = drmm_kzalloc(drm, sizeof(*disp), GFP_KERNEL); + disp = kzalloc(sizeof(*disp), GFP_KERNEL); if (!disp) return -ENOMEM; disp->dev = &pdev->dev; disp->dpsub = dpsub; - dpsub->disp = disp; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "blend"); disp->blend.base = devm_ioremap_resource(disp->dev, res); - if (IS_ERR(disp->blend.base)) - return PTR_ERR(disp->blend.base); + if (IS_ERR(disp->blend.base)) { + ret = PTR_ERR(disp->blend.base); + goto error; + } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "av_buf"); disp->avbuf.base = devm_ioremap_resource(disp->dev, res); - if (IS_ERR(disp->avbuf.base)) - return PTR_ERR(disp->avbuf.base); + if (IS_ERR(disp->avbuf.base)) { + ret = PTR_ERR(disp->avbuf.base); + goto error; + } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud"); disp->audio.base = devm_ioremap_resource(disp->dev, res); - if (IS_ERR(disp->audio.base)) - return PTR_ERR(disp->audio.base); + if (IS_ERR(disp->audio.base)) { + ret = PTR_ERR(disp->audio.base); + goto error; + } ret = zynqmp_disp_create_layers(disp); if (ret) - return ret; + goto error; layer = &disp->layers[ZYNQMP_DPSUB_LAYER_VID]; dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; + dpsub->disp = disp; + return 0; + +error: + kfree(disp); + return ret; } void zynqmp_disp_remove(struct zynqmp_dpsub *dpsub) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h index 663f7d67c78f9..9b8b202224d9c 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h @@ -25,7 +25,6 @@ #define ZYNQMP_DISP_MAX_DMA_BIT 44 struct device; -struct drm_device; struct drm_format_info; struct drm_plane_state; struct platform_device; @@ -60,7 +59,7 @@ void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, struct drm_plane_state *state); -int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); +int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub); void zynqmp_disp_remove(struct zynqmp_dpsub *dpsub); #endif /* _ZYNQMP_DISP_H_ */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 0c7add926da3d..001bc24f92bd5 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include #include "zynqmp_disp.h" #include "zynqmp_dp.h" @@ -1610,7 +1610,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) * Initialization & Cleanup */ -int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) +int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub) { struct platform_device *pdev = to_platform_device(dpsub->dev); struct drm_bridge *bridge; @@ -1618,7 +1618,7 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) struct resource *res; int ret; - dp = drmm_kzalloc(drm, sizeof(*dp), GFP_KERNEL); + dp = kzalloc(sizeof(*dp), GFP_KERNEL); if (!dp) return -ENOMEM; @@ -1628,29 +1628,32 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) INIT_DELAYED_WORK(&dp->hpd_work, zynqmp_dp_hpd_work_func); - dpsub->dp = dp; - /* Acquire all resources (IOMEM, IRQ and PHYs). */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp"); dp->iomem = devm_ioremap_resource(dp->dev, res); - if (IS_ERR(dp->iomem)) - return PTR_ERR(dp->iomem); + if (IS_ERR(dp->iomem)) { + ret = PTR_ERR(dp->iomem); + goto err_free; + } dp->irq = platform_get_irq(pdev, 0); - if (dp->irq < 0) - return dp->irq; + if (dp->irq < 0) { + ret = dp->irq; + goto err_free; + } dp->reset = devm_reset_control_get(dp->dev, NULL); if (IS_ERR(dp->reset)) { if (PTR_ERR(dp->reset) != -EPROBE_DEFER) dev_err(dp->dev, "failed to get reset: %ld\n", PTR_ERR(dp->reset)); - return PTR_ERR(dp->reset); + ret = PTR_ERR(dp->reset); + goto err_free; } ret = zynqmp_dp_reset(dp, false); if (ret < 0) - return ret; + goto err_free; ret = zynqmp_dp_phy_probe(dp); if (ret) @@ -1700,6 +1703,8 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) if (ret < 0) goto err_phy_exit; + dpsub->dp = dp; + dev_dbg(dp->dev, "ZynqMP DisplayPort Tx probed with %u lanes\n", dp->num_lanes); @@ -1709,7 +1714,8 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) zynqmp_dp_phy_exit(dp); err_reset: zynqmp_dp_reset(dp, true); - +err_free: + kfree(dp); return ret; } diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.h b/drivers/gpu/drm/xlnx/zynqmp_dp.h index 736d810fa16f4..f077d7fbd0ada 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.h @@ -12,7 +12,6 @@ #ifndef _ZYNQMP_DP_H_ #define _ZYNQMP_DP_H_ -struct drm_device; struct platform_device; struct zynqmp_dp; struct zynqmp_dpsub; @@ -20,7 +19,7 @@ struct zynqmp_dpsub; void zynqmp_dp_enable_vblank(struct zynqmp_dp *dp); void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp); -int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); +int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub); void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub); #endif /* _ZYNQMP_DP_H_ */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 5d61f6c7aee56..6a6fba28fd6f9 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -247,6 +248,14 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) return 0; } +static void zynqmp_dpsub_release(struct drm_device *drm, void *res) +{ + struct zynqmp_dpsub *dpsub = res; + + kfree(dpsub->disp); + kfree(dpsub->dp); +} + static int zynqmp_dpsub_probe(struct platform_device *pdev) { struct zynqmp_dpsub *dpsub; @@ -258,6 +267,10 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) if (IS_ERR(dpsub)) return PTR_ERR(dpsub); + ret = drmm_add_action(&dpsub->drm, zynqmp_dpsub_release, dpsub); + if (ret < 0) + return ret; + dpsub->dev = &pdev->dev; platform_set_drvdata(pdev, dpsub); @@ -276,11 +289,11 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) * DP should be probed first so that the zynqmp_disp can set the output * format accordingly. */ - ret = zynqmp_dp_probe(dpsub, &dpsub->drm); + ret = zynqmp_dp_probe(dpsub); if (ret) goto err_pm; - ret = zynqmp_disp_probe(dpsub, &dpsub->drm); + ret = zynqmp_disp_probe(dpsub); if (ret) goto err_dp; From 074ef0ce9f8300697e6dc1f423124cc2e73d1eb5 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 29/37] drm: xlnx: zynqmp_dpsub: Move all DRM init and cleanup to zynqmp_kms.c Continue the isolation of DRM/KMS code by moving all DRM init and cleanup from zynqmp_dpsub.c to zynqmp_kms.c. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 117 +------------------------- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 5 -- drivers/gpu/drm/xlnx/zynqmp_kms.c | 123 +++++++++++++++++++++++++++- drivers/gpu/drm/xlnx/zynqmp_kms.h | 5 +- 4 files changed, 127 insertions(+), 123 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 6a6fba28fd6f9..946f58124e6fb 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -18,126 +18,16 @@ #include #include -#include -#include #include -#include -#include -#include -#include #include -#include +#include #include -#include -#include #include "zynqmp_disp.h" #include "zynqmp_dp.h" #include "zynqmp_dpsub.h" #include "zynqmp_kms.h" -/* ----------------------------------------------------------------------------- - * Dumb Buffer & Framebuffer Allocation - */ - -static int zynqmp_dpsub_dumb_create(struct drm_file *file_priv, - struct drm_device *drm, - struct drm_mode_create_dumb *args) -{ - struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); - unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); - - /* Enforce the alignment constraints of the DMA engine. */ - args->pitch = ALIGN(pitch, dpsub->dma_align); - - return drm_gem_dma_dumb_create_internal(file_priv, drm, args); -} - -static struct drm_framebuffer * -zynqmp_dpsub_fb_create(struct drm_device *drm, struct drm_file *file_priv, - const struct drm_mode_fb_cmd2 *mode_cmd) -{ - struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); - struct drm_mode_fb_cmd2 cmd = *mode_cmd; - unsigned int i; - - /* Enforce the alignment constraints of the DMA engine. */ - for (i = 0; i < ARRAY_SIZE(cmd.pitches); ++i) - cmd.pitches[i] = ALIGN(cmd.pitches[i], dpsub->dma_align); - - return drm_gem_fb_create(drm, file_priv, &cmd); -} - -static const struct drm_mode_config_funcs zynqmp_dpsub_mode_config_funcs = { - .fb_create = zynqmp_dpsub_fb_create, - .atomic_check = drm_atomic_helper_check, - .atomic_commit = drm_atomic_helper_commit, -}; - -/* ----------------------------------------------------------------------------- - * DRM/KMS Driver - */ - -DEFINE_DRM_GEM_DMA_FOPS(zynqmp_dpsub_drm_fops); - -static const struct drm_driver zynqmp_dpsub_drm_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM | - DRIVER_ATOMIC, - - DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(zynqmp_dpsub_dumb_create), - - .fops = &zynqmp_dpsub_drm_fops, - - .name = "zynqmp-dpsub", - .desc = "Xilinx DisplayPort Subsystem Driver", - .date = "20130509", - .major = 1, - .minor = 0, -}; - -static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) -{ - struct drm_device *drm = &dpsub->drm; - int ret; - - /* Initialize mode config, vblank and the KMS poll helper. */ - ret = drmm_mode_config_init(drm); - if (ret < 0) - return ret; - - drm->mode_config.funcs = &zynqmp_dpsub_mode_config_funcs; - drm->mode_config.min_width = 0; - drm->mode_config.min_height = 0; - drm->mode_config.max_width = ZYNQMP_DISP_MAX_WIDTH; - drm->mode_config.max_height = ZYNQMP_DISP_MAX_HEIGHT; - - ret = drm_vblank_init(drm, 1); - if (ret) - return ret; - - drm_kms_helper_poll_init(drm); - - ret = zynqmp_dpsub_kms_init(dpsub); - if (ret < 0) - goto err_poll_fini; - - /* Reset all components and register the DRM device. */ - drm_mode_config_reset(drm); - - ret = drm_dev_register(drm, 0); - if (ret < 0) - goto err_poll_fini; - - /* Initialize fbdev generic emulation. */ - drm_fbdev_generic_setup(drm, 24); - - return 0; - -err_poll_fini: - drm_kms_helper_poll_fini(drm); - return ret; -} - /* ----------------------------------------------------------------------------- * Power Management */ @@ -320,11 +210,8 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) static int zynqmp_dpsub_remove(struct platform_device *pdev) { struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); - struct drm_device *drm = &dpsub->drm; - drm_dev_unregister(drm); - drm_atomic_helper_shutdown(drm); - drm_kms_helper_poll_fini(drm); + zynqmp_dpsub_drm_cleanup(dpsub); zynqmp_disp_remove(dpsub); zynqmp_dp_remove(dpsub); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index bad9148d2d3e5..012dd05cf7bbd 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -72,11 +72,6 @@ struct zynqmp_dpsub { unsigned int dma_align; }; -static inline struct zynqmp_dpsub *to_zynqmp_dpsub(struct drm_device *drm) -{ - return container_of(drm, struct zynqmp_dpsub, drm); -} - bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub); unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index 3b89097a2c6e4..7e59f306d6897 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -16,12 +16,19 @@ #include #include #include +#include +#include #include +#include #include #include +#include +#include #include +#include #include #include +#include #include #include @@ -35,6 +42,11 @@ #include "zynqmp_dpsub.h" #include "zynqmp_kms.h" +static inline struct zynqmp_dpsub *to_zynqmp_dpsub(struct drm_device *drm) +{ + return container_of(drm, struct zynqmp_dpsub, drm); +} + /* ----------------------------------------------------------------------------- * DRM Planes */ @@ -339,10 +351,65 @@ void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub) } /* ----------------------------------------------------------------------------- - * Initialization + * Dumb Buffer & Framebuffer Allocation */ -int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) +static int zynqmp_dpsub_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); + unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + + /* Enforce the alignment constraints of the DMA engine. */ + args->pitch = ALIGN(pitch, dpsub->dma_align); + + return drm_gem_dma_dumb_create_internal(file_priv, drm, args); +} + +static struct drm_framebuffer * +zynqmp_dpsub_fb_create(struct drm_device *drm, struct drm_file *file_priv, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); + struct drm_mode_fb_cmd2 cmd = *mode_cmd; + unsigned int i; + + /* Enforce the alignment constraints of the DMA engine. */ + for (i = 0; i < ARRAY_SIZE(cmd.pitches); ++i) + cmd.pitches[i] = ALIGN(cmd.pitches[i], dpsub->dma_align); + + return drm_gem_fb_create(drm, file_priv, &cmd); +} + +static const struct drm_mode_config_funcs zynqmp_dpsub_mode_config_funcs = { + .fb_create = zynqmp_dpsub_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +/* ----------------------------------------------------------------------------- + * DRM/KMS Driver + */ + +DEFINE_DRM_GEM_DMA_FOPS(zynqmp_dpsub_drm_fops); + +const struct drm_driver zynqmp_dpsub_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | + DRIVER_ATOMIC, + + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(zynqmp_dpsub_dumb_create), + + .fops = &zynqmp_dpsub_drm_fops, + + .name = "zynqmp-dpsub", + .desc = "Xilinx DisplayPort Subsystem Driver", + .date = "20130509", + .major = 1, + .minor = 0, +}; + +static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) { struct drm_encoder *encoder = &dpsub->encoder; struct drm_connector *connector; @@ -385,3 +452,55 @@ int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) return 0; } + +int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) +{ + struct drm_device *drm = &dpsub->drm; + int ret; + + /* Initialize mode config, vblank and the KMS poll helper. */ + ret = drmm_mode_config_init(drm); + if (ret < 0) + return ret; + + drm->mode_config.funcs = &zynqmp_dpsub_mode_config_funcs; + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = ZYNQMP_DISP_MAX_WIDTH; + drm->mode_config.max_height = ZYNQMP_DISP_MAX_HEIGHT; + + ret = drm_vblank_init(drm, 1); + if (ret) + return ret; + + drm_kms_helper_poll_init(drm); + + ret = zynqmp_dpsub_kms_init(dpsub); + if (ret < 0) + goto err_poll_fini; + + /* Reset all components and register the DRM device. */ + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto err_poll_fini; + + /* Initialize fbdev generic emulation. */ + drm_fbdev_generic_setup(drm, 24); + + return 0; + +err_poll_fini: + drm_kms_helper_poll_fini(drm); + return ret; +} + +void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub) +{ + struct drm_device *drm = &dpsub->drm; + + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + drm_kms_helper_poll_fini(drm); +} diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h b/drivers/gpu/drm/xlnx/zynqmp_kms.h index a8934b1abb051..8074148fd4290 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.h +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h @@ -14,8 +14,11 @@ struct zynqmp_dpsub; +extern const struct drm_driver zynqmp_dpsub_drm_driver; + void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub); -int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub); +int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub); +void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub); #endif /* _ZYNQMP_KMS_H_ */ From d189835fffed7fed4aec15a41eaa27190ad4e04d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 30/37] drm: xlnx: zynqmp_dpsub: Decouple DRM device from zynqmp_dpsub To complete the decoupling of the DRM device from the zynqmp_dpsub, group all DRM-related structures in a zynqmp_dpsub_drm structure and allocate it separately from the zynqmp_dpsub. The DRM managed allocation of the drm_device now doesn't cover the zynqmp_dpsub anymore, so we need to register a cleanup action to release the zynqmp_dpsub when the drm_device is released. The will allow usage of the DisplayPort encoder as a standalone bridge, without registering a DRM device in this driver. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 41 ++++++++++-------- drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 18 +++----- drivers/gpu/drm/xlnx/zynqmp_kms.c | 66 ++++++++++++++++++++--------- drivers/gpu/drm/xlnx/zynqmp_kms.h | 24 ++++++++++- 4 files changed, 99 insertions(+), 50 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 946f58124e6fb..86faa6edda4b5 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -18,8 +18,6 @@ #include #include -#include -#include #include #include @@ -36,14 +34,20 @@ static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev) { struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); - return drm_mode_config_helper_suspend(&dpsub->drm); + if (!dpsub->drm) + return 0; + + return drm_mode_config_helper_suspend(&dpsub->drm->dev); } static int __maybe_unused zynqmp_dpsub_resume(struct device *dev) { struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); - return drm_mode_config_helper_resume(&dpsub->drm); + if (!dpsub->drm) + return 0; + + return drm_mode_config_helper_resume(&dpsub->drm->dev); } static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { @@ -138,12 +142,11 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) return 0; } -static void zynqmp_dpsub_release(struct drm_device *drm, void *res) +void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub) { - struct zynqmp_dpsub *dpsub = res; - kfree(dpsub->disp); kfree(dpsub->dp); + kfree(dpsub); } static int zynqmp_dpsub_probe(struct platform_device *pdev) @@ -152,14 +155,9 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) int ret; /* Allocate private data. */ - dpsub = devm_drm_dev_alloc(&pdev->dev, &zynqmp_dpsub_drm_driver, - struct zynqmp_dpsub, drm); - if (IS_ERR(dpsub)) - return PTR_ERR(dpsub); - - ret = drmm_add_action(&dpsub->drm, zynqmp_dpsub_release, dpsub); - if (ret < 0) - return ret; + dpsub = kzalloc(sizeof(*dpsub), GFP_KERNEL); + if (!dpsub) + return -ENOMEM; dpsub->dev = &pdev->dev; platform_set_drvdata(pdev, dpsub); @@ -204,6 +202,8 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) clk_disable_unprepare(dpsub->apb_clk); err_mem: of_reserved_mem_device_release(&pdev->dev); + if (!dpsub->drm) + zynqmp_dpsub_release(dpsub); return ret; } @@ -211,7 +211,8 @@ static int zynqmp_dpsub_remove(struct platform_device *pdev) { struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); - zynqmp_dpsub_drm_cleanup(dpsub); + if (dpsub->drm) + zynqmp_dpsub_drm_cleanup(dpsub); zynqmp_disp_remove(dpsub); zynqmp_dp_remove(dpsub); @@ -220,6 +221,9 @@ static int zynqmp_dpsub_remove(struct platform_device *pdev) clk_disable_unprepare(dpsub->apb_clk); of_reserved_mem_device_release(&pdev->dev); + if (!dpsub->drm) + zynqmp_dpsub_release(dpsub); + return 0; } @@ -227,7 +231,10 @@ static void zynqmp_dpsub_shutdown(struct platform_device *pdev) { struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); - drm_atomic_helper_shutdown(&dpsub->drm); + if (!dpsub->drm) + return; + + drm_atomic_helper_shutdown(&dpsub->drm->dev); } static const struct of_device_id zynqmp_dpsub_of_match[] = { diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 012dd05cf7bbd..6c6029ad9bc59 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -12,17 +12,13 @@ #ifndef _ZYNQMP_DPSUB_H_ #define _ZYNQMP_DPSUB_H_ -#include -#include -#include - struct clk; struct device; struct drm_bridge; -struct drm_device; struct zynqmp_disp; struct zynqmp_disp_layer; struct zynqmp_dp; +struct zynqmp_dpsub_drm; #define ZYNQMP_DPSUB_NUM_LAYERS 2 @@ -35,23 +31,19 @@ enum zynqmp_dpsub_format { /** * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem - * @drm: The DRM/KMS device * @dev: The physical device * @apb_clk: The APB clock * @vid_clk: Video clock * @vid_clk_from_ps: True of the video clock comes from PS, false from PL * @aud_clk: Audio clock * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL - * @planes: The DRM planes - * @crtc: The DRM CRTC - * @encoder: The dummy DRM encoder + * @drm: The DRM/KMS device data * @bridge: The DP encoder bridge * @disp: The display controller * @dp: The DisplayPort controller * @dma_align: DMA alignment constraint (must be a power of 2) */ struct zynqmp_dpsub { - struct drm_device drm; struct device *dev; struct clk *apb_clk; @@ -60,9 +52,7 @@ struct zynqmp_dpsub { struct clk *aud_clk; bool aud_clk_from_ps; - struct drm_plane planes[ZYNQMP_DPSUB_NUM_LAYERS]; - struct drm_crtc crtc; - struct drm_encoder encoder; + struct zynqmp_dpsub_drm *drm; struct drm_bridge *bridge; struct zynqmp_disp *disp; @@ -75,4 +65,6 @@ struct zynqmp_dpsub { bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub); unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub); +void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub); + #endif /* _ZYNQMP_DPSUB_H_ */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index 7e59f306d6897..1ac81807f4fd1 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -44,7 +44,7 @@ static inline struct zynqmp_dpsub *to_zynqmp_dpsub(struct drm_device *drm) { - return container_of(drm, struct zynqmp_dpsub, drm); + return container_of(drm, struct zynqmp_dpsub_drm, dev)->dpsub; } /* ----------------------------------------------------------------------------- @@ -146,9 +146,9 @@ static int zynqmp_dpsub_create_planes(struct zynqmp_dpsub *dpsub) unsigned int i; int ret; - for (i = 0; i < ARRAY_SIZE(dpsub->planes); i++) { + for (i = 0; i < ARRAY_SIZE(dpsub->drm->planes); i++) { struct zynqmp_disp_layer *layer = dpsub->layers[i]; - struct drm_plane *plane = &dpsub->planes[i]; + struct drm_plane *plane = &dpsub->drm->planes[i]; enum drm_plane_type type; unsigned int num_formats; u32 *formats; @@ -160,7 +160,7 @@ static int zynqmp_dpsub_create_planes(struct zynqmp_dpsub *dpsub) /* Graphics layer is primary, and video layer is overlay. */ type = i == ZYNQMP_DPSUB_LAYER_VID ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; - ret = drm_universal_plane_init(&dpsub->drm, plane, 0, + ret = drm_universal_plane_init(&dpsub->drm->dev, plane, 0, &zynqmp_dpsub_plane_funcs, formats, num_formats, NULL, type, NULL); @@ -184,7 +184,7 @@ static int zynqmp_dpsub_create_planes(struct zynqmp_dpsub *dpsub) static inline struct zynqmp_dpsub *crtc_to_dpsub(struct drm_crtc *crtc) { - return container_of(crtc, struct zynqmp_dpsub, crtc); + return container_of(crtc, struct zynqmp_dpsub_drm, crtc)->dpsub; } static void zynqmp_dpsub_crtc_atomic_enable(struct drm_crtc *crtc, @@ -312,11 +312,11 @@ static const struct drm_crtc_funcs zynqmp_dpsub_crtc_funcs = { static int zynqmp_dpsub_create_crtc(struct zynqmp_dpsub *dpsub) { - struct drm_plane *plane = &dpsub->planes[ZYNQMP_DPSUB_LAYER_GFX]; - struct drm_crtc *crtc = &dpsub->crtc; + struct drm_plane *plane = &dpsub->drm->planes[ZYNQMP_DPSUB_LAYER_GFX]; + struct drm_crtc *crtc = &dpsub->drm->crtc; int ret; - ret = drm_crtc_init_with_planes(&dpsub->drm, crtc, plane, + ret = drm_crtc_init_with_planes(&dpsub->drm->dev, crtc, plane, NULL, &zynqmp_dpsub_crtc_funcs, NULL); if (ret < 0) return ret; @@ -331,11 +331,11 @@ static int zynqmp_dpsub_create_crtc(struct zynqmp_dpsub *dpsub) static void zynqmp_dpsub_map_crtc_to_plane(struct zynqmp_dpsub *dpsub) { - u32 possible_crtcs = drm_crtc_mask(&dpsub->crtc); + u32 possible_crtcs = drm_crtc_mask(&dpsub->drm->crtc); unsigned int i; - for (i = 0; i < ARRAY_SIZE(dpsub->planes); i++) - dpsub->planes[i].possible_crtcs = possible_crtcs; + for (i = 0; i < ARRAY_SIZE(dpsub->drm->planes); i++) + dpsub->drm->planes[i].possible_crtcs = possible_crtcs; } /** @@ -347,7 +347,7 @@ static void zynqmp_dpsub_map_crtc_to_plane(struct zynqmp_dpsub *dpsub) */ void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub) { - drm_crtc_handle_vblank(&dpsub->crtc); + drm_crtc_handle_vblank(&dpsub->drm->crtc); } /* ----------------------------------------------------------------------------- @@ -394,7 +394,7 @@ static const struct drm_mode_config_funcs zynqmp_dpsub_mode_config_funcs = { DEFINE_DRM_GEM_DMA_FOPS(zynqmp_dpsub_drm_fops); -const struct drm_driver zynqmp_dpsub_drm_driver = { +static const struct drm_driver zynqmp_dpsub_drm_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, @@ -411,7 +411,7 @@ const struct drm_driver zynqmp_dpsub_drm_driver = { static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) { - struct drm_encoder *encoder = &dpsub->encoder; + struct drm_encoder *encoder = &dpsub->drm->encoder; struct drm_connector *connector; int ret; @@ -427,8 +427,8 @@ static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) zynqmp_dpsub_map_crtc_to_plane(dpsub); /* Create the encoder and attach the bridge. */ - encoder->possible_crtcs |= drm_crtc_mask(&dpsub->crtc); - drm_simple_encoder_init(&dpsub->drm, encoder, DRM_MODE_ENCODER_NONE); + encoder->possible_crtcs |= drm_crtc_mask(&dpsub->drm->crtc); + drm_simple_encoder_init(&dpsub->drm->dev, encoder, DRM_MODE_ENCODER_NONE); ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); @@ -438,7 +438,7 @@ static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) } /* Create the connector for the chain of bridges. */ - connector = drm_bridge_connector_init(&dpsub->drm, encoder); + connector = drm_bridge_connector_init(&dpsub->drm->dev, encoder); if (IS_ERR(connector)) { dev_err(dpsub->dev, "failed to created connector\n"); return PTR_ERR(connector); @@ -453,11 +453,39 @@ static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) return 0; } +static void zynqmp_dpsub_drm_release(struct drm_device *drm, void *res) +{ + struct zynqmp_dpsub_drm *dpdrm = res; + + zynqmp_dpsub_release(dpdrm->dpsub); +} + int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) { - struct drm_device *drm = &dpsub->drm; + struct zynqmp_dpsub_drm *dpdrm; + struct drm_device *drm; int ret; + /* + * Allocate the drm_device and immediately add a cleanup action to + * release the zynqmp_dpsub instance. If any of those operations fail, + * dpsub->drm will remain NULL, which tells the caller that it must + * cleanup manually. + */ + dpdrm = devm_drm_dev_alloc(dpsub->dev, &zynqmp_dpsub_drm_driver, + struct zynqmp_dpsub_drm, dev); + if (IS_ERR(dpdrm)) + return PTR_ERR(dpdrm); + + dpdrm->dpsub = dpsub; + drm = &dpdrm->dev; + + ret = drmm_add_action(drm, zynqmp_dpsub_drm_release, dpdrm); + if (ret < 0) + return ret; + + dpsub->drm = dpdrm; + /* Initialize mode config, vblank and the KMS poll helper. */ ret = drmm_mode_config_init(drm); if (ret < 0) @@ -498,7 +526,7 @@ int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub) { - struct drm_device *drm = &dpsub->drm; + struct drm_device *drm = &dpsub->drm->dev; drm_dev_unregister(drm); drm_atomic_helper_shutdown(drm); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h b/drivers/gpu/drm/xlnx/zynqmp_kms.h index 8074148fd4290..b5cb271591628 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.h +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h @@ -12,9 +12,31 @@ #ifndef _ZYNQMP_KMS_H_ #define _ZYNQMP_KMS_H_ +#include +#include +#include +#include + +#include "zynqmp_dpsub.h" + struct zynqmp_dpsub; -extern const struct drm_driver zynqmp_dpsub_drm_driver; +/** + * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem DRM/KMS data + * @dpsub: Backpointer to the DisplayPort subsystem + * @drm: The DRM/KMS device + * @planes: The DRM planes + * @crtc: The DRM CRTC + * @encoder: The dummy DRM encoder + */ +struct zynqmp_dpsub_drm { + struct zynqmp_dpsub *dpsub; + + struct drm_device dev; + struct drm_plane planes[ZYNQMP_DPSUB_NUM_LAYERS]; + struct drm_crtc crtc; + struct drm_encoder encoder; +}; void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub); From 4ce6ecd499749177b361d164849bd34924b7b3b7 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 31/37] drm: xlnx: zynqmp_dpsub: Rename zynqmp_dpsub_handle_vblank with DRM prefix The better convey its purpose, rename the zynqmp_dpsub_handle_vblank() function that belongs to the DRM layer to zynqmp_dpsub_drm_handle_vblank(). Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +- drivers/gpu/drm/xlnx/zynqmp_kms.c | 4 ++-- drivers/gpu/drm/xlnx/zynqmp_kms.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 001bc24f92bd5..5424f955be28f 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -1581,7 +1581,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) zynqmp_dp_write(dp, ZYNQMP_DP_INT_STATUS, status); if (status & ZYNQMP_DP_INT_VBLANK_START) - zynqmp_dpsub_handle_vblank(dp->dpsub); + zynqmp_dpsub_drm_handle_vblank(dp->dpsub); if (status & ZYNQMP_DP_INT_HPD_EVENT) schedule_delayed_work(&dp->hpd_work, 0); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index 1ac81807f4fd1..ff53a5ae8103e 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -339,13 +339,13 @@ static void zynqmp_dpsub_map_crtc_to_plane(struct zynqmp_dpsub *dpsub) } /** - * zynqmp_dpsub_handle_vblank - Handle the vblank event + * zynqmp_dpsub_drm_handle_vblank - Handle the vblank event * @dpsub: DisplayPort subsystem * * This function handles the vblank interrupt, and sends an event to * CRTC object. This will be called by the DP vblank interrupt handler. */ -void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub) +void zynqmp_dpsub_drm_handle_vblank(struct zynqmp_dpsub *dpsub) { drm_crtc_handle_vblank(&dpsub->drm->crtc); } diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h b/drivers/gpu/drm/xlnx/zynqmp_kms.h index b5cb271591628..01be96b00e3fb 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.h +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h @@ -38,7 +38,7 @@ struct zynqmp_dpsub_drm { struct drm_encoder encoder; }; -void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub); +void zynqmp_dpsub_drm_handle_vblank(struct zynqmp_dpsub *dpsub); int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub); void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub); From 52c2cf1471b35eda9a29c8dfdee5c687909e126a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 32/37] drm: xlnx: zynqmp_dpsub: Parse DT to find connected ports To prepare for live video input support, parse the device tree to find the connected ports. Warn about unsupported configurations, and error out when invalid. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 54 +++++++++++++++++++++++++++++ drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 13 +++++++ 2 files changed, 67 insertions(+) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 86faa6edda4b5..6627a1ec77919 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -142,6 +143,55 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) return 0; } +static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) +{ + struct device_node *np; + unsigned int i; + + /* + * For backward compatibility with old device trees that don't contain + * ports, consider that only the DP output port is connected if no + * ports child no exists. + */ + np = of_get_child_by_name(dpsub->dev->of_node, "ports"); + of_node_put(np); + if (!np) { + dev_warn(dpsub->dev, "missing ports, update DT bindings\n"); + dpsub->connected_ports = BIT(ZYNQMP_DPSUB_PORT_OUT_DP); + return 0; + } + + /* Check which ports are connected. */ + for (i = 0; i < ZYNQMP_DPSUB_NUM_PORTS; ++i) { + struct device_node *np; + + np = of_graph_get_remote_node(dpsub->dev->of_node, i, -1); + if (np) { + dpsub->connected_ports |= BIT(i); + of_node_put(np); + } + } + + /* Sanity checks. */ + if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) || + (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) + dev_warn(dpsub->dev, "live video unsupported, ignoring\n"); + + if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO)) + dev_warn(dpsub->dev, "live audio unsupported, ignoring\n"); + + if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_VIDEO)) || + (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_AUDIO))) + dev_warn(dpsub->dev, "output to PL unsupported, ignoring\n"); + + if (!(dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_DP))) { + dev_err(dpsub->dev, "DP output port not connected\n"); + return -EINVAL; + } + + return 0; +} + void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub) { kfree(dpsub->disp); @@ -171,6 +221,10 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) if (ret < 0) goto err_mem; + ret = zynqmp_dpsub_parse_dt(dpsub); + if (ret < 0) + goto err_mem; + pm_runtime_enable(&pdev->dev); /* diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 6c6029ad9bc59..6ded6e45ac0a4 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -22,6 +22,16 @@ struct zynqmp_dpsub_drm; #define ZYNQMP_DPSUB_NUM_LAYERS 2 +enum zynqmp_dpsub_port { + ZYNQMP_DPSUB_PORT_LIVE_VIDEO, + ZYNQMP_DPSUB_PORT_LIVE_GFX, + ZYNQMP_DPSUB_PORT_LIVE_AUDIO, + ZYNQMP_DPSUB_PORT_OUT_VIDEO, + ZYNQMP_DPSUB_PORT_OUT_AUDIO, + ZYNQMP_DPSUB_PORT_OUT_DP, + ZYNQMP_DPSUB_NUM_PORTS, +}; + enum zynqmp_dpsub_format { ZYNQMP_DPSUB_FORMAT_RGB, ZYNQMP_DPSUB_FORMAT_YCRCB444, @@ -37,6 +47,7 @@ enum zynqmp_dpsub_format { * @vid_clk_from_ps: True of the video clock comes from PS, false from PL * @aud_clk: Audio clock * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL + * @connected_ports: Bitmask of connected ports in the device tree * @drm: The DRM/KMS device data * @bridge: The DP encoder bridge * @disp: The display controller @@ -52,6 +63,8 @@ struct zynqmp_dpsub { struct clk *aud_clk; bool aud_clk_from_ps; + unsigned int connected_ports; + struct zynqmp_dpsub_drm *drm; struct drm_bridge *bridge; From 3662bbfca5d2b83c5d549f237a8d87f69aefa46c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 33/37] drm: xlnx: zynqmp_dpsub: Allow configuration of layer mode Add a mode parameter to the zynqmp_disp_layer_enable() to set the layer mode, to prepare for live mode support. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 30 +++++++++--------------------- drivers/gpu/drm/xlnx/zynqmp_disp.h | 13 ++++++++++++- drivers/gpu/drm/xlnx/zynqmp_kms.c | 2 +- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index e9d4b891fac94..e572cbb5f293e 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -78,16 +78,6 @@ struct zynqmp_disp_format { const u32 *sf; }; -/** - * enum zynqmp_disp_layer_mode - Layer mode - * @ZYNQMP_DISP_LAYER_NONLIVE: non-live (memory) mode - * @ZYNQMP_DISP_LAYER_LIVE: live (stream) mode - */ -enum zynqmp_disp_layer_mode { - ZYNQMP_DISP_LAYER_NONLIVE, - ZYNQMP_DISP_LAYER_LIVE -}; - /** * struct zynqmp_disp_layer_dma - DMA channel for one data plane of a layer * @chan: DMA channel @@ -131,7 +121,7 @@ struct zynqmp_disp_layer { const struct zynqmp_disp_format *disp_fmt; const struct drm_format_info *drm_fmt; - enum zynqmp_disp_layer_mode mode; + enum zynqmp_dpsub_layer_mode mode; }; /** @@ -519,27 +509,25 @@ static void zynqmp_disp_avbuf_disable_audio(struct zynqmp_disp *disp) * zynqmp_disp_avbuf_enable_video - Enable a video layer * @disp: Display controller * @layer: The layer - * @mode: Operating mode of layer * * Enable the video/graphics buffer for @layer. */ static void zynqmp_disp_avbuf_enable_video(struct zynqmp_disp *disp, - struct zynqmp_disp_layer *layer, - enum zynqmp_disp_layer_mode mode) + struct zynqmp_disp_layer *layer) { u32 val; val = zynqmp_disp_avbuf_read(disp, ZYNQMP_DISP_AV_BUF_OUTPUT); if (zynqmp_disp_layer_is_video(layer)) { val &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK; - if (mode == ZYNQMP_DISP_LAYER_NONLIVE) + if (layer->mode == ZYNQMP_DPSUB_LAYER_NONLIVE) val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM; else val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE; } else { val &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK; val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM; - if (mode == ZYNQMP_DISP_LAYER_NONLIVE) + if (layer->mode == ZYNQMP_DPSUB_LAYER_NONLIVE) val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM; else val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE; @@ -914,17 +902,17 @@ u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, /** * zynqmp_disp_layer_enable - Enable a layer * @layer: The layer + * @mode: Operating mode of layer * * Enable the @layer in the audio/video buffer manager and the blender. DMA * channels are started separately by zynqmp_disp_layer_update(). */ -void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer) +void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer, + enum zynqmp_dpsub_layer_mode mode) { - zynqmp_disp_avbuf_enable_video(layer->disp, layer, - ZYNQMP_DISP_LAYER_NONLIVE); + layer->mode = mode; + zynqmp_disp_avbuf_enable_video(layer->disp, layer); zynqmp_disp_blend_layer_enable(layer->disp, layer); - - layer->mode = ZYNQMP_DISP_LAYER_NONLIVE; } /** diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h index 9b8b202224d9c..123cffac08be7 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h @@ -42,6 +42,16 @@ enum zynqmp_dpsub_layer_id { ZYNQMP_DPSUB_LAYER_GFX, }; +/** + * enum zynqmp_dpsub_layer_mode - Layer mode + * @ZYNQMP_DPSUB_LAYER_NONLIVE: non-live (memory) mode + * @ZYNQMP_DPSUB_LAYER_LIVE: live (stream) mode + */ +enum zynqmp_dpsub_layer_mode { + ZYNQMP_DPSUB_LAYER_NONLIVE, + ZYNQMP_DPSUB_LAYER_LIVE, +}; + void zynqmp_disp_enable(struct zynqmp_disp *disp); void zynqmp_disp_disable(struct zynqmp_disp *disp); int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, @@ -52,7 +62,8 @@ void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, unsigned int *num_formats); -void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer); +void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer, + enum zynqmp_dpsub_layer_mode mode); void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer); void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, const struct drm_format_info *info); diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c index ff53a5ae8103e..1847792cf13db 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_kms.c +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -123,7 +123,7 @@ static void zynqmp_dpsub_plane_atomic_update(struct drm_plane *plane, /* Enable or re-enable the plane if the format has changed. */ if (format_changed) - zynqmp_disp_layer_enable(layer); + zynqmp_disp_layer_enable(layer, ZYNQMP_DPSUB_LAYER_NONLIVE); } static const struct drm_plane_helper_funcs zynqmp_dpsub_plane_helper_funcs = { From 51ae3bd4f0577d250c2b95f58fa93e7937136498 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 34/37] drm: xlnx: zynqmp_dpsub: Support operation without DMA engine To prepare for usage of the DPSUB as a DisplayPort bridge without creating a DRM device, make initialization and usage of the DMA engine optional. The flag that controls this feature is currently hardcoded to operating with the DMA engine, this will be made dynamic based on the device tree configuration in a subsequent change. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_disp.c | 26 ++++++++++++++++++++------ drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 3 +++ drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 3 +++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index e572cbb5f293e..3b87eebddc979 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -926,8 +926,10 @@ void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) { unsigned int i; - for (i = 0; i < layer->drm_fmt->num_planes; i++) - dmaengine_terminate_sync(layer->dmas[i].chan); + if (layer->disp->dpsub->dma_enabled) { + for (i = 0; i < layer->drm_fmt->num_planes; i++) + dmaengine_terminate_sync(layer->dmas[i].chan); + } zynqmp_disp_avbuf_disable_video(layer->disp, layer); zynqmp_disp_blend_layer_disable(layer->disp, layer); @@ -950,6 +952,9 @@ void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, zynqmp_disp_avbuf_set_format(layer->disp, layer, layer->disp_fmt); + if (!layer->disp->dpsub->dma_enabled) + return; + /* * Set pconfig for each DMA channel to indicate they're part of a * video group. @@ -985,6 +990,9 @@ int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, const struct drm_format_info *info = layer->drm_fmt; unsigned int i; + if (!layer->disp->dpsub->dma_enabled) + return 0; + for (i = 0; i < info->num_planes; i++) { unsigned int width = state->crtc_w / (i ? info->hsub : 1); unsigned int height = state->crtc_h / (i ? info->vsub : 1); @@ -1032,7 +1040,7 @@ static void zynqmp_disp_layer_release_dma(struct zynqmp_disp *disp, { unsigned int i; - if (!layer->info) + if (!layer->info || !disp->dpsub->dma_enabled) return; for (i = 0; i < layer->info->num_channels; i++) { @@ -1075,6 +1083,9 @@ static int zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp, unsigned int i; int ret; + if (!disp->dpsub->dma_enabled) + return 0; + for (i = 0; i < layer->info->num_channels; i++) { struct zynqmp_disp_layer_dma *dma = &layer->dmas[i]; char dma_channel_name[16]; @@ -1217,7 +1228,6 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub) { struct platform_device *pdev = to_platform_device(dpsub->dev); struct zynqmp_disp *disp; - struct zynqmp_disp_layer *layer; struct resource *res; int ret; @@ -1253,8 +1263,12 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub) if (ret) goto error; - layer = &disp->layers[ZYNQMP_DPSUB_LAYER_VID]; - dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; + if (disp->dpsub->dma_enabled) { + struct zynqmp_disp_layer *layer; + + layer = &disp->layers[ZYNQMP_DPSUB_LAYER_VID]; + dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; + } dpsub->disp = disp; diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 6627a1ec77919..6e4cd4479de14 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -158,6 +158,7 @@ static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) if (!np) { dev_warn(dpsub->dev, "missing ports, update DT bindings\n"); dpsub->connected_ports = BIT(ZYNQMP_DPSUB_PORT_OUT_DP); + dpsub->dma_enabled = true; return 0; } @@ -177,6 +178,8 @@ static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) dev_warn(dpsub->dev, "live video unsupported, ignoring\n"); + dpsub->dma_enabled = true; + if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO)) dev_warn(dpsub->dev, "live audio unsupported, ignoring\n"); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index 6ded6e45ac0a4..09ea01878f2af 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -48,6 +48,8 @@ enum zynqmp_dpsub_format { * @aud_clk: Audio clock * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL * @connected_ports: Bitmask of connected ports in the device tree + * @dma_enabled: True if the DMA interface is enabled, false if the DPSUB is + * driven by the live input * @drm: The DRM/KMS device data * @bridge: The DP encoder bridge * @disp: The display controller @@ -64,6 +66,7 @@ struct zynqmp_dpsub { bool aud_clk_from_ps; unsigned int connected_ports; + bool dma_enabled; struct zynqmp_dpsub_drm *drm; struct drm_bridge *bridge; From 56167161239472c35e902d40a13d1c6b2ff5e7c7 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:29:52 +0300 Subject: [PATCH 35/37] drm: xlnx: zynqmp_dpsub: Add support for live video input Add partial live video support, with a single video input that bypasses blending. Skip registration of the DRM device in that case, but register the DRM bridge instead. The DRM device will be created by the driver for the display controller in the PL. Full live video mode with concurrent usage of the video and gfx inputs, and blending in the DPSUB video pipeline, is currently unsupported. Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/xlnx/zynqmp_dp.c | 55 +++++++++++++++++++++++++++++ drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 32 +++++++++++++---- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index 5424f955be28f..7c9ae167eac7b 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -1271,6 +1272,55 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, zynqmp_dp_write(dp, ZYNQMP_DP_USER_DATA_COUNT_PER_LANE, reg); } +/* ----------------------------------------------------------------------------- + * DISP Configuration + */ + +static void zynqmp_dp_disp_enable(struct zynqmp_dp *dp, + struct drm_bridge_state *old_bridge_state) +{ + enum zynqmp_dpsub_layer_id layer_id; + struct zynqmp_disp_layer *layer; + const struct drm_format_info *info; + + if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) + layer_id = ZYNQMP_DPSUB_LAYER_VID; + else if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX)) + layer_id = ZYNQMP_DPSUB_LAYER_GFX; + else + return; + + layer = dp->dpsub->layers[layer_id]; + + /* TODO: Make the format configurable. */ + info = drm_format_info(DRM_FORMAT_YUV422); + zynqmp_disp_layer_set_format(layer, info); + zynqmp_disp_layer_enable(layer, ZYNQMP_DPSUB_LAYER_LIVE); + + if (layer_id == ZYNQMP_DPSUB_LAYER_GFX) + zynqmp_disp_blend_set_global_alpha(dp->dpsub->disp, true, 255); + else + zynqmp_disp_blend_set_global_alpha(dp->dpsub->disp, false, 0); + + zynqmp_disp_enable(dp->dpsub->disp); +} + +static void zynqmp_dp_disp_disable(struct zynqmp_dp *dp, + struct drm_bridge_state *old_bridge_state) +{ + struct zynqmp_disp_layer *layer; + + if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) + layer = dp->dpsub->layers[ZYNQMP_DPSUB_LAYER_VID]; + else if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX)) + layer = dp->dpsub->layers[ZYNQMP_DPSUB_LAYER_GFX]; + else + return; + + zynqmp_disp_disable(dp->dpsub->disp); + zynqmp_disp_layer_disable(layer); +} + /* ----------------------------------------------------------------------------- * DRM Bridge */ @@ -1355,6 +1405,8 @@ static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge, pm_runtime_get_sync(dp->dev); + zynqmp_dp_disp_enable(dp, old_bridge_state); + /* * Retrieve the CRTC mode and adjusted mode. This requires a little * dance to go from the bridge to the encoder, to the connector and to @@ -1428,6 +1480,9 @@ static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge, ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); if (zynqmp_dpsub_audio_enabled(dp->dpsub)) zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0); + + zynqmp_dp_disp_disable(dp, old_bridge_state); + pm_runtime_put_sync(dp->dev); } diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 6e4cd4479de14..bab862484d429 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -174,11 +175,22 @@ static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) } /* Sanity checks. */ - if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) || - (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) - dev_warn(dpsub->dev, "live video unsupported, ignoring\n"); + if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) && + (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) { + dev_err(dpsub->dev, "only one live video input is supported\n"); + return -EINVAL; + } - dpsub->dma_enabled = true; + if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) || + (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) { + if (dpsub->vid_clk_from_ps) { + dev_err(dpsub->dev, + "live video input requires PL clock\n"); + return -EINVAL; + } + } else { + dpsub->dma_enabled = true; + } if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO)) dev_warn(dpsub->dev, "live audio unsupported, ignoring\n"); @@ -242,9 +254,13 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) if (ret) goto err_dp; - ret = zynqmp_dpsub_drm_init(dpsub); - if (ret) - goto err_disp; + if (dpsub->dma_enabled) { + ret = zynqmp_dpsub_drm_init(dpsub); + if (ret) + goto err_disp; + } else { + drm_bridge_add(dpsub->bridge); + } dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed"); @@ -270,6 +286,8 @@ static int zynqmp_dpsub_remove(struct platform_device *pdev) if (dpsub->drm) zynqmp_dpsub_drm_cleanup(dpsub); + else + drm_bridge_remove(dpsub->bridge); zynqmp_disp_remove(dpsub); zynqmp_dp_remove(dpsub); From 1f367ee9554af5d67c86f206e1d6889cc99f6f45 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:02:23 +0300 Subject: [PATCH 36/37] arm64: dts: zynqmp: Add ports for the DisplayPort subsystem The DPSUB DT bindings now specify ports to model the connections with the programmable logic and the DisplayPort output. Add them to the device tree. Signed-off-by: Laurent Pinchart Acked-by: Michal Simek --- arch/arm64/boot/dts/xilinx/zynqmp.dtsi | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi index a549265e55f6e..307c76cd8544c 100644 --- a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi +++ b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi @@ -930,6 +930,30 @@ <&zynqmp_dpdma ZYNQMP_DPDMA_VIDEO1>, <&zynqmp_dpdma ZYNQMP_DPDMA_VIDEO2>, <&zynqmp_dpdma ZYNQMP_DPDMA_GRAPHICS>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + }; + port@1 { + reg = <1>; + }; + port@2 { + reg = <2>; + }; + port@3 { + reg = <3>; + }; + port@4 { + reg = <4>; + }; + port@5 { + reg = <5>; + }; + }; }; }; }; From ddcb8fa6514f2baf0fdb45e7ba12fbf3abb112c7 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Aug 2021 14:19:32 +0300 Subject: [PATCH 37/37] arm64: dts: zynqmp: zcu106a: Describe DisplayPort connector Add a device tree node to describe the DisplayPort connector, and connect it to the DPSUB output. Signed-off-by: Laurent Pinchart Acked-by: Michal Simek --- .../boot/dts/xilinx/zynqmp-zcu106-revA.dts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/arm64/boot/dts/xilinx/zynqmp-zcu106-revA.dts b/arch/arm64/boot/dts/xilinx/zynqmp-zcu106-revA.dts index e2dd72fe33ce8..24a252317150f 100644 --- a/arch/arm64/boot/dts/xilinx/zynqmp-zcu106-revA.dts +++ b/arch/arm64/boot/dts/xilinx/zynqmp-zcu106-revA.dts @@ -150,6 +150,18 @@ #clock-cells = <0>; clock-frequency = <114285000>; }; + + dpcon { + compatible = "dp-connector"; + label = "P11"; + type = "full-size"; + + port { + dpcon_in: endpoint { + remote-endpoint = <&dpsub_dp_out>; + }; + }; + }; }; &can1 { @@ -1015,4 +1027,12 @@ phy-names = "dp-phy0", "dp-phy1"; phys = <&psgtr 1 PHY_TYPE_DP 0 3>, <&psgtr 0 PHY_TYPE_DP 1 3>; + + ports { + port@5 { + dpsub_dp_out: endpoint { + remote-endpoint = <&dpcon_in>; + }; + }; + }; };