| 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 | #include <drm/drm_print.h> |
| 10 | |
| 11 | #include "intel_atomic.h" |
| 12 | #include "intel_crtc.h" |
| 13 | #include "intel_display_core.h" |
| 14 | #include "intel_display_types.h" |
| 15 | #include "intel_load_detect.h" |
| 16 | |
| 17 | /* VESA 640x480x72Hz mode to set on the pipe */ |
| 18 | static const struct drm_display_mode load_detect_mode = { |
| 19 | DRM_MODE("640x480" , DRM_MODE_TYPE_DEFAULT, 31500, 640, 664, |
| 20 | 704, 832, 0, 480, 489, 491, 520, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), |
| 21 | }; |
| 22 | |
| 23 | static int intel_modeset_disable_planes(struct drm_atomic_state *state, |
| 24 | struct drm_crtc *crtc) |
| 25 | { |
| 26 | struct drm_plane *plane; |
| 27 | struct drm_plane_state *plane_state; |
| 28 | int ret, i; |
| 29 | |
| 30 | ret = drm_atomic_add_affected_planes(state, crtc); |
| 31 | if (ret) |
| 32 | return ret; |
| 33 | |
| 34 | for_each_new_plane_in_state(state, plane, plane_state, i) { |
| 35 | if (plane_state->crtc != crtc) |
| 36 | continue; |
| 37 | |
| 38 | ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); |
| 39 | if (ret) |
| 40 | return ret; |
| 41 | |
| 42 | drm_atomic_set_fb_for_plane(plane_state, NULL); |
| 43 | } |
| 44 | |
| 45 | return 0; |
| 46 | } |
| 47 | |
| 48 | struct drm_atomic_state * |
| 49 | intel_load_detect_get_pipe(struct drm_connector *connector, |
| 50 | struct drm_modeset_acquire_ctx *ctx) |
| 51 | { |
| 52 | struct intel_display *display = to_intel_display(connector->dev); |
| 53 | struct intel_encoder *encoder = |
| 54 | intel_attached_encoder(to_intel_connector(connector)); |
| 55 | struct intel_crtc *possible_crtc; |
| 56 | struct intel_crtc *crtc = NULL; |
| 57 | struct drm_mode_config *config = &display->drm->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(display->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(display->drm, !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(display->drm, 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(display->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: display->drm); |
| 122 | restore_state = drm_atomic_state_alloc(dev: display->drm); |
| 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(display->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(display->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_display *display = to_intel_display(connector->dev); |
| 208 | struct intel_encoder *intel_encoder = |
| 209 | intel_attached_encoder(to_intel_connector(connector)); |
| 210 | struct drm_encoder *encoder = &intel_encoder->base; |
| 211 | int ret; |
| 212 | |
| 213 | drm_dbg_kms(display->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(display->drm, |
| 223 | "Couldn't release load detect pipe: %i\n" , ret); |
| 224 | drm_atomic_state_put(state); |
| 225 | } |
| 226 | |