| 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 |  | 
|---|