| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | // Copyright (c) 2021, ASPEED Technology Inc. |
| 3 | // Authors: KuoHsiang Chou <kuohsiang_chou@aspeedtech.com> |
| 4 | |
| 5 | #include <linux/firmware.h> |
| 6 | #include <linux/delay.h> |
| 7 | |
| 8 | #include <drm/drm_atomic.h> |
| 9 | #include <drm/drm_atomic_state_helper.h> |
| 10 | #include <drm/drm_edid.h> |
| 11 | #include <drm/drm_modeset_helper_vtables.h> |
| 12 | #include <drm/drm_print.h> |
| 13 | #include <drm/drm_probe_helper.h> |
| 14 | |
| 15 | #include "ast_drv.h" |
| 16 | #include "ast_vbios.h" |
| 17 | |
| 18 | struct ast_astdp_mode_index_table_entry { |
| 19 | unsigned int hdisplay; |
| 20 | unsigned int vdisplay; |
| 21 | unsigned int mode_index; |
| 22 | }; |
| 23 | |
| 24 | /* FIXME: Do refresh rate and flags actually matter? */ |
| 25 | static const struct ast_astdp_mode_index_table_entry ast_astdp_mode_index_table[] = { |
| 26 | { 320, 240, ASTDP_320x240_60 }, |
| 27 | { 400, 300, ASTDP_400x300_60 }, |
| 28 | { 512, 384, ASTDP_512x384_60 }, |
| 29 | { 640, 480, ASTDP_640x480_60 }, |
| 30 | { 800, 600, ASTDP_800x600_56 }, |
| 31 | { 1024, 768, ASTDP_1024x768_60 }, |
| 32 | { 1152, 864, ASTDP_1152x864_75 }, |
| 33 | { 1280, 800, ASTDP_1280x800_60_RB }, |
| 34 | { 1280, 1024, ASTDP_1280x1024_60 }, |
| 35 | { 1360, 768, ASTDP_1366x768_60 }, // same as 1366x786 |
| 36 | { 1366, 768, ASTDP_1366x768_60 }, |
| 37 | { 1440, 900, ASTDP_1440x900_60_RB }, |
| 38 | { 1600, 900, ASTDP_1600x900_60_RB }, |
| 39 | { 1600, 1200, ASTDP_1600x1200_60 }, |
| 40 | { 1680, 1050, ASTDP_1680x1050_60_RB }, |
| 41 | { 1920, 1080, ASTDP_1920x1080_60 }, |
| 42 | { 1920, 1200, ASTDP_1920x1200_60 }, |
| 43 | { 0 } |
| 44 | }; |
| 45 | |
| 46 | struct ast_astdp_connector_state { |
| 47 | struct drm_connector_state base; |
| 48 | |
| 49 | int mode_index; |
| 50 | }; |
| 51 | |
| 52 | static struct ast_astdp_connector_state * |
| 53 | to_ast_astdp_connector_state(const struct drm_connector_state *state) |
| 54 | { |
| 55 | return container_of(state, struct ast_astdp_connector_state, base); |
| 56 | } |
| 57 | |
| 58 | static int ast_astdp_get_mode_index(unsigned int hdisplay, unsigned int vdisplay) |
| 59 | { |
| 60 | const struct ast_astdp_mode_index_table_entry *entry = ast_astdp_mode_index_table; |
| 61 | |
| 62 | while (entry->hdisplay && entry->vdisplay) { |
| 63 | if (entry->hdisplay == hdisplay && entry->vdisplay == vdisplay) |
| 64 | return entry->mode_index; |
| 65 | ++entry; |
| 66 | } |
| 67 | |
| 68 | return -EINVAL; |
| 69 | } |
| 70 | |
| 71 | static bool ast_astdp_is_connected(struct ast_device *ast) |
| 72 | { |
| 73 | if (!ast_get_index_reg_mask(ast, AST_IO_VGACRI, index: 0xDF, AST_IO_VGACRDF_HPD)) |
| 74 | return false; |
| 75 | /* |
| 76 | * HPD might be set even if no monitor is connected, so also check that |
| 77 | * the link training was successful. |
| 78 | */ |
| 79 | if (!ast_get_index_reg_mask(ast, AST_IO_VGACRI, index: 0xDC, AST_IO_VGACRDC_LINK_SUCCESS)) |
| 80 | return false; |
| 81 | return true; |
| 82 | } |
| 83 | |
| 84 | static int ast_astdp_read_edid_block(void *data, u8 *buf, unsigned int block, size_t len) |
| 85 | { |
| 86 | struct ast_device *ast = data; |
| 87 | size_t rdlen = round_up(len, 4); |
| 88 | int ret = 0; |
| 89 | unsigned int i; |
| 90 | |
| 91 | if (block > 0) |
| 92 | return -EIO; /* extension headers not supported */ |
| 93 | |
| 94 | /* |
| 95 | * Protect access to I/O registers from concurrent modesetting |
| 96 | * by acquiring the I/O-register lock. |
| 97 | */ |
| 98 | mutex_lock(&ast->modeset_lock); |
| 99 | |
| 100 | /* Start reading EDID data */ |
| 101 | ast_set_index_reg_mask(ast, AST_IO_VGACRI, index: 0xe5, preserve_mask: (u8)~AST_IO_VGACRE5_EDID_READ_DONE, val: 0x00); |
| 102 | |
| 103 | for (i = 0; i < rdlen; i += 4) { |
| 104 | unsigned int offset; |
| 105 | unsigned int j; |
| 106 | u8 ediddata[4]; |
| 107 | u8 vgacre4; |
| 108 | |
| 109 | offset = (i + block * EDID_LENGTH) / 4; |
| 110 | if (offset >= 64) { |
| 111 | ret = -EIO; |
| 112 | goto out; |
| 113 | } |
| 114 | vgacre4 = offset; |
| 115 | |
| 116 | /* |
| 117 | * CRE4[7:0]: Read-Pointer for EDID (Unit: 4bytes); valid range: 0~64 |
| 118 | */ |
| 119 | ast_set_index_reg(ast, AST_IO_VGACRI, index: 0xe4, val: vgacre4); |
| 120 | |
| 121 | /* |
| 122 | * CRD7[b0]: valid flag for EDID |
| 123 | * CRD6[b0]: mirror read pointer for EDID |
| 124 | */ |
| 125 | for (j = 0; j < 200; ++j) { |
| 126 | u8 vgacrd7, vgacrd6; |
| 127 | |
| 128 | /* |
| 129 | * Delay are getting longer with each retry. |
| 130 | * |
| 131 | * 1. No delay on first try |
| 132 | * 2. The Delays are often 2 loops when users request "Display Settings" |
| 133 | * of right-click of mouse. |
| 134 | * 3. The Delays are often longer a lot when system resume from S3/S4. |
| 135 | */ |
| 136 | if (j) |
| 137 | msleep(msecs: j + 1); |
| 138 | |
| 139 | /* Wait for EDID offset to show up in mirror register */ |
| 140 | vgacrd7 = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xd7); |
| 141 | if (vgacrd7 & AST_IO_VGACRD7_EDID_VALID_FLAG) { |
| 142 | vgacrd6 = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xd6); |
| 143 | if (vgacrd6 == offset) |
| 144 | break; |
| 145 | } |
| 146 | } |
| 147 | if (j == 200) { |
| 148 | ret = -EBUSY; |
| 149 | goto out; |
| 150 | } |
| 151 | |
| 152 | ediddata[0] = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xd8); |
| 153 | ediddata[1] = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xd9); |
| 154 | ediddata[2] = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xda); |
| 155 | ediddata[3] = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xdb); |
| 156 | |
| 157 | if (i == 31) { |
| 158 | /* |
| 159 | * For 128-bytes EDID_1.3, |
| 160 | * 1. Add the value of Bytes-126 to Bytes-127. |
| 161 | * The Bytes-127 is Checksum. Sum of all 128bytes should |
| 162 | * equal 0 (mod 256). |
| 163 | * 2. Modify Bytes-126 to be 0. |
| 164 | * The Bytes-126 indicates the Number of extensions to |
| 165 | * follow. 0 represents noextensions. |
| 166 | */ |
| 167 | ediddata[3] = ediddata[3] + ediddata[2]; |
| 168 | ediddata[2] = 0; |
| 169 | } |
| 170 | |
| 171 | memcpy(buf, ediddata, min((len - i), 4)); |
| 172 | buf += 4; |
| 173 | } |
| 174 | |
| 175 | out: |
| 176 | /* Signal end of reading */ |
| 177 | ast_set_index_reg_mask(ast, AST_IO_VGACRI, index: 0xe5, preserve_mask: (u8)~AST_IO_VGACRE5_EDID_READ_DONE, |
| 178 | AST_IO_VGACRE5_EDID_READ_DONE); |
| 179 | |
| 180 | mutex_unlock(lock: &ast->modeset_lock); |
| 181 | |
| 182 | return ret; |
| 183 | } |
| 184 | |
| 185 | /* |
| 186 | * Launch Aspeed DP |
| 187 | */ |
| 188 | int ast_dp_launch(struct ast_device *ast) |
| 189 | { |
| 190 | struct drm_device *dev = &ast->base; |
| 191 | unsigned int i = 10; |
| 192 | |
| 193 | while (i) { |
| 194 | u8 vgacrd1 = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xd1); |
| 195 | |
| 196 | if (vgacrd1 & AST_IO_VGACRD1_MCU_FW_EXECUTING) |
| 197 | break; |
| 198 | --i; |
| 199 | msleep(msecs: 100); |
| 200 | } |
| 201 | if (!i) { |
| 202 | drm_err(dev, "Wait DPMCU executing timeout\n" ); |
| 203 | return -ENODEV; |
| 204 | } |
| 205 | |
| 206 | ast_set_index_reg_mask(ast, AST_IO_VGACRI, index: 0xe5, |
| 207 | preserve_mask: (u8) ~AST_IO_VGACRE5_EDID_READ_DONE, |
| 208 | AST_IO_VGACRE5_EDID_READ_DONE); |
| 209 | |
| 210 | return 0; |
| 211 | } |
| 212 | |
| 213 | static bool ast_dp_get_phy_sleep(struct ast_device *ast) |
| 214 | { |
| 215 | u8 vgacre3 = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xe3); |
| 216 | |
| 217 | return (vgacre3 & AST_IO_VGACRE3_DP_PHY_SLEEP); |
| 218 | } |
| 219 | |
| 220 | static void ast_dp_set_phy_sleep(struct ast_device *ast, bool sleep) |
| 221 | { |
| 222 | u8 vgacre3 = 0x00; |
| 223 | |
| 224 | if (sleep) |
| 225 | vgacre3 |= AST_IO_VGACRE3_DP_PHY_SLEEP; |
| 226 | |
| 227 | ast_set_index_reg_mask(ast, AST_IO_VGACRI, index: 0xe3, preserve_mask: (u8)~AST_IO_VGACRE3_DP_PHY_SLEEP, |
| 228 | val: vgacre3); |
| 229 | msleep(msecs: 50); |
| 230 | } |
| 231 | |
| 232 | static void ast_dp_link_training(struct ast_device *ast) |
| 233 | { |
| 234 | struct drm_device *dev = &ast->base; |
| 235 | int i; |
| 236 | |
| 237 | for (i = 0; i < 10; i++) { |
| 238 | u8 vgacrdc; |
| 239 | |
| 240 | if (i) |
| 241 | msleep(msecs: 100); |
| 242 | |
| 243 | vgacrdc = ast_get_index_reg(ast, AST_IO_VGACRI, index: 0xdc); |
| 244 | if (vgacrdc & AST_IO_VGACRDC_LINK_SUCCESS) |
| 245 | return; |
| 246 | } |
| 247 | drm_err(dev, "Link training failed\n" ); |
| 248 | } |
| 249 | |
| 250 | static bool __ast_dp_wait_enable(struct ast_device *ast, bool enabled) |
| 251 | { |
| 252 | u8 vgacrdf_test = 0x00; |
| 253 | u8 vgacrdf; |
| 254 | unsigned int i; |
| 255 | |
| 256 | if (enabled) |
| 257 | vgacrdf_test |= AST_IO_VGACRDF_DP_VIDEO_ENABLE; |
| 258 | |
| 259 | for (i = 0; i < 1000; ++i) { |
| 260 | if (i) |
| 261 | mdelay(1); |
| 262 | vgacrdf = ast_get_index_reg_mask(ast, AST_IO_VGACRI, index: 0xdf, |
| 263 | AST_IO_VGACRDF_DP_VIDEO_ENABLE); |
| 264 | if (vgacrdf == vgacrdf_test) |
| 265 | return true; |
| 266 | } |
| 267 | |
| 268 | return false; |
| 269 | } |
| 270 | |
| 271 | static void ast_dp_set_enable(struct ast_device *ast, bool enabled) |
| 272 | { |
| 273 | struct drm_device *dev = &ast->base; |
| 274 | u8 vgacre3 = 0x00; |
| 275 | |
| 276 | if (enabled) |
| 277 | vgacre3 |= AST_IO_VGACRE3_DP_VIDEO_ENABLE; |
| 278 | |
| 279 | ast_set_index_reg_mask(ast, AST_IO_VGACRI, index: 0xe3, preserve_mask: (u8)~AST_IO_VGACRE3_DP_VIDEO_ENABLE, |
| 280 | val: vgacre3); |
| 281 | |
| 282 | drm_WARN_ON(dev, !__ast_dp_wait_enable(ast, enabled)); |
| 283 | } |
| 284 | |
| 285 | static void ast_wait_for_vretrace(struct ast_device *ast) |
| 286 | { |
| 287 | unsigned long timeout = jiffies + HZ; |
| 288 | u8 vgair1; |
| 289 | |
| 290 | do { |
| 291 | vgair1 = ast_io_read8(ast, AST_IO_VGAIR1_R); |
| 292 | } while (!(vgair1 & AST_IO_VGAIR1_VREFRESH) && time_before(jiffies, timeout)); |
| 293 | } |
| 294 | |
| 295 | /* |
| 296 | * Encoder |
| 297 | */ |
| 298 | |
| 299 | static const struct drm_encoder_funcs ast_astdp_encoder_funcs = { |
| 300 | .destroy = drm_encoder_cleanup, |
| 301 | }; |
| 302 | |
| 303 | static enum drm_mode_status |
| 304 | ast_astdp_encoder_helper_mode_valid(struct drm_encoder *encoder, |
| 305 | const struct drm_display_mode *mode) |
| 306 | { |
| 307 | int res; |
| 308 | |
| 309 | res = ast_astdp_get_mode_index(hdisplay: mode->hdisplay, vdisplay: mode->vdisplay); |
| 310 | if (res < 0) |
| 311 | return MODE_NOMODE; |
| 312 | |
| 313 | return MODE_OK; |
| 314 | } |
| 315 | |
| 316 | static void ast_astdp_encoder_helper_atomic_mode_set(struct drm_encoder *encoder, |
| 317 | struct drm_crtc_state *crtc_state, |
| 318 | struct drm_connector_state *conn_state) |
| 319 | { |
| 320 | struct drm_device *dev = encoder->dev; |
| 321 | struct ast_device *ast = to_ast_device(dev); |
| 322 | struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state); |
| 323 | const struct ast_vbios_enhtable *vmode = ast_crtc_state->vmode; |
| 324 | struct ast_astdp_connector_state *astdp_conn_state = |
| 325 | to_ast_astdp_connector_state(state: conn_state); |
| 326 | int mode_index = astdp_conn_state->mode_index; |
| 327 | u8 refresh_rate_index; |
| 328 | u8 vgacre0, vgacre1, vgacre2; |
| 329 | |
| 330 | if (drm_WARN_ON(dev, vmode->refresh_rate_index < 1 || vmode->refresh_rate_index > 255)) |
| 331 | return; |
| 332 | refresh_rate_index = vmode->refresh_rate_index - 1; |
| 333 | |
| 334 | /* FIXME: Why are we doing this? */ |
| 335 | switch (mode_index) { |
| 336 | case ASTDP_1280x800_60_RB: |
| 337 | case ASTDP_1440x900_60_RB: |
| 338 | case ASTDP_1600x900_60_RB: |
| 339 | case ASTDP_1680x1050_60_RB: |
| 340 | mode_index = (u8)(mode_index - (u8)refresh_rate_index); |
| 341 | break; |
| 342 | default: |
| 343 | mode_index = (u8)(mode_index + (u8)refresh_rate_index); |
| 344 | break; |
| 345 | } |
| 346 | |
| 347 | /* |
| 348 | * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp) |
| 349 | * CRE1[7:0]: MISC1 (default: 0x00) |
| 350 | * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50) |
| 351 | */ |
| 352 | vgacre0 = AST_IO_VGACRE0_24BPP; |
| 353 | vgacre1 = 0x00; |
| 354 | vgacre2 = mode_index & 0xff; |
| 355 | |
| 356 | ast_set_index_reg(ast, AST_IO_VGACRI, index: 0xe0, val: vgacre0); |
| 357 | ast_set_index_reg(ast, AST_IO_VGACRI, index: 0xe1, val: vgacre1); |
| 358 | ast_set_index_reg(ast, AST_IO_VGACRI, index: 0xe2, val: vgacre2); |
| 359 | } |
| 360 | |
| 361 | static void ast_astdp_encoder_helper_atomic_enable(struct drm_encoder *encoder, |
| 362 | struct drm_atomic_state *state) |
| 363 | { |
| 364 | struct ast_device *ast = to_ast_device(dev: encoder->dev); |
| 365 | struct ast_connector *ast_connector = &ast->output.astdp.connector; |
| 366 | |
| 367 | if (ast_connector->physical_status == connector_status_connected) { |
| 368 | ast_dp_set_phy_sleep(ast, sleep: false); |
| 369 | ast_dp_link_training(ast); |
| 370 | |
| 371 | ast_wait_for_vretrace(ast); |
| 372 | ast_dp_set_enable(ast, enabled: true); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | static void ast_astdp_encoder_helper_atomic_disable(struct drm_encoder *encoder, |
| 377 | struct drm_atomic_state *state) |
| 378 | { |
| 379 | struct ast_device *ast = to_ast_device(dev: encoder->dev); |
| 380 | |
| 381 | ast_dp_set_enable(ast, enabled: false); |
| 382 | ast_dp_set_phy_sleep(ast, sleep: true); |
| 383 | } |
| 384 | |
| 385 | static int ast_astdp_encoder_helper_atomic_check(struct drm_encoder *encoder, |
| 386 | struct drm_crtc_state *crtc_state, |
| 387 | struct drm_connector_state *conn_state) |
| 388 | { |
| 389 | const struct drm_display_mode *mode = &crtc_state->mode; |
| 390 | struct ast_astdp_connector_state *astdp_conn_state = |
| 391 | to_ast_astdp_connector_state(state: conn_state); |
| 392 | int res; |
| 393 | |
| 394 | if (drm_atomic_crtc_needs_modeset(state: crtc_state)) { |
| 395 | res = ast_astdp_get_mode_index(hdisplay: mode->hdisplay, vdisplay: mode->vdisplay); |
| 396 | if (res < 0) |
| 397 | return res; |
| 398 | astdp_conn_state->mode_index = res; |
| 399 | } |
| 400 | |
| 401 | return 0; |
| 402 | } |
| 403 | |
| 404 | static const struct drm_encoder_helper_funcs ast_astdp_encoder_helper_funcs = { |
| 405 | .mode_valid = ast_astdp_encoder_helper_mode_valid, |
| 406 | .atomic_mode_set = ast_astdp_encoder_helper_atomic_mode_set, |
| 407 | .atomic_enable = ast_astdp_encoder_helper_atomic_enable, |
| 408 | .atomic_disable = ast_astdp_encoder_helper_atomic_disable, |
| 409 | .atomic_check = ast_astdp_encoder_helper_atomic_check, |
| 410 | }; |
| 411 | |
| 412 | /* |
| 413 | * Connector |
| 414 | */ |
| 415 | |
| 416 | static int ast_astdp_connector_helper_get_modes(struct drm_connector *connector) |
| 417 | { |
| 418 | struct ast_connector *ast_connector = to_ast_connector(connector); |
| 419 | int count; |
| 420 | |
| 421 | if (ast_connector->physical_status == connector_status_connected) { |
| 422 | struct ast_device *ast = to_ast_device(dev: connector->dev); |
| 423 | const struct drm_edid *drm_edid; |
| 424 | |
| 425 | drm_edid = drm_edid_read_custom(connector, read_block: ast_astdp_read_edid_block, context: ast); |
| 426 | drm_edid_connector_update(connector, edid: drm_edid); |
| 427 | count = drm_edid_connector_add_modes(connector); |
| 428 | drm_edid_free(drm_edid); |
| 429 | } else { |
| 430 | drm_edid_connector_update(connector, NULL); |
| 431 | |
| 432 | /* |
| 433 | * There's no EDID data without a connected monitor. Set BMC- |
| 434 | * compatible modes in this case. The XGA default resolution |
| 435 | * should work well for all BMCs. |
| 436 | */ |
| 437 | count = drm_add_modes_noedid(connector, hdisplay: 4096, vdisplay: 4096); |
| 438 | if (count) |
| 439 | drm_set_preferred_mode(connector, hpref: 1024, vpref: 768); |
| 440 | } |
| 441 | |
| 442 | return count; |
| 443 | } |
| 444 | |
| 445 | static int ast_astdp_connector_helper_detect_ctx(struct drm_connector *connector, |
| 446 | struct drm_modeset_acquire_ctx *ctx, |
| 447 | bool force) |
| 448 | { |
| 449 | struct ast_connector *ast_connector = to_ast_connector(connector); |
| 450 | struct ast_device *ast = to_ast_device(dev: connector->dev); |
| 451 | enum drm_connector_status status = connector_status_disconnected; |
| 452 | bool phy_sleep; |
| 453 | |
| 454 | mutex_lock(&ast->modeset_lock); |
| 455 | |
| 456 | phy_sleep = ast_dp_get_phy_sleep(ast); |
| 457 | if (phy_sleep) |
| 458 | ast_dp_set_phy_sleep(ast, sleep: false); |
| 459 | |
| 460 | if (ast_astdp_is_connected(ast)) |
| 461 | status = connector_status_connected; |
| 462 | |
| 463 | if (phy_sleep && status == connector_status_disconnected) |
| 464 | ast_dp_set_phy_sleep(ast, sleep: true); |
| 465 | |
| 466 | mutex_unlock(lock: &ast->modeset_lock); |
| 467 | |
| 468 | if (status != ast_connector->physical_status) |
| 469 | ++connector->epoch_counter; |
| 470 | ast_connector->physical_status = status; |
| 471 | |
| 472 | return connector_status_connected; |
| 473 | } |
| 474 | |
| 475 | static const struct drm_connector_helper_funcs ast_astdp_connector_helper_funcs = { |
| 476 | .get_modes = ast_astdp_connector_helper_get_modes, |
| 477 | .detect_ctx = ast_astdp_connector_helper_detect_ctx, |
| 478 | }; |
| 479 | |
| 480 | static void ast_astdp_connector_reset(struct drm_connector *connector) |
| 481 | { |
| 482 | struct ast_astdp_connector_state *astdp_state = |
| 483 | kzalloc(sizeof(*astdp_state), GFP_KERNEL); |
| 484 | |
| 485 | if (connector->state) |
| 486 | connector->funcs->atomic_destroy_state(connector, connector->state); |
| 487 | |
| 488 | if (astdp_state) |
| 489 | __drm_atomic_helper_connector_reset(connector, conn_state: &astdp_state->base); |
| 490 | else |
| 491 | __drm_atomic_helper_connector_reset(connector, NULL); |
| 492 | } |
| 493 | |
| 494 | static struct drm_connector_state * |
| 495 | ast_astdp_connector_atomic_duplicate_state(struct drm_connector *connector) |
| 496 | { |
| 497 | struct ast_astdp_connector_state *new_astdp_state, *astdp_state; |
| 498 | struct drm_device *dev = connector->dev; |
| 499 | |
| 500 | if (drm_WARN_ON(dev, !connector->state)) |
| 501 | return NULL; |
| 502 | |
| 503 | new_astdp_state = kmalloc(sizeof(*new_astdp_state), GFP_KERNEL); |
| 504 | if (!new_astdp_state) |
| 505 | return NULL; |
| 506 | __drm_atomic_helper_connector_duplicate_state(connector, state: &new_astdp_state->base); |
| 507 | |
| 508 | astdp_state = to_ast_astdp_connector_state(state: connector->state); |
| 509 | |
| 510 | new_astdp_state->mode_index = astdp_state->mode_index; |
| 511 | |
| 512 | return &new_astdp_state->base; |
| 513 | } |
| 514 | |
| 515 | static void ast_astdp_connector_atomic_destroy_state(struct drm_connector *connector, |
| 516 | struct drm_connector_state *state) |
| 517 | { |
| 518 | struct ast_astdp_connector_state *astdp_state = to_ast_astdp_connector_state(state); |
| 519 | |
| 520 | __drm_atomic_helper_connector_destroy_state(state: &astdp_state->base); |
| 521 | kfree(objp: astdp_state); |
| 522 | } |
| 523 | |
| 524 | static const struct drm_connector_funcs ast_astdp_connector_funcs = { |
| 525 | .reset = ast_astdp_connector_reset, |
| 526 | .fill_modes = drm_helper_probe_single_connector_modes, |
| 527 | .destroy = drm_connector_cleanup, |
| 528 | .atomic_duplicate_state = ast_astdp_connector_atomic_duplicate_state, |
| 529 | .atomic_destroy_state = ast_astdp_connector_atomic_destroy_state, |
| 530 | }; |
| 531 | |
| 532 | /* |
| 533 | * Output |
| 534 | */ |
| 535 | |
| 536 | int ast_astdp_output_init(struct ast_device *ast) |
| 537 | { |
| 538 | struct drm_device *dev = &ast->base; |
| 539 | struct drm_crtc *crtc = &ast->crtc; |
| 540 | struct drm_encoder *encoder; |
| 541 | struct ast_connector *ast_connector; |
| 542 | struct drm_connector *connector; |
| 543 | int ret; |
| 544 | |
| 545 | /* encoder */ |
| 546 | |
| 547 | encoder = &ast->output.astdp.encoder; |
| 548 | ret = drm_encoder_init(dev, encoder, funcs: &ast_astdp_encoder_funcs, |
| 549 | DRM_MODE_ENCODER_TMDS, NULL); |
| 550 | if (ret) |
| 551 | return ret; |
| 552 | drm_encoder_helper_add(encoder, funcs: &ast_astdp_encoder_helper_funcs); |
| 553 | |
| 554 | encoder->possible_crtcs = drm_crtc_mask(crtc); |
| 555 | |
| 556 | /* connector */ |
| 557 | |
| 558 | ast_connector = &ast->output.astdp.connector; |
| 559 | connector = &ast_connector->base; |
| 560 | ret = drm_connector_init(dev, connector, funcs: &ast_astdp_connector_funcs, |
| 561 | DRM_MODE_CONNECTOR_DisplayPort); |
| 562 | if (ret) |
| 563 | return ret; |
| 564 | drm_connector_helper_add(connector, funcs: &ast_astdp_connector_helper_funcs); |
| 565 | |
| 566 | connector->interlace_allowed = 0; |
| 567 | connector->doublescan_allowed = 0; |
| 568 | connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; |
| 569 | |
| 570 | ast_connector->physical_status = connector->status; |
| 571 | |
| 572 | ret = drm_connector_attach_encoder(connector, encoder); |
| 573 | if (ret) |
| 574 | return ret; |
| 575 | |
| 576 | return 0; |
| 577 | } |
| 578 | |