1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * Copyright © 2023 Intel Corporation |
4 | */ |
5 | |
6 | #include <drm/drm_atomic.h> |
7 | #include <drm/drm_atomic_helper.h> |
8 | #include <drm/drm_atomic_uapi.h> |
9 | |
10 | #include "i915_drv.h" |
11 | #include "intel_atomic.h" |
12 | #include "intel_crtc.h" |
13 | #include "intel_display_types.h" |
14 | #include "intel_load_detect.h" |
15 | |
16 | /* VESA 640x480x72Hz mode to set on the pipe */ |
17 | static const struct drm_display_mode load_detect_mode = { |
18 | DRM_MODE("640x480" , DRM_MODE_TYPE_DEFAULT, 31500, 640, 664, |
19 | 704, 832, 0, 480, 489, 491, 520, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), |
20 | }; |
21 | |
22 | static int intel_modeset_disable_planes(struct drm_atomic_state *state, |
23 | struct drm_crtc *crtc) |
24 | { |
25 | struct drm_plane *plane; |
26 | struct drm_plane_state *plane_state; |
27 | int ret, i; |
28 | |
29 | ret = drm_atomic_add_affected_planes(state, crtc); |
30 | if (ret) |
31 | return ret; |
32 | |
33 | for_each_new_plane_in_state(state, plane, plane_state, i) { |
34 | if (plane_state->crtc != crtc) |
35 | continue; |
36 | |
37 | ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); |
38 | if (ret) |
39 | return ret; |
40 | |
41 | drm_atomic_set_fb_for_plane(plane_state, NULL); |
42 | } |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | struct drm_atomic_state * |
48 | intel_load_detect_get_pipe(struct drm_connector *connector, |
49 | struct drm_modeset_acquire_ctx *ctx) |
50 | { |
51 | struct intel_encoder *encoder = |
52 | intel_attached_encoder(to_intel_connector(connector)); |
53 | struct intel_crtc *possible_crtc; |
54 | struct intel_crtc *crtc = NULL; |
55 | struct drm_device *dev = encoder->base.dev; |
56 | struct drm_i915_private *dev_priv = to_i915(dev); |
57 | struct drm_mode_config *config = &dev->mode_config; |
58 | struct drm_atomic_state *state = NULL, *restore_state = NULL; |
59 | struct drm_connector_state *connector_state; |
60 | struct intel_crtc_state *crtc_state; |
61 | int ret; |
62 | |
63 | drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s], [ENCODER:%d:%s]\n" , |
64 | connector->base.id, connector->name, |
65 | encoder->base.base.id, encoder->base.name); |
66 | |
67 | drm_WARN_ON(dev, !drm_modeset_is_locked(&config->connection_mutex)); |
68 | |
69 | /* |
70 | * Algorithm gets a little messy: |
71 | * |
72 | * - if the connector already has an assigned crtc, use it (but make |
73 | * sure it's on first) |
74 | * |
75 | * - try to find the first unused crtc that can drive this connector, |
76 | * and use that if we find one |
77 | */ |
78 | |
79 | /* See if we already have a CRTC for this connector */ |
80 | if (connector->state->crtc) { |
81 | crtc = to_intel_crtc(connector->state->crtc); |
82 | |
83 | ret = drm_modeset_lock(lock: &crtc->base.mutex, ctx); |
84 | if (ret) |
85 | goto fail; |
86 | |
87 | /* Make sure the crtc and connector are running */ |
88 | goto found; |
89 | } |
90 | |
91 | /* Find an unused one (if possible) */ |
92 | for_each_intel_crtc(dev, possible_crtc) { |
93 | if (!(encoder->base.possible_crtcs & |
94 | drm_crtc_mask(crtc: &possible_crtc->base))) |
95 | continue; |
96 | |
97 | ret = drm_modeset_lock(lock: &possible_crtc->base.mutex, ctx); |
98 | if (ret) |
99 | goto fail; |
100 | |
101 | if (possible_crtc->base.state->enable) { |
102 | drm_modeset_unlock(lock: &possible_crtc->base.mutex); |
103 | continue; |
104 | } |
105 | |
106 | crtc = possible_crtc; |
107 | break; |
108 | } |
109 | |
110 | /* |
111 | * If we didn't find an unused CRTC, don't use any. |
112 | */ |
113 | if (!crtc) { |
114 | drm_dbg_kms(&dev_priv->drm, |
115 | "no pipe available for load-detect\n" ); |
116 | ret = -ENODEV; |
117 | goto fail; |
118 | } |
119 | |
120 | found: |
121 | state = drm_atomic_state_alloc(dev); |
122 | restore_state = drm_atomic_state_alloc(dev); |
123 | if (!state || !restore_state) { |
124 | ret = -ENOMEM; |
125 | goto fail; |
126 | } |
127 | |
128 | state->acquire_ctx = ctx; |
129 | to_intel_atomic_state(state)->internal = true; |
130 | |
131 | restore_state->acquire_ctx = ctx; |
132 | to_intel_atomic_state(restore_state)->internal = true; |
133 | |
134 | connector_state = drm_atomic_get_connector_state(state, connector); |
135 | if (IS_ERR(ptr: connector_state)) { |
136 | ret = PTR_ERR(ptr: connector_state); |
137 | goto fail; |
138 | } |
139 | |
140 | ret = drm_atomic_set_crtc_for_connector(conn_state: connector_state, crtc: &crtc->base); |
141 | if (ret) |
142 | goto fail; |
143 | |
144 | crtc_state = intel_atomic_get_crtc_state(state, crtc); |
145 | if (IS_ERR(ptr: crtc_state)) { |
146 | ret = PTR_ERR(ptr: crtc_state); |
147 | goto fail; |
148 | } |
149 | |
150 | crtc_state->uapi.active = true; |
151 | |
152 | ret = drm_atomic_set_mode_for_crtc(state: &crtc_state->uapi, |
153 | mode: &load_detect_mode); |
154 | if (ret) |
155 | goto fail; |
156 | |
157 | ret = intel_modeset_disable_planes(state, crtc: &crtc->base); |
158 | if (ret) |
159 | goto fail; |
160 | |
161 | ret = PTR_ERR_OR_ZERO(ptr: drm_atomic_get_connector_state(state: restore_state, connector)); |
162 | if (!ret) |
163 | ret = PTR_ERR_OR_ZERO(ptr: drm_atomic_get_crtc_state(state: restore_state, crtc: &crtc->base)); |
164 | if (!ret) |
165 | ret = drm_atomic_add_affected_planes(state: restore_state, crtc: &crtc->base); |
166 | if (ret) { |
167 | drm_dbg_kms(&dev_priv->drm, |
168 | "Failed to create a copy of old state to restore: %i\n" , |
169 | ret); |
170 | goto fail; |
171 | } |
172 | |
173 | ret = drm_atomic_commit(state); |
174 | if (ret) { |
175 | drm_dbg_kms(&dev_priv->drm, |
176 | "failed to set mode on load-detect pipe\n" ); |
177 | goto fail; |
178 | } |
179 | |
180 | drm_atomic_state_put(state); |
181 | |
182 | /* let the connector get through one full cycle before testing */ |
183 | intel_crtc_wait_for_next_vblank(crtc); |
184 | |
185 | return restore_state; |
186 | |
187 | fail: |
188 | if (state) { |
189 | drm_atomic_state_put(state); |
190 | state = NULL; |
191 | } |
192 | if (restore_state) { |
193 | drm_atomic_state_put(state: restore_state); |
194 | restore_state = NULL; |
195 | } |
196 | |
197 | if (ret == -EDEADLK) |
198 | return ERR_PTR(error: ret); |
199 | |
200 | return NULL; |
201 | } |
202 | |
203 | void intel_load_detect_release_pipe(struct drm_connector *connector, |
204 | struct drm_atomic_state *state, |
205 | struct drm_modeset_acquire_ctx *ctx) |
206 | { |
207 | struct intel_encoder *intel_encoder = |
208 | intel_attached_encoder(to_intel_connector(connector)); |
209 | struct drm_i915_private *i915 = to_i915(dev: intel_encoder->base.dev); |
210 | struct drm_encoder *encoder = &intel_encoder->base; |
211 | int ret; |
212 | |
213 | drm_dbg_kms(&i915->drm, "[CONNECTOR:%d:%s], [ENCODER:%d:%s]\n" , |
214 | connector->base.id, connector->name, |
215 | encoder->base.id, encoder->name); |
216 | |
217 | if (IS_ERR_OR_NULL(ptr: state)) |
218 | return; |
219 | |
220 | ret = drm_atomic_helper_commit_duplicated_state(state, ctx); |
221 | if (ret) |
222 | drm_dbg_kms(&i915->drm, |
223 | "Couldn't release load detect pipe: %i\n" , ret); |
224 | drm_atomic_state_put(state); |
225 | } |
226 | |