1c943b494SChandan Uddaraju // SPDX-License-Identifier: GPL-2.0-only
2c943b494SChandan Uddaraju /*
3c943b494SChandan Uddaraju * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
4c943b494SChandan Uddaraju */
5c943b494SChandan Uddaraju
6c943b494SChandan Uddaraju #include <drm/drm_atomic_helper.h>
7c943b494SChandan Uddaraju #include <drm/drm_atomic.h>
84b296d15SBjorn Andersson #include <drm/drm_bridge.h>
9a586191cSDmitry Baryshkov #include <drm/drm_bridge_connector.h>
10c943b494SChandan Uddaraju #include <drm/drm_crtc.h>
11c943b494SChandan Uddaraju
12c943b494SChandan Uddaraju #include "msm_drv.h"
13c943b494SChandan Uddaraju #include "msm_kms.h"
14c943b494SChandan Uddaraju #include "dp_drm.h"
15c943b494SChandan Uddaraju
16c943b494SChandan Uddaraju /**
17a586191cSDmitry Baryshkov * dp_bridge_detect - callback to determine if connector is connected
18a586191cSDmitry Baryshkov * @bridge: Pointer to drm bridge structure
19a586191cSDmitry Baryshkov * Returns: Bridge's 'is connected' status
20c943b494SChandan Uddaraju */
dp_bridge_detect(struct drm_bridge * bridge)21a586191cSDmitry Baryshkov static enum drm_connector_status dp_bridge_detect(struct drm_bridge *bridge)
22c943b494SChandan Uddaraju {
23c943b494SChandan Uddaraju struct msm_dp *dp;
24c943b494SChandan Uddaraju
2513ea4799SDmitry Baryshkov dp = to_dp_bridge(bridge)->dp_display;
26c943b494SChandan Uddaraju
27202aceacSKuogee Hsieh drm_dbg_dp(dp->drm_dev, "is_connected = %s\n",
28c943b494SChandan Uddaraju (dp->is_connected) ? "true" : "false");
29c943b494SChandan Uddaraju
30c943b494SChandan Uddaraju return (dp->is_connected) ? connector_status_connected :
31c943b494SChandan Uddaraju connector_status_disconnected;
32c943b494SChandan Uddaraju }
33c943b494SChandan Uddaraju
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)343a661247SKuogee Hsieh static int dp_bridge_atomic_check(struct drm_bridge *bridge,
353a661247SKuogee Hsieh struct drm_bridge_state *bridge_state,
363a661247SKuogee Hsieh struct drm_crtc_state *crtc_state,
373a661247SKuogee Hsieh struct drm_connector_state *conn_state)
383a661247SKuogee Hsieh {
393a661247SKuogee Hsieh struct msm_dp *dp;
403a661247SKuogee Hsieh
413a661247SKuogee Hsieh dp = to_dp_bridge(bridge)->dp_display;
423a661247SKuogee Hsieh
433a661247SKuogee Hsieh drm_dbg_dp(dp->drm_dev, "is_connected = %s\n",
443a661247SKuogee Hsieh (dp->is_connected) ? "true" : "false");
453a661247SKuogee Hsieh
463a661247SKuogee Hsieh /*
473a661247SKuogee Hsieh * There is no protection in the DRM framework to check if the display
483a661247SKuogee Hsieh * pipeline has been already disabled before trying to disable it again.
493a661247SKuogee Hsieh * Hence if the sink is unplugged, the pipeline gets disabled, but the
503a661247SKuogee Hsieh * crtc->active is still true. Any attempt to set the mode or manually
513a661247SKuogee Hsieh * disable this encoder will result in the crash.
523a661247SKuogee Hsieh *
533a661247SKuogee Hsieh * TODO: add support for telling the DRM subsystem that the pipeline is
543a661247SKuogee Hsieh * disabled by the hardware and thus all access to it should be forbidden.
553a661247SKuogee Hsieh * After that this piece of code can be removed.
563a661247SKuogee Hsieh */
573a661247SKuogee Hsieh if (bridge->ops & DRM_BRIDGE_OP_HPD)
583a661247SKuogee Hsieh return (dp->is_connected) ? 0 : -ENOTCONN;
593a661247SKuogee Hsieh
603a661247SKuogee Hsieh return 0;
613a661247SKuogee Hsieh }
623a661247SKuogee Hsieh
633a661247SKuogee Hsieh
64c943b494SChandan Uddaraju /**
65a586191cSDmitry Baryshkov * dp_bridge_get_modes - callback to add drm modes via drm_mode_probed_add()
66a586191cSDmitry Baryshkov * @bridge: Poiner to drm bridge
67c943b494SChandan Uddaraju * @connector: Pointer to drm connector structure
68c943b494SChandan Uddaraju * Returns: Number of modes added
69c943b494SChandan Uddaraju */
dp_bridge_get_modes(struct drm_bridge * bridge,struct drm_connector * connector)70a586191cSDmitry Baryshkov static int dp_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector)
71c943b494SChandan Uddaraju {
72c943b494SChandan Uddaraju int rc = 0;
73c943b494SChandan Uddaraju struct msm_dp *dp;
74c943b494SChandan Uddaraju
75c943b494SChandan Uddaraju if (!connector)
76c943b494SChandan Uddaraju return 0;
77c943b494SChandan Uddaraju
7813ea4799SDmitry Baryshkov dp = to_dp_bridge(bridge)->dp_display;
79c943b494SChandan Uddaraju
80c943b494SChandan Uddaraju /* pluggable case assumes EDID is read when HPD */
81c943b494SChandan Uddaraju if (dp->is_connected) {
82a52bfaf6SDmitry Baryshkov rc = dp_display_get_modes(dp);
83c943b494SChandan Uddaraju if (rc <= 0) {
84c943b494SChandan Uddaraju DRM_ERROR("failed to get DP sink modes, rc=%d\n", rc);
85c943b494SChandan Uddaraju return rc;
86c943b494SChandan Uddaraju }
87c943b494SChandan Uddaraju } else {
88202aceacSKuogee Hsieh drm_dbg_dp(connector->dev, "No sink connected\n");
89c943b494SChandan Uddaraju }
90c943b494SChandan Uddaraju return rc;
91c943b494SChandan Uddaraju }
92c943b494SChandan Uddaraju
938a3b4c17SKuogee Hsieh static const struct drm_bridge_funcs dp_bridge_ops = {
943a661247SKuogee Hsieh .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
953a661247SKuogee Hsieh .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
963a661247SKuogee Hsieh .atomic_reset = drm_atomic_helper_bridge_reset,
97cdfd0e62SVinod Polimera .atomic_enable = dp_bridge_atomic_enable,
98cdfd0e62SVinod Polimera .atomic_disable = dp_bridge_atomic_disable,
99cdfd0e62SVinod Polimera .atomic_post_disable = dp_bridge_atomic_post_disable,
1008a3b4c17SKuogee Hsieh .mode_set = dp_bridge_mode_set,
101a586191cSDmitry Baryshkov .mode_valid = dp_bridge_mode_valid,
102a586191cSDmitry Baryshkov .get_modes = dp_bridge_get_modes,
103a586191cSDmitry Baryshkov .detect = dp_bridge_detect,
1043a661247SKuogee Hsieh .atomic_check = dp_bridge_atomic_check,
105cd198cadSBjorn Andersson .hpd_enable = dp_bridge_hpd_enable,
106cd198cadSBjorn Andersson .hpd_disable = dp_bridge_hpd_disable,
107542b37efSBjorn Andersson .hpd_notify = dp_bridge_hpd_notify,
1088a3b4c17SKuogee Hsieh };
1098a3b4c17SKuogee Hsieh
edp_bridge_atomic_check(struct drm_bridge * drm_bridge,struct drm_bridge_state * bridge_state,struct drm_crtc_state * crtc_state,struct drm_connector_state * conn_state)110cd779808SVinod Polimera static int edp_bridge_atomic_check(struct drm_bridge *drm_bridge,
111cd779808SVinod Polimera struct drm_bridge_state *bridge_state,
112cd779808SVinod Polimera struct drm_crtc_state *crtc_state,
113cd779808SVinod Polimera struct drm_connector_state *conn_state)
114cd779808SVinod Polimera {
115cd779808SVinod Polimera struct msm_dp *dp = to_dp_bridge(drm_bridge)->dp_display;
116cd779808SVinod Polimera
117cd779808SVinod Polimera if (WARN_ON(!conn_state))
118cd779808SVinod Polimera return -ENODEV;
119cd779808SVinod Polimera
120*1844e680SVinod Polimera conn_state->self_refresh_aware = dp->psr_supported;
121*1844e680SVinod Polimera
122cd779808SVinod Polimera if (!conn_state->crtc || !crtc_state)
123cd779808SVinod Polimera return 0;
124cd779808SVinod Polimera
125cd779808SVinod Polimera if (crtc_state->self_refresh_active && !dp->psr_supported)
126cd779808SVinod Polimera return -EINVAL;
127cd779808SVinod Polimera
128cd779808SVinod Polimera return 0;
129cd779808SVinod Polimera }
130cd779808SVinod Polimera
edp_bridge_atomic_enable(struct drm_bridge * drm_bridge,struct drm_bridge_state * old_bridge_state)131cd779808SVinod Polimera static void edp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
132cd779808SVinod Polimera struct drm_bridge_state *old_bridge_state)
133cd779808SVinod Polimera {
134cd779808SVinod Polimera struct drm_atomic_state *atomic_state = old_bridge_state->base.state;
135cd779808SVinod Polimera struct drm_crtc *crtc;
136cd779808SVinod Polimera struct drm_crtc_state *old_crtc_state;
137cd779808SVinod Polimera struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
138cd779808SVinod Polimera struct msm_dp *dp = dp_bridge->dp_display;
139cd779808SVinod Polimera
140cd779808SVinod Polimera /*
141cd779808SVinod Polimera * Check the old state of the crtc to determine if the panel
142cd779808SVinod Polimera * was put into psr state previously by the edp_bridge_atomic_disable.
143cd779808SVinod Polimera * If the panel is in psr, just exit psr state and skip the full
144cd779808SVinod Polimera * bridge enable sequence.
145cd779808SVinod Polimera */
146cd779808SVinod Polimera crtc = drm_atomic_get_new_crtc_for_encoder(atomic_state,
147cd779808SVinod Polimera drm_bridge->encoder);
148cd779808SVinod Polimera if (!crtc)
149cd779808SVinod Polimera return;
150cd779808SVinod Polimera
151cd779808SVinod Polimera old_crtc_state = drm_atomic_get_old_crtc_state(atomic_state, crtc);
152cd779808SVinod Polimera
153cd779808SVinod Polimera if (old_crtc_state && old_crtc_state->self_refresh_active) {
154cd779808SVinod Polimera dp_display_set_psr(dp, false);
155cd779808SVinod Polimera return;
156cd779808SVinod Polimera }
157cd779808SVinod Polimera
158cd779808SVinod Polimera dp_bridge_atomic_enable(drm_bridge, old_bridge_state);
159cd779808SVinod Polimera }
160cd779808SVinod Polimera
edp_bridge_atomic_disable(struct drm_bridge * drm_bridge,struct drm_bridge_state * old_bridge_state)161cd779808SVinod Polimera static void edp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
162cd779808SVinod Polimera struct drm_bridge_state *old_bridge_state)
163cd779808SVinod Polimera {
164cd779808SVinod Polimera struct drm_atomic_state *atomic_state = old_bridge_state->base.state;
165cd779808SVinod Polimera struct drm_crtc *crtc;
166cd779808SVinod Polimera struct drm_crtc_state *new_crtc_state = NULL, *old_crtc_state = NULL;
167cd779808SVinod Polimera struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge);
168cd779808SVinod Polimera struct msm_dp *dp = dp_bridge->dp_display;
169cd779808SVinod Polimera
170cd779808SVinod Polimera crtc = drm_atomic_get_old_crtc_for_encoder(atomic_state,
171cd779808SVinod Polimera drm_bridge->encoder);
172cd779808SVinod Polimera if (!crtc)
173cd779808SVinod Polimera goto out;
174cd779808SVinod Polimera
175cd779808SVinod Polimera new_crtc_state = drm_atomic_get_new_crtc_state(atomic_state, crtc);
176cd779808SVinod Polimera if (!new_crtc_state)
177cd779808SVinod Polimera goto out;
178cd779808SVinod Polimera
179cd779808SVinod Polimera old_crtc_state = drm_atomic_get_old_crtc_state(atomic_state, crtc);
180cd779808SVinod Polimera if (!old_crtc_state)
181cd779808SVinod Polimera goto out;
182cd779808SVinod Polimera
183cd779808SVinod Polimera /*
184cd779808SVinod Polimera * Set self refresh mode if current crtc state is active.
185cd779808SVinod Polimera *
186cd779808SVinod Polimera * If old crtc state is active, then this is a display disable
187cd779808SVinod Polimera * call while the sink is in psr state. So, exit psr here.
188cd779808SVinod Polimera * The eDP controller will be disabled in the
189cd779808SVinod Polimera * edp_bridge_atomic_post_disable function.
190cd779808SVinod Polimera *
191cd779808SVinod Polimera * We observed sink is stuck in self refresh if psr exit is skipped
192cd779808SVinod Polimera * when display disable occurs while the sink is in psr state.
193cd779808SVinod Polimera */
194cd779808SVinod Polimera if (new_crtc_state->self_refresh_active) {
195cd779808SVinod Polimera dp_display_set_psr(dp, true);
196cd779808SVinod Polimera return;
197cd779808SVinod Polimera } else if (old_crtc_state->self_refresh_active) {
198cd779808SVinod Polimera dp_display_set_psr(dp, false);
199cd779808SVinod Polimera return;
200cd779808SVinod Polimera }
201cd779808SVinod Polimera
202cd779808SVinod Polimera out:
203cd779808SVinod Polimera dp_bridge_atomic_disable(drm_bridge, old_bridge_state);
204cd779808SVinod Polimera }
205cd779808SVinod Polimera
edp_bridge_atomic_post_disable(struct drm_bridge * drm_bridge,struct drm_bridge_state * old_bridge_state)206cd779808SVinod Polimera static void edp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
207cd779808SVinod Polimera struct drm_bridge_state *old_bridge_state)
208cd779808SVinod Polimera {
209cd779808SVinod Polimera struct drm_atomic_state *atomic_state = old_bridge_state->base.state;
210cd779808SVinod Polimera struct drm_crtc *crtc;
211cd779808SVinod Polimera struct drm_crtc_state *new_crtc_state = NULL;
212cd779808SVinod Polimera
213cd779808SVinod Polimera crtc = drm_atomic_get_old_crtc_for_encoder(atomic_state,
214cd779808SVinod Polimera drm_bridge->encoder);
215cd779808SVinod Polimera if (!crtc)
216cd779808SVinod Polimera return;
217cd779808SVinod Polimera
218cd779808SVinod Polimera new_crtc_state = drm_atomic_get_new_crtc_state(atomic_state, crtc);
219cd779808SVinod Polimera if (!new_crtc_state)
220cd779808SVinod Polimera return;
221cd779808SVinod Polimera
222cd779808SVinod Polimera /*
223cd779808SVinod Polimera * Self refresh mode is already set in edp_bridge_atomic_disable.
224cd779808SVinod Polimera */
225cd779808SVinod Polimera if (new_crtc_state->self_refresh_active)
226cd779808SVinod Polimera return;
227cd779808SVinod Polimera
228cd779808SVinod Polimera dp_bridge_atomic_post_disable(drm_bridge, old_bridge_state);
229cd779808SVinod Polimera }
230cd779808SVinod Polimera
23105d00135SVinod Polimera /**
23205d00135SVinod Polimera * edp_bridge_mode_valid - callback to determine if specified mode is valid
23305d00135SVinod Polimera * @bridge: Pointer to drm bridge structure
23405d00135SVinod Polimera * @info: display info
23505d00135SVinod Polimera * @mode: Pointer to drm mode structure
23605d00135SVinod Polimera * Returns: Validity status for specified mode
23705d00135SVinod Polimera */
edp_bridge_mode_valid(struct drm_bridge * bridge,const struct drm_display_info * info,const struct drm_display_mode * mode)23805d00135SVinod Polimera static enum drm_mode_status edp_bridge_mode_valid(struct drm_bridge *bridge,
23905d00135SVinod Polimera const struct drm_display_info *info,
24005d00135SVinod Polimera const struct drm_display_mode *mode)
24105d00135SVinod Polimera {
24205d00135SVinod Polimera struct msm_dp *dp;
24305d00135SVinod Polimera int mode_pclk_khz = mode->clock;
24405d00135SVinod Polimera
24505d00135SVinod Polimera dp = to_dp_bridge(bridge)->dp_display;
24605d00135SVinod Polimera
24705d00135SVinod Polimera if (!dp || !mode_pclk_khz || !dp->connector) {
24805d00135SVinod Polimera DRM_ERROR("invalid params\n");
24905d00135SVinod Polimera return -EINVAL;
25005d00135SVinod Polimera }
25105d00135SVinod Polimera
25205d00135SVinod Polimera if (mode->clock > DP_MAX_PIXEL_CLK_KHZ)
25305d00135SVinod Polimera return MODE_CLOCK_HIGH;
25405d00135SVinod Polimera
25505d00135SVinod Polimera /*
25605d00135SVinod Polimera * The eDP controller currently does not have a reliable way of
25705d00135SVinod Polimera * enabling panel power to read sink capabilities. So, we rely
25805d00135SVinod Polimera * on the panel driver to populate only supported modes for now.
25905d00135SVinod Polimera */
26005d00135SVinod Polimera return MODE_OK;
26105d00135SVinod Polimera }
26205d00135SVinod Polimera
263cd779808SVinod Polimera static const struct drm_bridge_funcs edp_bridge_ops = {
264cd779808SVinod Polimera .atomic_enable = edp_bridge_atomic_enable,
265cd779808SVinod Polimera .atomic_disable = edp_bridge_atomic_disable,
266cd779808SVinod Polimera .atomic_post_disable = edp_bridge_atomic_post_disable,
267cd779808SVinod Polimera .mode_set = dp_bridge_mode_set,
26805d00135SVinod Polimera .mode_valid = edp_bridge_mode_valid,
269cd779808SVinod Polimera .atomic_reset = drm_atomic_helper_bridge_reset,
270cd779808SVinod Polimera .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
271cd779808SVinod Polimera .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
272cd779808SVinod Polimera .atomic_check = edp_bridge_atomic_check,
273cd779808SVinod Polimera };
274cd779808SVinod Polimera
dp_bridge_init(struct msm_dp * dp_display,struct drm_device * dev,struct drm_encoder * encoder)27513ea4799SDmitry Baryshkov struct drm_bridge *dp_bridge_init(struct msm_dp *dp_display, struct drm_device *dev,
2768a3b4c17SKuogee Hsieh struct drm_encoder *encoder)
2778a3b4c17SKuogee Hsieh {
2788a3b4c17SKuogee Hsieh int rc;
2798a3b4c17SKuogee Hsieh struct msm_dp_bridge *dp_bridge;
2808a3b4c17SKuogee Hsieh struct drm_bridge *bridge;
2818a3b4c17SKuogee Hsieh
2828a3b4c17SKuogee Hsieh dp_bridge = devm_kzalloc(dev->dev, sizeof(*dp_bridge), GFP_KERNEL);
2838a3b4c17SKuogee Hsieh if (!dp_bridge)
2848a3b4c17SKuogee Hsieh return ERR_PTR(-ENOMEM);
2858a3b4c17SKuogee Hsieh
2868a3b4c17SKuogee Hsieh dp_bridge->dp_display = dp_display;
2878a3b4c17SKuogee Hsieh
2888a3b4c17SKuogee Hsieh bridge = &dp_bridge->bridge;
289cd779808SVinod Polimera bridge->funcs = dp_display->is_edp ? &edp_bridge_ops : &dp_bridge_ops;
290a586191cSDmitry Baryshkov bridge->type = dp_display->connector_type;
291a586191cSDmitry Baryshkov
292c3bf8e21SSankeerth Billakanti /*
293c3bf8e21SSankeerth Billakanti * Many ops only make sense for DP. Why?
294c3bf8e21SSankeerth Billakanti * - Detect/HPD are used by DRM to know if a display is _physically_
295c3bf8e21SSankeerth Billakanti * there, not whether the display is powered on / finished initting.
296c3bf8e21SSankeerth Billakanti * On eDP we assume the display is always there because you can't
297c3bf8e21SSankeerth Billakanti * know until power is applied. If we don't implement the ops DRM will
298c3bf8e21SSankeerth Billakanti * assume our display is always there.
299c3bf8e21SSankeerth Billakanti * - Currently eDP mode reading is driven by the panel driver. This
300c3bf8e21SSankeerth Billakanti * allows the panel driver to properly power itself on to read the
301c3bf8e21SSankeerth Billakanti * modes.
302c3bf8e21SSankeerth Billakanti */
303c3bf8e21SSankeerth Billakanti if (!dp_display->is_edp) {
304a586191cSDmitry Baryshkov bridge->ops =
305a586191cSDmitry Baryshkov DRM_BRIDGE_OP_DETECT |
306a586191cSDmitry Baryshkov DRM_BRIDGE_OP_HPD |
307a586191cSDmitry Baryshkov DRM_BRIDGE_OP_MODES;
308c3bf8e21SSankeerth Billakanti }
3098a3b4c17SKuogee Hsieh
310d28ea556SDmitry Baryshkov drm_bridge_add(bridge);
311d28ea556SDmitry Baryshkov
3128a3b4c17SKuogee Hsieh rc = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
3138a3b4c17SKuogee Hsieh if (rc) {
3148a3b4c17SKuogee Hsieh DRM_ERROR("failed to attach bridge, rc=%d\n", rc);
315d28ea556SDmitry Baryshkov drm_bridge_remove(bridge);
316d28ea556SDmitry Baryshkov
3178a3b4c17SKuogee Hsieh return ERR_PTR(rc);
3188a3b4c17SKuogee Hsieh }
3198a3b4c17SKuogee Hsieh
3209aa92468SDmitry Baryshkov if (dp_display->next_bridge) {
321efc76170SStephen Boyd rc = drm_bridge_attach(encoder,
3229aa92468SDmitry Baryshkov dp_display->next_bridge, bridge,
3234d793a02SDmitry Baryshkov DRM_BRIDGE_ATTACH_NO_CONNECTOR);
3244d793a02SDmitry Baryshkov if (rc < 0) {
3254d793a02SDmitry Baryshkov DRM_ERROR("failed to attach panel bridge: %d\n", rc);
3264d793a02SDmitry Baryshkov drm_bridge_remove(bridge);
3274d793a02SDmitry Baryshkov return ERR_PTR(rc);
3284d793a02SDmitry Baryshkov }
3294d793a02SDmitry Baryshkov }
3304d793a02SDmitry Baryshkov
3318a3b4c17SKuogee Hsieh return bridge;
3328a3b4c17SKuogee Hsieh }
333a586191cSDmitry Baryshkov
334a586191cSDmitry Baryshkov /* connector initialization */
dp_drm_connector_init(struct msm_dp * dp_display,struct drm_encoder * encoder)335efc76170SStephen Boyd struct drm_connector *dp_drm_connector_init(struct msm_dp *dp_display, struct drm_encoder *encoder)
336a586191cSDmitry Baryshkov {
337a586191cSDmitry Baryshkov struct drm_connector *connector = NULL;
338a586191cSDmitry Baryshkov
339efc76170SStephen Boyd connector = drm_bridge_connector_init(dp_display->drm_dev, encoder);
340a586191cSDmitry Baryshkov if (IS_ERR(connector))
341a586191cSDmitry Baryshkov return connector;
342a586191cSDmitry Baryshkov
343efc76170SStephen Boyd drm_connector_attach_encoder(connector, encoder);
344a586191cSDmitry Baryshkov
345a586191cSDmitry Baryshkov return connector;
346a586191cSDmitry Baryshkov }
347