Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
4 */
5
6#include <drm/drm_atomic_helper.h>
7#include <drm/drm_atomic.h>
8#include <drm/drm_bridge.h>
9#include <drm/drm_crtc.h>
10
11#include "msm_drv.h"
12#include "msm_kms.h"
13#include "dp_drm.h"
14
15
16struct msm_dp_bridge {
17 struct drm_bridge bridge;
18 struct msm_dp *dp_display;
19};
20
21#define to_dp_display(x) container_of((x), struct msm_dp_bridge, bridge)
22
23struct dp_connector {
24 struct drm_connector base;
25 struct msm_dp *dp_display;
26};
27#define to_dp_connector(x) container_of(x, struct dp_connector, base)
28
29/**
30 * dp_connector_detect - callback to determine if connector is connected
31 * @conn: Pointer to drm connector structure
32 * @force: Force detect setting from drm framework
33 * Returns: Connector 'is connected' status
34 */
35static enum drm_connector_status dp_connector_detect(struct drm_connector *conn,
36 bool force)
37{
38 struct msm_dp *dp;
39
40 dp = to_dp_connector(conn)->dp_display;
41
42 DRM_DEBUG_DP("is_connected = %s\n",
43 (dp->is_connected) ? "true" : "false");
44
45 return (dp->is_connected) ? connector_status_connected :
46 connector_status_disconnected;
47}
48
49/**
50 * dp_connector_get_modes - callback to add drm modes via drm_mode_probed_add()
51 * @connector: Pointer to drm connector structure
52 * Returns: Number of modes added
53 */
54static int dp_connector_get_modes(struct drm_connector *connector)
55{
56 int rc = 0;
57 struct msm_dp *dp;
58 struct dp_display_mode *dp_mode = NULL;
59 struct drm_display_mode *m, drm_mode;
60
61 if (!connector)
62 return 0;
63
64 dp = to_dp_connector(connector)->dp_display;
65
66 dp_mode = kzalloc(sizeof(*dp_mode), GFP_KERNEL);
67 if (!dp_mode)
68 return 0;
69
70 /* pluggable case assumes EDID is read when HPD */
71 if (dp->is_connected) {
72 /*
73 *The get_modes() function might return one mode that is stored
74 * in dp_mode when compliance test is in progress. If not, the
75 * return value is equal to the total number of modes supported
76 * by the sink
77 */
78 rc = dp_display_get_modes(dp, dp_mode);
79 if (rc <= 0) {
80 DRM_ERROR("failed to get DP sink modes, rc=%d\n", rc);
81 kfree(dp_mode);
82 return rc;
83 }
84 if (dp_mode->drm_mode.clock) { /* valid DP mode */
85 memset(&drm_mode, 0x0, sizeof(drm_mode));
86 drm_mode_copy(&drm_mode, &dp_mode->drm_mode);
87 m = drm_mode_duplicate(connector->dev, &drm_mode);
88 if (!m) {
89 DRM_ERROR("failed to add mode %ux%u\n",
90 drm_mode.hdisplay,
91 drm_mode.vdisplay);
92 kfree(dp_mode);
93 return 0;
94 }
95 drm_mode_probed_add(connector, m);
96 }
97 } else {
98 DRM_DEBUG_DP("No sink connected\n");
99 }
100 kfree(dp_mode);
101 return rc;
102}
103
104/**
105 * dp_connector_mode_valid - callback to determine if specified mode is valid
106 * @connector: Pointer to drm connector structure
107 * @mode: Pointer to drm mode structure
108 * Returns: Validity status for specified mode
109 */
110static enum drm_mode_status dp_connector_mode_valid(
111 struct drm_connector *connector,
112 struct drm_display_mode *mode)
113{
114 struct msm_dp *dp_disp;
115
116 dp_disp = to_dp_connector(connector)->dp_display;
117
118 if ((dp_disp->max_pclk_khz <= 0) ||
119 (dp_disp->max_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) ||
120 (mode->clock > dp_disp->max_pclk_khz))
121 return MODE_BAD;
122
123 return dp_display_validate_mode(dp_disp, mode->clock);
124}
125
126static const struct drm_connector_funcs dp_connector_funcs = {
127 .detect = dp_connector_detect,
128 .fill_modes = drm_helper_probe_single_connector_modes,
129 .destroy = drm_connector_cleanup,
130 .reset = drm_atomic_helper_connector_reset,
131 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
132 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
133};
134
135static const struct drm_connector_helper_funcs dp_connector_helper_funcs = {
136 .get_modes = dp_connector_get_modes,
137 .mode_valid = dp_connector_mode_valid,
138};
139
140/* connector initialization */
141struct drm_connector *dp_drm_connector_init(struct msm_dp *dp_display)
142{
143 struct drm_connector *connector = NULL;
144 struct dp_connector *dp_connector;
145 int ret;
146
147 dp_connector = devm_kzalloc(dp_display->drm_dev->dev,
148 sizeof(*dp_connector),
149 GFP_KERNEL);
150 if (!dp_connector)
151 return ERR_PTR(-ENOMEM);
152
153 dp_connector->dp_display = dp_display;
154
155 connector = &dp_connector->base;
156
157 ret = drm_connector_init(dp_display->drm_dev, connector,
158 &dp_connector_funcs,
159 dp_display->connector_type);
160 if (ret)
161 return ERR_PTR(ret);
162
163 drm_connector_helper_add(connector, &dp_connector_helper_funcs);
164
165 /*
166 * Enable HPD to let hpd event is handled when cable is connected.
167 */
168 connector->polled = DRM_CONNECTOR_POLL_HPD;
169
170 drm_connector_attach_encoder(connector, dp_display->encoder);
171
172 return connector;
173}
174
175static void dp_bridge_mode_set(struct drm_bridge *drm_bridge,
176 const struct drm_display_mode *mode,
177 const struct drm_display_mode *adjusted_mode)
178{
179 struct msm_dp_bridge *dp_bridge = to_dp_display(drm_bridge);
180 struct msm_dp *dp_display = dp_bridge->dp_display;
181
182 msm_dp_display_mode_set(dp_display, drm_bridge->encoder, mode, adjusted_mode);
183}
184
185static void dp_bridge_enable(struct drm_bridge *drm_bridge)
186{
187 struct msm_dp_bridge *dp_bridge = to_dp_display(drm_bridge);
188 struct msm_dp *dp_display = dp_bridge->dp_display;
189
190 msm_dp_display_enable(dp_display, drm_bridge->encoder);
191}
192
193static void dp_bridge_disable(struct drm_bridge *drm_bridge)
194{
195 struct msm_dp_bridge *dp_bridge = to_dp_display(drm_bridge);
196 struct msm_dp *dp_display = dp_bridge->dp_display;
197
198 msm_dp_display_pre_disable(dp_display, drm_bridge->encoder);
199}
200
201static void dp_bridge_post_disable(struct drm_bridge *drm_bridge)
202{
203 struct msm_dp_bridge *dp_bridge = to_dp_display(drm_bridge);
204 struct msm_dp *dp_display = dp_bridge->dp_display;
205
206 msm_dp_display_disable(dp_display, drm_bridge->encoder);
207}
208
209static const struct drm_bridge_funcs dp_bridge_ops = {
210 .enable = dp_bridge_enable,
211 .disable = dp_bridge_disable,
212 .post_disable = dp_bridge_post_disable,
213 .mode_set = dp_bridge_mode_set,
214};
215
216struct drm_bridge *msm_dp_bridge_init(struct msm_dp *dp_display, struct drm_device *dev,
217 struct drm_encoder *encoder)
218{
219 int rc;
220 struct msm_dp_bridge *dp_bridge;
221 struct drm_bridge *bridge;
222
223 dp_bridge = devm_kzalloc(dev->dev, sizeof(*dp_bridge), GFP_KERNEL);
224 if (!dp_bridge)
225 return ERR_PTR(-ENOMEM);
226
227 dp_bridge->dp_display = dp_display;
228
229 bridge = &dp_bridge->bridge;
230 bridge->funcs = &dp_bridge_ops;
231 bridge->encoder = encoder;
232
233 rc = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
234 if (rc) {
235 DRM_ERROR("failed to attach bridge, rc=%d\n", rc);
236 return ERR_PTR(rc);
237 }
238
239 if (dp_display->next_bridge) {
240 rc = drm_bridge_attach(dp_display->encoder,
241 dp_display->next_bridge, bridge,
242 DRM_BRIDGE_ATTACH_NO_CONNECTOR);
243 if (rc < 0) {
244 DRM_ERROR("failed to attach panel bridge: %d\n", rc);
245 drm_bridge_remove(bridge);
246 return ERR_PTR(rc);
247 }
248 }
249
250 return bridge;
251}